Merge "Remove dnd, expand, alarm stream from volume dlg"
diff --git a/api/current.txt b/api/current.txt
index 12a3f07..6a8bb01 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -35177,6 +35177,7 @@
     field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
     field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
     field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
+    field public static final java.lang.String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
     field public static final java.lang.String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG";
     field public static final java.lang.String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON";
   }
@@ -40416,6 +40417,7 @@
     method public void sendMultimediaMessage(android.content.Context, android.net.Uri, java.lang.String, android.os.Bundle, android.app.PendingIntent);
     method public void sendMultipartTextMessage(java.lang.String, java.lang.String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
     method public void sendTextMessage(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
+    method public void sendTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
     field public static final java.lang.String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA";
     field public static final java.lang.String EXTRA_MMS_HTTP_STATUS = "android.telephony.extra.MMS_HTTP_STATUS";
     field public static final java.lang.String MMS_CONFIG_ALIAS_ENABLED = "aliasEnabled";
@@ -48971,6 +48973,7 @@
   public abstract interface TextClassifier {
     method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options);
     method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
+    method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
     field public static final android.view.textclassifier.TextClassifier NO_OP;
@@ -48981,6 +48984,36 @@
     field public static final java.lang.String TYPE_URL = "url";
   }
 
+  public final class TextLinks {
+    method public boolean apply(android.text.SpannableString, java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.text.style.ClickableSpan>);
+    method public java.util.Collection<android.view.textclassifier.TextLinks.TextLink> getLinks();
+  }
+
+  public static final class TextLinks.Builder {
+    ctor public TextLinks.Builder(java.lang.String);
+    method public android.view.textclassifier.TextLinks.Builder addLink(android.view.textclassifier.TextLinks.TextLink);
+    method public android.view.textclassifier.TextLinks build();
+  }
+
+  public static final class TextLinks.Options {
+    method public android.os.LocaleList getDefaultLocales();
+  }
+
+  public static final class TextLinks.Options.Builder {
+    ctor public TextLinks.Options.Builder();
+    method public android.view.textclassifier.TextLinks.Options build();
+    method public android.view.textclassifier.TextLinks.Options.Builder setLocaleList(android.os.LocaleList);
+  }
+
+  public static final class TextLinks.TextLink {
+    ctor public TextLinks.TextLink(java.lang.String, int, int, java.util.Map<java.lang.String, java.lang.Float>);
+    method public float getConfidenceScore(java.lang.String);
+    method public int getEnd();
+    method public java.lang.String getEntity(int);
+    method public int getEntityCount();
+    method public int getStart();
+  }
+
   public final class TextSelection {
     method public float getConfidenceScore(java.lang.String);
     method public java.lang.String getEntity(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 0b34782..e485e964e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -34726,6 +34726,7 @@
     field public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11; // 0xb
     field public static final int POST_INSTALL_RUNNER_ERROR = 5; // 0x5
     field public static final int SUCCESS = 0; // 0x0
+    field public static final int UPDATED_BUT_NOT_ACTIVE = 52; // 0x34
   }
 
   public static final class UpdateEngine.UpdateStatusConstants {
@@ -38243,6 +38244,7 @@
     field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
     field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
     field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
+    field public static final java.lang.String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
     field public static final java.lang.String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG";
     field public static final java.lang.String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON";
   }
@@ -52710,6 +52712,7 @@
   public abstract interface TextClassifier {
     method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options);
     method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
+    method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
     field public static final android.view.textclassifier.TextClassifier NO_OP;
@@ -52720,6 +52723,36 @@
     field public static final java.lang.String TYPE_URL = "url";
   }
 
+  public final class TextLinks {
+    method public boolean apply(android.text.SpannableString, java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.text.style.ClickableSpan>);
+    method public java.util.Collection<android.view.textclassifier.TextLinks.TextLink> getLinks();
+  }
+
+  public static final class TextLinks.Builder {
+    ctor public TextLinks.Builder(java.lang.String);
+    method public android.view.textclassifier.TextLinks.Builder addLink(android.view.textclassifier.TextLinks.TextLink);
+    method public android.view.textclassifier.TextLinks build();
+  }
+
+  public static final class TextLinks.Options {
+    method public android.os.LocaleList getDefaultLocales();
+  }
+
+  public static final class TextLinks.Options.Builder {
+    ctor public TextLinks.Options.Builder();
+    method public android.view.textclassifier.TextLinks.Options build();
+    method public android.view.textclassifier.TextLinks.Options.Builder setLocaleList(android.os.LocaleList);
+  }
+
+  public static final class TextLinks.TextLink {
+    ctor public TextLinks.TextLink(java.lang.String, int, int, java.util.Map<java.lang.String, java.lang.Float>);
+    method public float getConfidenceScore(java.lang.String);
+    method public int getEnd();
+    method public java.lang.String getEntity(int);
+    method public int getEntityCount();
+    method public int getStart();
+  }
+
   public final class TextSelection {
     method public float getConfidenceScore(java.lang.String);
     method public java.lang.String getEntity(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 4eba70c..40f732c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -35449,6 +35449,7 @@
     field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
     field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
     field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
+    field public static final java.lang.String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
     field public static final java.lang.String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG";
     field public static final java.lang.String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON";
   }
@@ -40814,6 +40815,7 @@
     method public void sendMultimediaMessage(android.content.Context, android.net.Uri, java.lang.String, android.os.Bundle, android.app.PendingIntent);
     method public void sendMultipartTextMessage(java.lang.String, java.lang.String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
     method public void sendTextMessage(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
+    method public void sendTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
     field public static final java.lang.String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA";
     field public static final java.lang.String EXTRA_MMS_HTTP_STATUS = "android.telephony.extra.MMS_HTTP_STATUS";
     field public static final java.lang.String MMS_CONFIG_ALIAS_ENABLED = "aliasEnabled";
@@ -49608,6 +49610,7 @@
   public abstract interface TextClassifier {
     method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options);
     method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
+    method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
     field public static final android.view.textclassifier.TextClassifier NO_OP;
@@ -49618,6 +49621,36 @@
     field public static final java.lang.String TYPE_URL = "url";
   }
 
+  public final class TextLinks {
+    method public boolean apply(android.text.SpannableString, java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.text.style.ClickableSpan>);
+    method public java.util.Collection<android.view.textclassifier.TextLinks.TextLink> getLinks();
+  }
+
+  public static final class TextLinks.Builder {
+    ctor public TextLinks.Builder(java.lang.String);
+    method public android.view.textclassifier.TextLinks.Builder addLink(android.view.textclassifier.TextLinks.TextLink);
+    method public android.view.textclassifier.TextLinks build();
+  }
+
+  public static final class TextLinks.Options {
+    method public android.os.LocaleList getDefaultLocales();
+  }
+
+  public static final class TextLinks.Options.Builder {
+    ctor public TextLinks.Options.Builder();
+    method public android.view.textclassifier.TextLinks.Options build();
+    method public android.view.textclassifier.TextLinks.Options.Builder setLocaleList(android.os.LocaleList);
+  }
+
+  public static final class TextLinks.TextLink {
+    ctor public TextLinks.TextLink(java.lang.String, int, int, java.util.Map<java.lang.String, java.lang.Float>);
+    method public float getConfidenceScore(java.lang.String);
+    method public int getEnd();
+    method public java.lang.String getEntity(int);
+    method public int getEntityCount();
+    method public int getStart();
+  }
+
   public final class TextSelection {
     method public float getConfidenceScore(java.lang.String);
     method public java.lang.String getEntity(int);
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index a9a4118..699de94 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -20,6 +20,9 @@
 import static android.os.storage.StorageManager.PROP_HAS_ADOPTABLE;
 import static android.os.storage.StorageManager.PROP_VIRTUAL_DISK;
 
+import android.os.IBinder;
+import android.os.IVoldTaskListener;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -29,6 +32,8 @@
 import android.os.storage.VolumeInfo;
 import android.util.Log;
 
+import java.util.concurrent.CompletableFuture;
+
 public final class Sm {
     private static final String TAG = "Sm";
 
@@ -221,9 +226,23 @@
         mSm.format(volId);
     }
 
-    public void runBenchmark() throws RemoteException {
+    public void runBenchmark() throws Exception {
         final String volId = nextArg();
-        mSm.benchmark(volId);
+        final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+        mSm.benchmark(volId, new IVoldTaskListener.Stub() {
+            @Override
+            public void onStatus(int status, PersistableBundle extras) {
+                // Ignored
+            }
+
+            @Override
+            public void onFinished(int status, PersistableBundle extras) {
+                // Touch to unparcel
+                extras.size();
+                result.complete(extras);
+            }
+        });
+        System.out.println(result.get());
     }
 
     public void runForget() throws RemoteException {
@@ -235,8 +254,22 @@
         }
     }
 
-    public void runFstrim() throws RemoteException {
-        mSm.fstrim(0);
+    public void runFstrim() throws Exception {
+        final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+        mSm.fstrim(0, new IVoldTaskListener.Stub() {
+            @Override
+            public void onStatus(int status, PersistableBundle extras) {
+                // Ignored
+            }
+
+            @Override
+            public void onFinished(int status, PersistableBundle extras) {
+                // Touch to unparcel
+                extras.size();
+                result.complete(extras);
+            }
+        });
+        System.out.println(result.get());
     }
 
     public void runSetVirtualDisk() throws RemoteException {
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 100a7a4..94f4adf 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -24,7 +24,11 @@
 #include <limits.h>
 #include <stdlib.h>
 
-using namespace android::util;
+using android::util::FIELD_TYPE_BOOL;
+using android::util::FIELD_TYPE_FLOAT;
+using android::util::FIELD_TYPE_INT32;
+using android::util::FIELD_TYPE_INT64;
+using android::util::FIELD_TYPE_MESSAGE;
 using android::util::ProtoOutputStream;
 using std::map;
 using std::string;
@@ -166,17 +170,8 @@
     mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
                   (long long)mCurrentBucketStartTimeNs);
 
-    size_t bufferSize = mProto->size();
-    std::unique_ptr<uint8_t[]> buffer(new uint8_t[bufferSize]);
-
-    size_t pos = 0;
-    auto it = mProto->data();
-    while (it.readBuffer() != NULL) {
-        size_t toRead = it.currentToRead();
-        std::memcpy(&buffer[pos], it.readBuffer(), toRead);
-        pos += toRead;
-        it.rp()->move(toRead);
-    }
+    VLOG("metric %lld dump report now...", mMetric.metric_id());
+    std::unique_ptr<uint8_t[]> buffer = serializeProto();
 
     startNewProtoOutputStream(endTime);
     mPastBuckets.clear();
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 3bfc724..c3af006 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -53,6 +53,7 @@
 
     void finish() override;
 
+    // TODO: Pass a timestamp as a parameter in onDumpReport.
     StatsLogReport onDumpReport() override;
 
     void onSlicedConditionMayChange(const uint64_t eventTime) override;
@@ -70,9 +71,12 @@
                                    bool condition, const LogEvent& event,
                                    bool scheduledPull) override;
 
+    void startNewProtoOutputStream(long long timestamp) override;
+
 private:
     const CountMetric mMetric;
 
+    // TODO: Add a lock to mPastBuckets.
     std::unordered_map<HashableDimensionKey, std::vector<CountBucket>> mPastBuckets;
 
     size_t mByteSize;
@@ -84,12 +88,6 @@
 
     void flushCounterIfNeeded(const uint64_t newEventTime);
 
-    std::unique_ptr<android::util::ProtoOutputStream> mProto;
-
-    long long mProtoToken;
-
-    void startNewProtoOutputStream(long long timestamp);
-
     FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 09132bf..c0a0d98 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -23,6 +23,12 @@
 #include <limits.h>
 #include <stdlib.h>
 
+using android::util::FIELD_TYPE_BOOL;
+using android::util::FIELD_TYPE_FLOAT;
+using android::util::FIELD_TYPE_INT32;
+using android::util::FIELD_TYPE_INT64;
+using android::util::FIELD_TYPE_MESSAGE;
+using android::util::ProtoOutputStream;
 using std::string;
 using std::unordered_map;
 using std::vector;
@@ -31,6 +37,27 @@
 namespace os {
 namespace statsd {
 
+// for StatsLogReport
+const int FIELD_ID_METRIC_ID = 1;
+const int FIELD_ID_START_REPORT_NANOS = 2;
+const int FIELD_ID_END_REPORT_NANOS = 3;
+const int FIELD_ID_DURATION_METRICS = 6;
+// for DurationMetricDataWrapper
+const int FIELD_ID_DATA = 1;
+// for DurationMetricData
+const int FIELD_ID_DIMENSION = 1;
+const int FIELD_ID_BUCKET_INFO = 2;
+// for KeyValuePair
+const int FIELD_ID_KEY = 1;
+const int FIELD_ID_VALUE_STR = 2;
+const int FIELD_ID_VALUE_INT = 3;
+const int FIELD_ID_VALUE_BOOL = 4;
+const int FIELD_ID_VALUE_FLOAT = 5;
+// for DurationBucketInfo
+const int FIELD_ID_START_BUCKET_NANOS = 1;
+const int FIELD_ID_END_BUCKET_NANOS = 2;
+const int FIELD_ID_DURATION = 3;
+
 DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric,
                                                const int conditionIndex, const size_t startIndex,
                                                const size_t stopIndex, const size_t stopAllIndex,
@@ -61,6 +88,8 @@
         mConditionSliced = true;
     }
 
+    startNewProtoOutputStream(mStartTimeNs);
+
     VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
          (long long)mBucketSizeNs, (long long)mStartTimeNs);
 }
@@ -69,8 +98,15 @@
     VLOG("~DurationMetric() called");
 }
 
+void DurationMetricProducer::startNewProtoOutputStream(long long startTime) {
+    mProto = std::make_unique<ProtoOutputStream>();
+    mProto->write(FIELD_TYPE_INT32 | FIELD_ID_METRIC_ID, mMetric.metric_id());
+    mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, startTime);
+    mProtoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS);
+}
+
 unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
-        vector<DurationBucketInfo>& bucket) {
+        vector<DurationBucket>& bucket) {
     switch (mMetric.type()) {
         case DurationMetric_AggregationType_DURATION_SUM:
             return make_unique<OringDurationTracker>(mWizard, mConditionTrackerIndex,
@@ -124,29 +160,69 @@
 }
 
 StatsLogReport DurationMetricProducer::onDumpReport() {
-    VLOG("metric %lld dump report now...", mMetric.metric_id());
-    StatsLogReport report;
-    report.set_metric_id(mMetric.metric_id());
-    report.set_start_report_nanos(mStartTimeNs);
+    long long endTime = time(nullptr) * NS_PER_SEC;
+
     // Dump current bucket if it's stale.
     // If current bucket is still on-going, don't force dump current bucket.
     // In finish(), We can force dump current bucket.
-    flushIfNeeded(time(nullptr) * NS_PER_SEC);
-    report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+    flushIfNeeded(endTime);
+    VLOG("metric %lld dump report now...", mMetric.metric_id());
 
-    StatsLogReport_DurationMetricDataWrapper* wrapper = report.mutable_duration_metrics();
     for (const auto& pair : mPastBuckets) {
         const HashableDimensionKey& hashableKey = pair.first;
+        VLOG("  dimension key %s", hashableKey.c_str());
         auto it = mDimensionKeyMap.find(hashableKey);
         if (it == mDimensionKeyMap.end()) {
             ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
             continue;
         }
-        VLOG("  dimension key %s", hashableKey.c_str());
-        addDurationBucketsToReport(*wrapper, it->second, pair.second);
+        long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
+
+        // First fill dimension (KeyValuePairs).
+        for (const auto& kv : it->second) {
+            long long dimensionToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION);
+            mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
+            if (kv.has_value_str()) {
+                mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+            } else if (kv.has_value_int()) {
+                mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
+            } else if (kv.has_value_bool()) {
+                mProto->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool());
+            } else if (kv.has_value_float()) {
+                mProto->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float());
+            }
+            mProto->end(dimensionToken);
+        }
+
+        // Then fill bucket_info (DurationBucketInfo).
+        for (const auto& bucket : pair.second) {
+            long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+            mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
+                          (long long)bucket.mBucketStartNs);
+            mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
+                          (long long)bucket.mBucketEndNs);
+            mProto->write(FIELD_TYPE_INT64 | FIELD_ID_DURATION, (long long)bucket.mDuration);
+            mProto->end(bucketInfoToken);
+            VLOG("\t bucket [%lld - %lld] duration: %lld", (long long)bucket.mBucketStartNs,
+                 (long long)bucket.mBucketEndNs, (long long)bucket.mDuration);
+        }
+
+        mProto->end(wrapperToken);
     }
-    return report;
-};
+
+    mProto->end(mProtoToken);
+    mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
+                  (long long)mCurrentBucketStartTimeNs);
+
+    std::unique_ptr<uint8_t[]> buffer = serializeProto();
+
+    startNewProtoOutputStream(endTime);
+    mPastBuckets.clear();
+
+    // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
+    // return std::move(buffer);
+    return StatsLogReport();
+}
 
 void DurationMetricProducer::flushIfNeeded(uint64_t eventTime) {
     if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
@@ -188,17 +264,17 @@
 
     if (matcherIndex == mStartIndex) {
         it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys);
-
     } else if (matcherIndex == mStopIndex) {
         it->second->noteStop(atomKey, event.GetTimestampNs());
     }
 }
 
 size_t DurationMetricProducer::byteSize() {
-    // TODO: return actual proto size when ProtoOutputStream is ready for use for
-    // DurationMetricsProducer.
-    //    return mProto->size();
-    return 0;
+  size_t totalSize = 0;
+  for (const auto& pair : mPastBuckets) {
+      totalSize += pair.second.size() * kBucketSize;
+  }
+  return totalSize;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 12ff58e..8fdd0d4 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -19,6 +19,7 @@
 
 #include <unordered_map>
 
+#include <android/util/ProtoOutputStream.h>
 #include "../condition/ConditionTracker.h"
 #include "../matchers/matcher_util.h"
 #include "MetricProducer.h"
@@ -48,6 +49,7 @@
 
     void finish() override;
 
+    // TODO: Pass a timestamp as a parameter in onDumpReport.
     StatsLogReport onDumpReport() override;
 
     void onSlicedConditionMayChange(const uint64_t eventTime) override;
@@ -65,6 +67,8 @@
                                    bool condition, const LogEvent& event,
                                    bool scheduledPull) override;
 
+    void startNewProtoOutputStream(long long timestamp) override;
+
 private:
     const DurationMetric mMetric;
 
@@ -81,7 +85,8 @@
     const vector<KeyMatcher> mInternalDimension;
 
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
-    std::unordered_map<HashableDimensionKey, std::vector<DurationBucketInfo>> mPastBuckets;
+    // TODO: Add a lock to mPastBuckets.
+    std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>> mPastBuckets;
 
     // The current bucket.
     std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
@@ -89,9 +94,11 @@
 
     void flushDurationIfNeeded(const uint64_t newEventTime);
 
-    std::unique_ptr<DurationTracker> createDurationTracker(std::vector<DurationBucketInfo>& bucket);
+    std::unique_ptr<DurationTracker> createDurationTracker(std::vector<DurationBucket>& bucket);
 
     void flushIfNeeded(uint64_t timestamp);
+
+    static const size_t kBucketSize = sizeof(DurationBucket{});
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 677ae38..ee0bfde 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -23,7 +23,11 @@
 #include <limits.h>
 #include <stdlib.h>
 
-using namespace android::util;
+using android::util::FIELD_TYPE_BOOL;
+using android::util::FIELD_TYPE_FLOAT;
+using android::util::FIELD_TYPE_INT32;
+using android::util::FIELD_TYPE_INT64;
+using android::util::FIELD_TYPE_MESSAGE;
 using android::util::ProtoOutputStream;
 using std::map;
 using std::string;
@@ -87,15 +91,7 @@
 
     size_t bufferSize = mProto->size();
     VLOG("metric %lld dump report now... proto size: %zu ", mMetric.metric_id(), bufferSize);
-    std::unique_ptr<uint8_t[]> buffer(new uint8_t[bufferSize]);
-    size_t pos = 0;
-    auto it = mProto->data();
-    while (it.readBuffer() != NULL) {
-        size_t toRead = it.currentToRead();
-        std::memcpy(&buffer[pos], it.readBuffer(), toRead);
-        pos += toRead;
-        it.rp()->move(toRead);
-    }
+    std::unique_ptr<uint8_t[]> buffer = serializeProto();
 
     startNewProtoOutputStream(endTime);
 
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 0fc2b5b..2ca8181 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -49,6 +49,7 @@
 
     void finish() override;
 
+    // TODO: Pass a timestamp as a parameter in onDumpReport.
     StatsLogReport onDumpReport() override;
 
     void onSlicedConditionMayChange(const uint64_t eventTime) override;
@@ -60,14 +61,11 @@
     // TODO: Implement this later.
     virtual void notifyAppRemoved(const string& apk, const int uid) override{};
 
+protected:
+    void startNewProtoOutputStream(long long timestamp) override;
+
 private:
     const EventMetric mMetric;
-
-    std::unique_ptr<android::util::ProtoOutputStream> mProto;
-
-    long long mProtoToken;
-
-    void startNewProtoOutputStream(long long timestamp);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 285c8f4..dcfcc19 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -24,6 +24,12 @@
 #include <limits.h>
 #include <stdlib.h>
 
+using android::util::FIELD_TYPE_BOOL;
+using android::util::FIELD_TYPE_FLOAT;
+using android::util::FIELD_TYPE_INT32;
+using android::util::FIELD_TYPE_INT64;
+using android::util::FIELD_TYPE_MESSAGE;
+using android::util::ProtoOutputStream;
 using std::map;
 using std::string;
 using std::unordered_map;
@@ -33,6 +39,27 @@
 namespace os {
 namespace statsd {
 
+// for StatsLogReport
+const int FIELD_ID_METRIC_ID = 1;
+const int FIELD_ID_START_REPORT_NANOS = 2;
+const int FIELD_ID_END_REPORT_NANOS = 3;
+const int FIELD_ID_GAUGE_METRICS = 8;
+// for GaugeMetricDataWrapper
+const int FIELD_ID_DATA = 1;
+// for GaugeMetricData
+const int FIELD_ID_DIMENSION = 1;
+const int FIELD_ID_BUCKET_INFO = 2;
+// for KeyValuePair
+const int FIELD_ID_KEY = 1;
+const int FIELD_ID_VALUE_STR = 2;
+const int FIELD_ID_VALUE_INT = 3;
+const int FIELD_ID_VALUE_BOOL = 4;
+const int FIELD_ID_VALUE_FLOAT = 5;
+// for GaugeBucketInfo
+const int FIELD_ID_START_BUCKET_NANOS = 1;
+const int FIELD_ID_END_BUCKET_NANOS = 2;
+const int FIELD_ID_GAUGE = 3;
+
 GaugeMetricProducer::GaugeMetricProducer(const GaugeMetric& metric, const int conditionIndex,
                                          const sp<ConditionWizard>& wizard, const int pullTagId)
     : MetricProducer((time(nullptr) * NS_PER_SEC), conditionIndex, wizard),
@@ -59,6 +86,8 @@
                                              metric.bucket().bucket_size_millis());
     }
 
+    startNewProtoOutputStream(mStartTimeNs);
+
     VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
          (long long)mBucketSizeNs, (long long)mStartTimeNs);
 }
@@ -67,37 +96,23 @@
     VLOG("~GaugeMetricProducer() called");
 }
 
-void GaugeMetricProducer::finish() {
+void GaugeMetricProducer::startNewProtoOutputStream(long long startTime) {
+    mProto = std::make_unique<ProtoOutputStream>();
+    mProto->write(FIELD_TYPE_INT32 | FIELD_ID_METRIC_ID, mMetric.metric_id());
+    mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, startTime);
+    mProtoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS);
 }
 
-static void addSlicedGaugeToReport(const vector<KeyValuePair>& key,
-                                   const vector<GaugeBucketInfo>& buckets,
-                                   StatsLogReport_GaugeMetricDataWrapper& wrapper) {
-    GaugeMetricData* data = wrapper.add_data();
-    for (const auto& kv : key) {
-        data->add_dimension()->CopyFrom(kv);
-    }
-    for (const auto& bucket : buckets) {
-        data->add_bucket_info()->CopyFrom(bucket);
-        VLOG("\t bucket [%lld - %lld] gauge: %lld", bucket.start_bucket_nanos(),
-             bucket.end_bucket_nanos(), bucket.gauge());
-    }
+void GaugeMetricProducer::finish() {
 }
 
 StatsLogReport GaugeMetricProducer::onDumpReport() {
     VLOG("gauge metric %lld dump report now...", mMetric.metric_id());
 
-    StatsLogReport report;
-    report.set_metric_id(mMetric.metric_id());
-    report.set_start_report_nanos(mStartTimeNs);
-
     // Dump current bucket if it's stale.
     // If current bucket is still on-going, don't force dump current bucket.
     // In finish(), We can force dump current bucket.
     flushGaugeIfNeededLocked(time(nullptr) * NS_PER_SEC);
-    report.set_end_report_nanos(mCurrentBucketStartTimeNs);
-
-    StatsLogReport_GaugeMetricDataWrapper* wrapper = report.mutable_gauge_metrics();
 
     for (const auto& pair : mPastBuckets) {
         const HashableDimensionKey& hashableKey = pair.first;
@@ -108,10 +123,53 @@
         }
 
         VLOG("  dimension key %s", hashableKey.c_str());
-        addSlicedGaugeToReport(it->second, pair.second, *wrapper);
+        long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
+
+        // First fill dimension (KeyValuePairs).
+        for (const auto& kv : it->second) {
+            long long dimensionToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION);
+            mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
+            if (kv.has_value_str()) {
+                mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+            } else if (kv.has_value_int()) {
+                mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
+            } else if (kv.has_value_bool()) {
+                mProto->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool());
+            } else if (kv.has_value_float()) {
+                mProto->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float());
+            }
+            mProto->end(dimensionToken);
+        }
+
+        // Then fill bucket_info (GaugeBucketInfo).
+        for (const auto& bucket : pair.second) {
+            long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+            mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
+                          (long long)bucket.mBucketStartNs);
+            mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
+                          (long long)bucket.mBucketEndNs);
+            mProto->write(FIELD_TYPE_INT64 | FIELD_ID_GAUGE, (long long)bucket.mGauge);
+            mProto->end(bucketInfoToken);
+            VLOG("\t bucket [%lld - %lld] count: %lld", (long long)bucket.mBucketStartNs,
+                 (long long)bucket.mBucketEndNs, (long long)bucket.mGauge);
+        }
+        mProto->end(wrapperToken);
     }
-    return report;
-    // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
+    mProto->end(mProtoToken);
+    mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
+                  (long long)mCurrentBucketStartTimeNs);
+
+    std::unique_ptr<uint8_t[]> buffer = serializeProto();
+
+    startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
+    mPastBuckets.clear();
+    mByteSize = 0;
+
+    // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
+    // return std::move(buffer);
+    return StatsLogReport();
+
+    // TODO: Clear mDimensionKeyMap once the report is dumped.
 }
 
 void GaugeMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
@@ -207,14 +265,15 @@
     // Adjusts the bucket start time
     int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
 
-    GaugeBucketInfo info;
-    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
-    info.set_end_bucket_nanos(mCurrentBucketStartTimeNs + mBucketSizeNs);
+    GaugeBucket info;
+    info.mBucketStartNs = mCurrentBucketStartTimeNs;
+    info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
 
     for (const auto& slice : mCurrentSlicedBucket) {
-        info.set_gauge(slice.second);
+        info.mGauge = slice.second;
         auto& bucketList = mPastBuckets[slice.first];
         bucketList.push_back(info);
+        mByteSize += sizeof(info);
 
         VLOG("gauge metric %lld, dump key value: %s -> %ld", mMetric.metric_id(),
              slice.first.c_str(), slice.second);
@@ -227,6 +286,10 @@
          (long long)mCurrentBucketStartTimeNs);
 }
 
+size_t GaugeMetricProducer::byteSize() {
+    return mByteSize;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index bf8a86f..3757174 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -18,6 +18,7 @@
 
 #include <unordered_map>
 
+#include <android/util/ProtoOutputStream.h>
 #include "../condition/ConditionTracker.h"
 #include "../external/PullDataReceiver.h"
 #include "../external/StatsPullerManager.h"
@@ -31,6 +32,12 @@
 namespace os {
 namespace statsd {
 
+struct GaugeBucket {
+    int64_t mBucketStartNs;
+    int64_t mBucketEndNs;
+    int64_t mGauge;
+};
+
 // This gauge metric producer first register the puller to automatically pull the gauge at the
 // beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise
 // proactively pull the gauge when the condition is changed to be true. Therefore, the gauge metric
@@ -52,12 +59,10 @@
 
     void finish() override;
 
+    // TODO: Pass a timestamp as a parameter in onDumpReport.
     StatsLogReport onDumpReport() override;
 
-    // TODO: implements it when supporting proto stream.
-    size_t byteSize() override {
-        return 0;
-    };
+    size_t byteSize() override;
 
     // TODO: Implement this later.
     virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
@@ -70,6 +75,8 @@
                                    bool condition, const LogEvent& event,
                                    bool scheduledPull) override;
 
+    void startNewProtoOutputStream(long long timestamp) override;
+
 private:
     // The default bucket size for gauge metric is 1 second.
     static const uint64_t kDefaultGaugemBucketSizeNs = 1000 * 1000 * 1000;
@@ -82,7 +89,8 @@
     Mutex mLock;
 
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
-    std::unordered_map<HashableDimensionKey, std::vector<GaugeBucketInfo>> mPastBuckets;
+    // TODO: Add a lock to mPastBuckets.
+    std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
 
     // The current bucket.
     std::unordered_map<HashableDimensionKey, long> mCurrentSlicedBucket;
@@ -90,6 +98,8 @@
     void flushGaugeIfNeededLocked(const uint64_t newEventTime);
 
     long getGauge(const LogEvent& event);
+
+    size_t mByteSize;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 535f4a2..5ca83b2 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -64,6 +64,23 @@
                               scheduledPull);
 }
 
+std::unique_ptr<uint8_t[]> MetricProducer::serializeProto() {
+    size_t bufferSize = mProto->size();
+
+    std::unique_ptr<uint8_t[]> buffer(new uint8_t[bufferSize]);
+
+    size_t pos = 0;
+    auto it = mProto->data();
+    while (it.readBuffer() != NULL) {
+        size_t toRead = it.currentToRead();
+        std::memcpy(&buffer[pos], it.readBuffer(), toRead);
+        pos += toRead;
+        it.rp()->move(toRead);
+    }
+
+    return buffer;
+}
+
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 6ba726f4..7ac97a8 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -59,6 +59,8 @@
     // coming. MetricProducer should do the clean up, and dump existing data to dropbox.
     virtual void finish() = 0;
 
+    // TODO: Pass a timestamp as a parameter in onDumpReport and update all its
+    // implementations.
     virtual StatsLogReport onDumpReport() = 0;
 
     virtual bool isConditionSliced() const {
@@ -109,6 +111,14 @@
             const size_t matcherIndex, const HashableDimensionKey& eventKey,
             const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
             const LogEvent& event, bool scheduledPull) = 0;
+
+    std::unique_ptr<android::util::ProtoOutputStream> mProto;
+
+    long long mProtoToken;
+
+    virtual void startNewProtoOutputStream(long long timestamp) = 0;
+
+    std::unique_ptr<uint8_t[]> serializeProto();
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 07a078f..41b7a5d 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -23,6 +23,12 @@
 #include <limits.h>
 #include <stdlib.h>
 
+using android::util::FIELD_TYPE_BOOL;
+using android::util::FIELD_TYPE_FLOAT;
+using android::util::FIELD_TYPE_INT32;
+using android::util::FIELD_TYPE_INT64;
+using android::util::FIELD_TYPE_MESSAGE;
+using android::util::ProtoOutputStream;
 using std::list;
 using std::make_shared;
 using std::map;
@@ -34,6 +40,27 @@
 namespace os {
 namespace statsd {
 
+// for StatsLogReport
+const int FIELD_ID_METRIC_ID = 1;
+const int FIELD_ID_START_REPORT_NANOS = 2;
+const int FIELD_ID_END_REPORT_NANOS = 3;
+const int FIELD_ID_VALUE_METRICS = 7;
+// for ValueMetricDataWrapper
+const int FIELD_ID_DATA = 1;
+// for ValueMetricData
+const int FIELD_ID_DIMENSION = 1;
+const int FIELD_ID_BUCKET_INFO = 2;
+// for KeyValuePair
+const int FIELD_ID_KEY = 1;
+const int FIELD_ID_VALUE_STR = 2;
+const int FIELD_ID_VALUE_INT = 3;
+const int FIELD_ID_VALUE_BOOL = 4;
+const int FIELD_ID_VALUE_FLOAT = 5;
+// for ValueBucketInfo
+const int FIELD_ID_START_BUCKET_NANOS = 1;
+const int FIELD_ID_END_BUCKET_NANOS = 2;
+const int FIELD_ID_VALUE = 3;
+
 // ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
 ValueMetricProducer::ValueMetricProducer(const ValueMetric& metric, const int conditionIndex,
                                          const sp<ConditionWizard>& wizard, const int pullTagId,
@@ -55,6 +82,8 @@
                                              metric.bucket().bucket_size_millis());
     }
 
+    startNewProtoOutputStream(mStartTimeNs);
+
     VLOG("value metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
          (long long)mBucketSizeNs, (long long)mStartTimeNs);
 }
@@ -63,25 +92,18 @@
     VLOG("~ValueMetricProducer() called");
 }
 
+void ValueMetricProducer::startNewProtoOutputStream(long long startTime) {
+    mProto = std::make_unique<ProtoOutputStream>();
+    mProto->write(FIELD_TYPE_INT32 | FIELD_ID_METRIC_ID, mMetric.metric_id());
+    mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, startTime);
+    mProtoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS);
+}
+
 void ValueMetricProducer::finish() {
     // TODO: write the StatsLogReport to dropbox using
     // DropboxWriter.
 }
 
-static void addSlicedCounterToReport(StatsLogReport_ValueMetricDataWrapper& wrapper,
-                                     const vector<KeyValuePair>& key,
-                                     const vector<ValueBucketInfo>& buckets) {
-    ValueMetricData* data = wrapper.add_data();
-    for (const auto& kv : key) {
-        data->add_dimension()->CopyFrom(kv);
-    }
-    for (const auto& bucket : buckets) {
-        data->add_bucket_info()->CopyFrom(bucket);
-        VLOG("\t bucket [%lld - %lld] value: %lld", bucket.start_bucket_nanos(),
-             bucket.end_bucket_nanos(), bucket.value());
-    }
-}
-
 void ValueMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
     VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
 }
@@ -89,26 +111,62 @@
 StatsLogReport ValueMetricProducer::onDumpReport() {
     VLOG("metric %lld dump report now...", mMetric.metric_id());
 
-    StatsLogReport report;
-    report.set_metric_id(mMetric.metric_id());
-    report.set_start_report_nanos(mStartTimeNs);
-    report.set_end_report_nanos(mCurrentBucketStartTimeNs);
-
-    StatsLogReport_ValueMetricDataWrapper* wrapper = report.mutable_value_metrics();
-
     for (const auto& pair : mPastBuckets) {
         const HashableDimensionKey& hashableKey = pair.first;
+        VLOG("  dimension key %s", hashableKey.c_str());
         auto it = mDimensionKeyMap.find(hashableKey);
         if (it == mDimensionKeyMap.end()) {
             ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
             continue;
         }
+        long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
 
-        VLOG("  dimension key %s", hashableKey.c_str());
-        addSlicedCounterToReport(*wrapper, it->second, pair.second);
+        // First fill dimension (KeyValuePairs).
+        for (const auto& kv : it->second) {
+            long long dimensionToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION);
+            mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
+            if (kv.has_value_str()) {
+                mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+            } else if (kv.has_value_int()) {
+                mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
+            } else if (kv.has_value_bool()) {
+                mProto->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool());
+            } else if (kv.has_value_float()) {
+                mProto->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float());
+            }
+            mProto->end(dimensionToken);
+        }
+
+        // Then fill bucket_info (ValueBucketInfo).
+        for (const auto& bucket : pair.second) {
+            long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+            mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
+                          (long long)bucket.mBucketStartNs);
+            mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
+                          (long long)bucket.mBucketEndNs);
+            mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE, (long long)bucket.mValue);
+            mProto->end(bucketInfoToken);
+            VLOG("\t bucket [%lld - %lld] count: %lld", (long long)bucket.mBucketStartNs,
+                 (long long)bucket.mBucketEndNs, (long long)bucket.mValue);
+        }
+        mProto->end(wrapperToken);
     }
-    return report;
-    // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
+    mProto->end(mProtoToken);
+    mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
+                  (long long)mCurrentBucketStartTimeNs);
+
+    VLOG("metric %lld dump report now...", mMetric.metric_id());
+    std::unique_ptr<uint8_t[]> buffer = serializeProto();
+
+    startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
+    mPastBuckets.clear();
+    mByteSize = 0;
+
+    // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
+    // return std::move(buffer);
+    return StatsLogReport();
+
+    // TODO: Clear mDimensionKeyMap once the report is dumped.
 }
 
 void ValueMetricProducer::onConditionChanged(const bool condition, const uint64_t eventTime) {
@@ -207,20 +265,21 @@
 
     VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs,
          (int)mCurrentSlicedBucket.size());
-    ValueBucketInfo info;
-    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
-    info.set_end_bucket_nanos(mCurrentBucketStartTimeNs + mBucketSizeNs);
+    ValueBucket info;
+    info.mBucketStartNs = mCurrentBucketStartTimeNs;
+    info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
 
     for (const auto& slice : mCurrentSlicedBucket) {
         long value = 0;
         for (const auto& pair : slice.second.raw) {
             value += pair.second - pair.first;
         }
-        info.set_value(value);
+        info.mValue = value;
         VLOG(" %s, %ld", slice.first.c_str(), value);
         // it will auto create new vector of ValuebucketInfo if the key is not found.
         auto& bucketList = mPastBuckets[slice.first];
         bucketList.push_back(info);
+        mByteSize += sizeof(info);
     }
 
     // Reset counters
@@ -235,6 +294,10 @@
          (long long)mCurrentBucketStartTimeNs);
 }
 
+size_t ValueMetricProducer::byteSize() {
+    return mByteSize;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 548cd44..8437665 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -30,6 +30,12 @@
 namespace os {
 namespace statsd {
 
+struct ValueBucket {
+    int64_t mBucketStartNs;
+    int64_t mBucketEndNs;
+    int64_t mValue;
+};
+
 class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
 public:
     ValueMetricProducer(const ValueMetric& valueMetric, const int conditionIndex,
@@ -42,15 +48,14 @@
 
     void finish() override;
 
+    // TODO: Pass a timestamp as a parameter in onDumpReport.
     StatsLogReport onDumpReport() override;
 
     void onSlicedConditionMayChange(const uint64_t eventTime);
 
     void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
-    // TODO: Implement this later.
-    size_t byteSize() override {
-        return 0;
-    };
+
+    size_t byteSize() override;
 
     // TODO: Implement this later.
     virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
@@ -63,6 +68,8 @@
                                    bool condition, const LogEvent& event,
                                    bool scheduledPull) override;
 
+    void startNewProtoOutputStream(long long timestamp) override;
+
 private:
     const ValueMetric mMetric;
 
@@ -84,11 +91,14 @@
     std::unordered_map<HashableDimensionKey, Interval> mNextSlicedBucket;
 
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
-    std::unordered_map<HashableDimensionKey, std::vector<ValueBucketInfo>> mPastBuckets;
+    // TODO: Add a lock to mPastBuckets.
+    std::unordered_map<HashableDimensionKey, std::vector<ValueBucket>> mPastBuckets;
 
     long get_value(const LogEvent& event);
 
     void flush_if_needed(const uint64_t eventTimeNs);
+
+    size_t mByteSize;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 0d0d9a4..aeb234d 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -47,10 +47,16 @@
     DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
 };
 
+struct DurationBucket {
+    int64_t mBucketStartNs;
+    int64_t mBucketEndNs;
+    int64_t mDuration;
+};
+
 class DurationTracker {
 public:
     DurationTracker(sp<ConditionWizard> wizard, int conditionIndex, uint64_t currentBucketStartNs,
-                    uint64_t bucketSizeNs, std::vector<DurationBucketInfo>& bucket)
+                    uint64_t bucketSizeNs, std::vector<DurationBucket>& bucket)
         : mWizard(wizard),
           mConditionTrackerIndex(conditionIndex),
           mCurrentBucketStartTimeNs(currentBucketStartNs),
@@ -77,7 +83,7 @@
 
     int64_t mBucketSizeNs;
 
-    std::vector<DurationBucketInfo>& mBucket;  // where to write output
+    std::vector<DurationBucket>& mBucket;  // where to write output
 
     int64_t mDuration;  // current recorded duration result
 };
@@ -86,4 +92,4 @@
 }  // namespace os
 }  // namespace android
 
-#endif  // DURATION_TRACKER_H
\ No newline at end of file
+#endif  // DURATION_TRACKER_H
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 856ca9d..a4d3098 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -25,7 +25,7 @@
 
 MaxDurationTracker::MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
                                        uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
-                                       std::vector<DurationBucketInfo>& bucket)
+                                       std::vector<DurationBucket>& bucket)
     : DurationTracker(wizard, conditionIndex, currentBucketStartNs, bucketSizeNs, bucket) {
 }
 
@@ -106,10 +106,12 @@
     // adjust the bucket start time
     int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
 
-    DurationBucketInfo info;
     uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
-    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
-    info.set_end_bucket_nanos(endTime);
+
+    DurationBucket info;
+    info.mBucketStartNs = mCurrentBucketStartTimeNs;
+    info.mBucketEndNs = endTime;
+
 
     uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
     mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
@@ -150,7 +152,7 @@
     }
 
     if (mDuration != 0) {
-        info.set_duration_nanos(mDuration);
+        info.mDuration = mDuration;
         mBucket.push_back(info);
         VLOG("  final duration for last bucket: %lld", (long long)mDuration);
     }
@@ -158,10 +160,10 @@
     mDuration = 0;
     if (hasOnGoingStartedEvent) {
         for (int i = 1; i < numBucketsForward; i++) {
-            DurationBucketInfo info;
-            info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
-            info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
-            info.set_duration_nanos(mBucketSizeNs);
+            DurationBucket info;
+            info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
+            info.mBucketEndNs = endTime + mBucketSizeNs * i;
+            info.mDuration = mBucketSizeNs;
             mBucket.push_back(info);
             VLOG("  filling gap bucket with duration %lld", (long long)mBucketSizeNs);
         }
@@ -224,4 +226,4 @@
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index c74d070..de53d62 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -32,7 +32,7 @@
 public:
     MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
                        uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
-                       std::vector<DurationBucketInfo>& bucket);
+                       std::vector<DurationBucket>& bucket);
     void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
                    const ConditionKey& conditionKey) override;
     void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) override;
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index e045fb4..e4f1d21 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -22,7 +22,7 @@
 namespace statsd {
 OringDurationTracker::OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
                                            uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
-                                           std::vector<DurationBucketInfo>& bucket)
+                                           std::vector<DurationBucket>& bucket)
     : DurationTracker(wizard, conditionIndex, currentBucketStartNs, bucketSizeNs, bucket),
       mStarted(),
       mPaused() {
@@ -82,10 +82,10 @@
     VLOG("OringDurationTracker Flushing.............");
     // adjust the bucket start time
     int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-    DurationBucketInfo info;
+    DurationBucket info;
     uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
-    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
-    info.set_end_bucket_nanos(endTime);
+    info.mBucketStartNs = mCurrentBucketStartTimeNs;
+    info.mBucketEndNs = endTime;
 
     uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
     mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
@@ -94,7 +94,7 @@
         mDuration += (endTime - mLastStartTime);
     }
     if (mDuration != 0) {
-        info.set_duration_nanos(mDuration);
+        info.mDuration = mDuration;
         // it will auto create new vector of CountbucketInfo if the key is not found.
         mBucket.push_back(info);
         VLOG("  duration: %lld", (long long)mDuration);
@@ -102,10 +102,10 @@
 
     if (mStarted.size() > 0) {
         for (int i = 1; i < numBucketsForward; i++) {
-            DurationBucketInfo info;
-            info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
-            info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
-            info.set_duration_nanos(mBucketSizeNs);
+            DurationBucket info;
+            info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
+            info.mBucketEndNs = endTime + mBucketSizeNs * i;
+            info.mDuration = mBucketSizeNs;
             mBucket.push_back(info);
             VLOG("  add filling bucket with duration %lld", (long long)mBucketSizeNs);
         }
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index 5425251..b54dafa 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -29,7 +29,7 @@
 public:
     OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
                          uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
-                         std::vector<DurationBucketInfo>& bucket);
+                         std::vector<DurationBucket>& bucket);
     void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
                    const ConditionKey& conditionKey) override;
     void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) override;
@@ -54,4 +54,4 @@
 }  // namespace os
 }  // namespace android
 
-#endif  // ORING_DURATION_TRACKER_H
\ No newline at end of file
+#endif  // ORING_DURATION_TRACKER_H
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index f2abe7b..58bf1b3 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -39,7 +39,7 @@
 TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    vector<DurationBucketInfo> buckets;
+    vector<DurationBucket> buckets;
     ConditionKey key1;
 
     uint64_t bucketStartTimeNs = 10000000000;
@@ -55,13 +55,13 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(20, buckets[0].duration_nanos());
+    EXPECT_EQ(20, buckets[0].mDuration);
 }
 
 TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    vector<DurationBucketInfo> buckets;
+    vector<DurationBucket> buckets;
     ConditionKey key1;
 
     uint64_t bucketStartTimeNs = 10000000000;
@@ -73,8 +73,8 @@
     tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
 
     EXPECT_EQ(2u, buckets.size());
-    EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].duration_nanos());
-    EXPECT_EQ((long long)bucketSizeNs, buckets[1].duration_nanos());
+    EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].mDuration);
+    EXPECT_EQ((long long)bucketSizeNs, buckets[1].mDuration);
 }
 
 TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
@@ -86,7 +86,7 @@
     EXPECT_CALL(*wizard, query(_, key1))  // #4
             .WillOnce(Return(ConditionState::kFalse));
 
-    vector<DurationBucketInfo> buckets;
+    vector<DurationBucket> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
@@ -103,7 +103,7 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(5, buckets[0].duration_nanos());
+    EXPECT_EQ(5, buckets[0].mDuration);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 338d55d..74a6f11 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -40,7 +40,7 @@
     ConditionKey key1;
     key1["APP_BACKGROUND"] = "1:maps|";
 
-    vector<DurationBucketInfo> buckets;
+    vector<DurationBucket> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
@@ -56,7 +56,7 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(durationTimeNs, buckets[0].duration_nanos());
+    EXPECT_EQ(durationTimeNs, buckets[0].mDuration);
 }
 
 TEST(OringDurationTrackerTest, TestDurationConditionChange) {
@@ -68,7 +68,7 @@
     EXPECT_CALL(*wizard, query(_, key1))  // #4
             .WillOnce(Return(ConditionState::kFalse));
 
-    vector<DurationBucketInfo> buckets;
+    vector<DurationBucket> buckets;
 
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
@@ -85,7 +85,7 @@
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(5, buckets[0].duration_nanos());
+    EXPECT_EQ(5, buckets[0].mDuration);
 }
 }  // namespace statsd
 }  // namespace os
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8226e0f..d5d95fb 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -22,6 +22,7 @@
 import android.annotation.DrawableRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -3900,7 +3901,7 @@
             final Bundle ex = mN.extras;
             updateBackgroundColor(contentView);
             bindNotificationHeader(contentView, p.ambient);
-            bindLargeIcon(contentView);
+            bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply);
             boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
             if (p.title != null) {
                 contentView.setViewVisibility(R.id.title, View.VISIBLE);
@@ -4110,11 +4111,13 @@
             }
         }
 
-        private void bindLargeIcon(RemoteViews contentView) {
+        private void bindLargeIcon(RemoteViews contentView, boolean hideLargeIcon,
+                boolean alwaysShowReply) {
             if (mN.mLargeIcon == null && mN.largeIcon != null) {
                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
             }
-            if (mN.mLargeIcon != null) {
+            boolean showLargeIcon = mN.mLargeIcon != null && !hideLargeIcon;
+            if (showLargeIcon) {
                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
                 contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
                 processLargeLegacyIcon(mN.mLargeIcon, contentView);
@@ -4122,32 +4125,45 @@
                 contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin);
                 contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin);
                 contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin);
-                // Bind the reply action
-                Action action = findReplyAction();
-                contentView.setViewVisibility(R.id.reply_icon_action, action != null
-                        ? View.VISIBLE
-                        : View.GONE);
+            }
+            // Bind the reply action
+            Action action = findReplyAction();
 
-                if (action != null) {
-                    int contrastColor = resolveContrastColor();
+            boolean actionVisible = action != null && (showLargeIcon || alwaysShowReply);
+            int replyId = showLargeIcon ? R.id.reply_icon_action : R.id.right_icon;
+            if (actionVisible) {
+                // We're only showing the icon as big if we're hiding the large icon
+                int contrastColor = resolveContrastColor();
+                int iconColor;
+                if (showLargeIcon) {
                     contentView.setDrawableTint(R.id.reply_icon_action,
                             true /* targetBackground */,
                             contrastColor, PorterDuff.Mode.SRC_ATOP);
-                    int iconColor = NotificationColorUtil.isColorLight(contrastColor)
-                            ? Color.BLACK : Color.WHITE;
-                    contentView.setDrawableTint(R.id.reply_icon_action,
-                            false /* targetBackground */,
-                            iconColor, PorterDuff.Mode.SRC_ATOP);
                     contentView.setOnClickPendingIntent(R.id.right_icon,
                             action.actionIntent);
-                    contentView.setOnClickPendingIntent(R.id.reply_icon_action,
-                            action.actionIntent);
                     contentView.setRemoteInputs(R.id.right_icon, action.mRemoteInputs);
-                    contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
-
+                    iconColor = NotificationColorUtil.isColorLight(contrastColor)
+                            ? Color.BLACK : Color.WHITE;
+                } else {
+                    contentView.setImageViewResource(R.id.right_icon,
+                            R.drawable.ic_reply_notification_large);
+                    contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
+                    iconColor = contrastColor;
                 }
+                contentView.setDrawableTint(replyId,
+                        false /* targetBackground */,
+                        iconColor,
+                        PorterDuff.Mode.SRC_ATOP);
+                contentView.setOnClickPendingIntent(replyId,
+                        action.actionIntent);
+                contentView.setRemoteInputs(replyId, action.mRemoteInputs);
+            } else {
+                contentView.setRemoteInputs(R.id.right_icon, null);
             }
-            contentView.setViewVisibility(R.id.right_icon_container, mN.mLargeIcon != null
+            contentView.setViewVisibility(R.id.reply_icon_action, actionVisible && showLargeIcon
+                    ? View.VISIBLE
+                    : View.GONE);
+            contentView.setViewVisibility(R.id.right_icon_container, actionVisible || showLargeIcon
                     ? View.VISIBLE
                     : View.GONE);
         }
@@ -6055,18 +6071,12 @@
         protected void restoreFromExtras(Bundle extras) {
             super.restoreFromExtras(extras);
 
-            mMessages.clear();
-            mHistoricMessages.clear();
             mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
             Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
-            if (messages != null && messages instanceof Parcelable[]) {
-                mMessages = Message.getMessagesFromBundleArray(messages);
-            }
+            mMessages = Message.getMessagesFromBundleArray(messages);
             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
-            if (histMessages != null && histMessages instanceof Parcelable[]) {
-                mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
-            }
+            mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
         }
 
         /**
@@ -6074,38 +6084,34 @@
          */
         @Override
         public RemoteViews makeContentView(boolean increasedHeight) {
-            if (!increasedHeight) {
-                Message m = findLatestIncomingMessage();
-                CharSequence title = mConversationTitle != null
-                        ? mConversationTitle
-                        : (m == null) ? null : m.mSender;
-                CharSequence text = (m == null)
-                        ? null
-                        : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
-                return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(),
-                        mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
-            } else {
-                mBuilder.mOriginalActions = mBuilder.mActions;
-                mBuilder.mActions = new ArrayList<>();
-                RemoteViews remoteViews = makeBigContentView();
-                mBuilder.mActions = mBuilder.mOriginalActions;
-                mBuilder.mOriginalActions = null;
-                return remoteViews;
-            }
+            mBuilder.mOriginalActions = mBuilder.mActions;
+            mBuilder.mActions = new ArrayList<>();
+            RemoteViews remoteViews = makeBigContentView();
+            mBuilder.mActions = mBuilder.mOriginalActions;
+            mBuilder.mOriginalActions = null;
+            return remoteViews;
         }
 
         private Message findLatestIncomingMessage() {
-            for (int i = mMessages.size() - 1; i >= 0; i--) {
-                Message m = mMessages.get(i);
+            return findLatestIncomingMessage(mMessages);
+        }
+
+        /**
+         * @hide
+         */
+        @Nullable
+        public static Message findLatestIncomingMessage(
+                List<Message> messages) {
+            for (int i = messages.size() - 1; i >= 0; i--) {
+                Message m = messages.get(i);
                 // Incoming messages have a non-empty sender.
                 if (!TextUtils.isEmpty(m.mSender)) {
                     return m;
                 }
             }
-            if (!mMessages.isEmpty()) {
+            if (!messages.isEmpty()) {
                 // No incoming messages, fall back to outgoing message
-                return mMessages.get(mMessages.size() - 1);
+                return messages.get(messages.size() - 1);
             }
             return null;
         }
@@ -6115,118 +6121,82 @@
          */
         @Override
         public RemoteViews makeBigContentView() {
-            CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle)
+            CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
                     ? super.mBigContentTitle
                     : mConversationTitle;
-            boolean hasTitle = !TextUtils.isEmpty(title);
-
-            if (mMessages.size() == 1) {
-                // Special case for a single message: Use the big text style
-                // so the collapsed and expanded versions match nicely.
-                CharSequence bigTitle;
-                CharSequence text;
-                if (hasTitle) {
-                    bigTitle = title;
-                    text = makeMessageLine(mMessages.get(0), mBuilder);
-                } else {
-                    bigTitle = mMessages.get(0).mSender;
-                    text = mMessages.get(0).mText;
-                }
-                RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
-                        mBuilder.getBigTextLayoutResource(),
-                        mBuilder.mParams.reset().hasProgress(false).title(bigTitle).text(null));
-                BigTextStyle.applyBigTextContentView(mBuilder, contentView, text);
-                return contentView;
+            boolean isOneToOne = TextUtils.isEmpty(conversationTitle);
+            if (isOneToOne) {
+                // Let's add the conversationTitle in case we didn't have one before and all
+                // messages are from the same sender
+                conversationTitle = createConversationTitleFromMessages();
+            } else if (hasOnlyWhiteSpaceSenders()) {
+                isOneToOne = true;
             }
-
+            boolean hasTitle = !TextUtils.isEmpty(conversationTitle);
             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
                     mBuilder.getMessagingLayoutResource(),
-                    mBuilder.mParams.reset().hasProgress(false).title(title).text(null));
-
-            int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
-                    R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
-
-            // Make sure all rows are gone in case we reuse a view.
-            for (int rowId : rowIds) {
-                contentView.setViewVisibility(rowId, View.GONE);
-            }
-
-            int i=0;
-            contentView.setViewLayoutMarginBottomDimen(R.id.line1,
-                    hasTitle ? R.dimen.notification_messaging_spacing : 0);
-            contentView.setInt(R.id.notification_messaging, "setNumIndentLines",
-                    !mBuilder.mN.hasLargeIcon() ? 0 : (hasTitle ? 1 : 2));
-
-            int contractedChildId = View.NO_ID;
-            Message contractedMessage = findLatestIncomingMessage();
-            int firstHistoricMessage = Math.max(0, mHistoricMessages.size()
-                    - (rowIds.length - mMessages.size()));
-            while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) {
-                Message m = mHistoricMessages.get(firstHistoricMessage + i);
-                int rowId = rowIds[i];
-
-                contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
-
-                if (contractedMessage == m) {
-                    contractedChildId = rowId;
-                }
-
-                i++;
-            }
-
-            int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
-            while (firstMessage + i < mMessages.size() && i < rowIds.length) {
-                Message m = mMessages.get(firstMessage + i);
-                int rowId = rowIds[i];
-
-                contentView.setViewVisibility(rowId, View.VISIBLE);
-                contentView.setTextViewText(rowId, mBuilder.processTextSpans(
-                        makeMessageLine(m, mBuilder)));
-                mBuilder.setTextViewColorSecondary(contentView, rowId);
-
-                if (contractedMessage == m) {
-                    contractedChildId = rowId;
-                }
-
-                i++;
-            }
-            // Clear the remaining views for reapply. Ensures that historic message views can
-            // reliably be identified as being GONE and having non-null text.
-            while (i < rowIds.length) {
-                int rowId = rowIds[i];
-                contentView.setTextViewText(rowId, null);
-                i++;
-            }
-
-            // Record this here to allow transformation between the contracted and expanded views.
-            contentView.setInt(R.id.notification_messaging, "setContractedChildId",
-                    contractedChildId);
+                    mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
+                            .hideLargeIcon(isOneToOne).alwaysShowReply(true));
+            addExtras(mBuilder.mN.extras);
+            contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+                    mBuilder.resolveContrastColor());
+            contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
+                    mBuilder.mN.mLargeIcon);
+            contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
+                    isOneToOne);
+            contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
+                    mBuilder.mN.extras);
             return contentView;
         }
 
-        private CharSequence makeMessageLine(Message m, Builder builder) {
-            BidiFormatter bidi = BidiFormatter.getInstance();
-            SpannableStringBuilder sb = new SpannableStringBuilder();
-            boolean colorize = builder.isColorized();
-            TextAppearanceSpan colorSpan;
-            CharSequence messageName;
-            if (TextUtils.isEmpty(m.mSender)) {
-                CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
-                sb.append(bidi.unicodeWrap(replyName),
-                        makeFontColorSpan(colorize
-                                ? builder.getPrimaryTextColor()
-                                : mBuilder.resolveContrastColor()),
-                        0 /* flags */);
-            } else {
-                sb.append(bidi.unicodeWrap(m.mSender),
-                        makeFontColorSpan(colorize
-                                ? builder.getPrimaryTextColor()
-                                : Color.BLACK),
-                        0 /* flags */);
+        private boolean hasOnlyWhiteSpaceSenders() {
+            for (int i = 0; i < mMessages.size(); i++) {
+                Message m = mMessages.get(i);
+                CharSequence sender = m.getSender();
+                if (!isWhiteSpace(sender)) {
+                    return false;
+                }
             }
-            CharSequence text = m.mText == null ? "" : m.mText;
-            sb.append("  ").append(bidi.unicodeWrap(text));
-            return sb;
+            return true;
+        }
+
+        private boolean isWhiteSpace(CharSequence sender) {
+            if (TextUtils.isEmpty(sender)) {
+                return true;
+            }
+            if (sender.toString().matches("^\\s*$")) {
+                return true;
+            }
+            // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
+            // For the presentation that we had.
+            for (int i = 0; i < sender.length(); i++) {
+                char c = sender.charAt(i);
+                if (c != '\u200B') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private CharSequence createConversationTitleFromMessages() {
+            ArraySet<CharSequence> names = new ArraySet<>();
+            for (int i = 0; i < mMessages.size(); i++) {
+                Message m = mMessages.get(i);
+                CharSequence sender = m.getSender();
+                if (sender != null) {
+                    names.add(sender);
+                }
+            }
+            SpannableStringBuilder title = new SpannableStringBuilder();
+            int size = names.size();
+            for (int i = 0; i < size; i++) {
+                CharSequence name = names.valueAt(i);
+                if (!TextUtils.isEmpty(title)) {
+                    title.append(", ");
+                }
+                title.append(BidiFormatter.getInstance().unicodeWrap(name));
+            }
+            return title;
         }
 
         /**
@@ -6234,19 +6204,9 @@
          */
         @Override
         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
-            if (increasedHeight) {
-                return makeBigContentView();
-            }
-            Message m = findLatestIncomingMessage();
-            CharSequence title = mConversationTitle != null
-                    ? mConversationTitle
-                    : (m == null) ? null : m.mSender;
-            CharSequence text = (m == null)
-                    ? null
-                    : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
-            return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(),
-                    mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
+            RemoteViews remoteViews = makeBigContentView();
+            remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
+            return remoteViews;
         }
 
         private static TextAppearanceSpan makeFontColorSpan(int color) {
@@ -6394,7 +6354,15 @@
                 return bundles;
             }
 
-            static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+            /**
+             * @return A list of messages read from the bundles.
+             *
+             * @hide
+             */
+            public static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+                if (bundles == null) {
+                    return new ArrayList<>();
+                }
                 List<Message> messages = new ArrayList<>(bundles.length);
                 for (int i = 0; i < bundles.length; i++) {
                     if (bundles[i] instanceof Bundle) {
@@ -8487,6 +8455,8 @@
         boolean ambient = false;
         CharSequence title;
         CharSequence text;
+        boolean hideLargeIcon;
+        public boolean alwaysShowReply;
 
         final StandardTemplateParams reset() {
             hasProgress = true;
@@ -8511,6 +8481,16 @@
             return this;
         }
 
+        final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) {
+            this.alwaysShowReply = alwaysShowReply;
+            return this;
+        }
+
+        final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
+            this.hideLargeIcon = hideLargeIcon;
+            return this;
+        }
+
         final StandardTemplateParams ambient(boolean ambient) {
             Preconditions.checkState(title == null && text == null, "must set ambient before text");
             this.ambient = ambient;
@@ -8527,7 +8507,6 @@
                 text = extras.getCharSequence(EXTRA_TEXT);
             }
             this.text = b.processLegacyText(text, ambient);
-
             return this;
         }
     }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 772c6d6..a2e5cc9 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3246,6 +3246,7 @@
      *             that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
      */
     public void wipeData(int flags) {
+        throwIfParentInstance("wipeData");
         final String wipeReasonForUser = mContext.getString(
                 R.string.work_profile_deleted_description_dpm_wipe);
         wipeDataInternal(flags, wipeReasonForUser);
@@ -3270,6 +3271,7 @@
      * @throws IllegalArgumentException if the input reason string is null or empty.
      */
     public void wipeDataWithReason(int flags, @NonNull CharSequence reason) {
+        throwIfParentInstance("wipeDataWithReason");
         Preconditions.checkNotNull(reason, "CharSequence is null");
         wipeDataInternal(flags, reason.toString());
     }
@@ -3283,7 +3285,6 @@
      * @hide
      */
     private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
-        throwIfParentInstance("wipeDataWithReason");
         if (mService != null) {
             try {
                 mService.wipeDataWithReason(flags, wipeReasonForUser);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index f4fdcaa..5673361 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1184,10 +1184,10 @@
         }
 
         /**
-         * Sets the UID that initiated package installation. This is informational
+         * Sets the UID that initiated the package installation. This is informational
          * and may be used as a signal for anti-malware purposes.
          *
-         * @see PackageManager#EXTRA_VERIFICATION_INSTALLER_UID
+         * @see Intent#EXTRA_ORIGINATING_UID
          */
         public void setOriginatingUid(int originatingUid) {
             this.originatingUid = originatingUid;
diff --git a/core/java/android/net/ConnectivityMetricsEvent.java b/core/java/android/net/ConnectivityMetricsEvent.java
index 46bb346..394ac42 100644
--- a/core/java/android/net/ConnectivityMetricsEvent.java
+++ b/core/java/android/net/ConnectivityMetricsEvent.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+
 import com.android.internal.util.BitUtils;
 
 /**
@@ -80,7 +81,7 @@
         StringBuilder buffer = new StringBuilder("ConnectivityMetricsEvent(");
         buffer.append(String.format("%tT.%tL", timestamp, timestamp));
         if (netId != 0) {
-            buffer.append(", ").append(netId);
+            buffer.append(", ").append("netId=").append(netId);
         }
         if (ifname != null) {
             buffer.append(", ").append(ifname);
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
new file mode 100644
index 0000000..e76d17d
--- /dev/null
+++ b/core/java/android/net/MacAddress.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public final class MacAddress {
+
+    // TODO: add isLocallyAssigned().
+    // TODO: add getRandomAddress() factory method.
+
+    private static final int ETHER_ADDR_LEN = 6;
+    private static final byte FF = (byte) 0xff;
+    @VisibleForTesting
+    static final byte[] ETHER_ADDR_BROADCAST = { FF, FF, FF, FF, FF, FF };
+
+    public enum MacAddressType {
+        UNICAST,
+        MULTICAST,
+        BROADCAST;
+    }
+
+    /** Return true if the given byte array is not null and has the length of a mac address. */
+    public static boolean isMacAddress(byte[] addr) {
+        return addr != null && addr.length == ETHER_ADDR_LEN;
+    }
+
+    /**
+     * Return the MacAddressType of the mac address represented by the given byte array,
+     * or null if the given byte array does not represent an mac address. */
+    public static MacAddressType macAddressType(byte[] addr) {
+        if (!isMacAddress(addr)) {
+            return null;
+        }
+        if (Arrays.equals(addr, ETHER_ADDR_BROADCAST)) {
+            return MacAddressType.BROADCAST;
+        }
+        if ((addr[0] & 0x01) == 1) {
+            return MacAddressType.MULTICAST;
+        }
+        return MacAddressType.UNICAST;
+    }
+}
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index d5377c7..9edcc0e 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -1066,7 +1066,7 @@
                 return null;
             }
 
-            int end = authority.indexOf('@');
+            int end = authority.lastIndexOf('@');
             return end == NOT_FOUND ? null : authority.substring(0, end);
         }
 
@@ -1090,7 +1090,7 @@
             }
 
             // Parse out user info and then port.
-            int userInfoSeparator = authority.indexOf('@');
+            int userInfoSeparator = authority.lastIndexOf('@');
             int portSeparator = authority.indexOf(':', userInfoSeparator);
 
             String encodedHost = portSeparator == NOT_FOUND
@@ -1116,7 +1116,7 @@
 
             // Make sure we look for the port separtor *after* the user info
             // separator. We have URLs with a ':' in the user info.
-            int userInfoSeparator = authority.indexOf('@');
+            int userInfoSeparator = authority.lastIndexOf('@');
             int portSeparator = authority.indexOf(':', userInfoSeparator);
 
             if (portSeparator == NOT_FOUND) {
diff --git a/core/java/android/net/metrics/ConnectStats.java b/core/java/android/net/metrics/ConnectStats.java
index 2495cab..b320b75 100644
--- a/core/java/android/net/metrics/ConnectStats.java
+++ b/core/java/android/net/metrics/ConnectStats.java
@@ -119,7 +119,8 @@
 
     @Override
     public String toString() {
-        StringBuilder builder = new StringBuilder("ConnectStats(").append(netId).append(", ");
+        StringBuilder builder =
+                new StringBuilder("ConnectStats(").append("netId=").append(netId).append(", ");
         for (int t : BitUtils.unpackBits(transports)) {
             builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
         }
diff --git a/core/java/android/net/metrics/DnsEvent.java b/core/java/android/net/metrics/DnsEvent.java
index 81b098b..5aa705b 100644
--- a/core/java/android/net/metrics/DnsEvent.java
+++ b/core/java/android/net/metrics/DnsEvent.java
@@ -85,7 +85,8 @@
 
     @Override
     public String toString() {
-        StringBuilder builder = new StringBuilder("DnsEvent(").append(netId).append(", ");
+        StringBuilder builder =
+                new StringBuilder("DnsEvent(").append("netId=").append(netId).append(", ");
         for (int t : BitUtils.unpackBits(transports)) {
             builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
         }
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index c2cf3967..10adb5a 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -2020,8 +2020,6 @@
     @Deprecated
     static native void closeFileDescriptor(FileDescriptor desc) throws IOException;
 
-    static native void clearFileDescriptor(FileDescriptor desc);
-
     /**
      * Read a byte value from the parcel at the current dataPosition().
      */
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 7f588ad..7556f09 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -683,7 +683,7 @@
                 throw new IllegalStateException("Already closed");
             }
             final int fd = getFd();
-            Parcel.clearFileDescriptor(mFd);
+            mFd.setInt$(-1);
             writeCommStatusAndClose(Status.DETACHED, null);
             mClosed = true;
             mGuard.close();
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index ee0b623..c6149be 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -67,6 +67,7 @@
         public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10;
         public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11;
         public static final int DOWNLOAD_PAYLOAD_VERIFICATION_ERROR = 12;
+        public static final int UPDATED_BUT_NOT_ACTIVE = 52;
     }
 
     /**
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index e865ed1..0b76eec 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -17,6 +17,7 @@
 package android.os.storage;
 
 import android.content.pm.IPackageMoveObserver;
+import android.os.IVoldTaskListener;
 import android.os.ParcelFileDescriptor;
 import android.os.storage.DiskInfo;
 import android.os.storage.IStorageEventListener;
@@ -165,7 +166,7 @@
     void forgetAllVolumes() = 56;
     String getPrimaryStorageUuid() = 57;
     void setPrimaryStorageUuid(in String volumeUuid, IPackageMoveObserver callback) = 58;
-    long benchmark(in String volId) = 59;
+    void benchmark(in String volId, IVoldTaskListener listener) = 59;
     void setDebugFlags(int flags, int mask) = 60;
     void createUserKey(int userId, int serialNumber, boolean ephemeral) = 61;
     void destroyUserKey(int userId) = 62;
@@ -177,7 +178,7 @@
     boolean isConvertibleToFBE() = 68;
     void addUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 70;
     void fixateNewestUserKeyAuth(int userId) = 71;
-    void fstrim(int flags) = 72;
+    void fstrim(int flags, IVoldTaskListener listener) = 72;
     AppFuseMount mountProxyFileDescriptorBridge() = 73;
     ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74;
     long getCacheQuotaBytes(String volumeUuid, int uid) = 75;
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 6594cd0..0b007dd 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -42,10 +42,12 @@
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IVold;
+import android.os.IVoldTaskListener;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelableException;
+import android.os.PersistableBundle;
 import android.os.ProxyFileDescriptorCallback;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -87,7 +89,9 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -884,9 +888,32 @@
     }
 
     /** {@hide} */
+    @Deprecated
     public long benchmark(String volId) {
+        final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+        benchmark(volId, new IVoldTaskListener.Stub() {
+            @Override
+            public void onStatus(int status, PersistableBundle extras) {
+                // Ignored
+            }
+
+            @Override
+            public void onFinished(int status, PersistableBundle extras) {
+                result.complete(extras);
+            }
+        });
         try {
-            return mStorageManager.benchmark(volId);
+            // Convert ms to ns
+            return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE) * 1000000;
+        } catch (Exception e) {
+            return Long.MAX_VALUE;
+        }
+    }
+
+    /** {@hide} */
+    public void benchmark(String volId, IVoldTaskListener listener) {
+        try {
+            mStorageManager.benchmark(volId, listener);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2501f22..0a20c43 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -211,8 +211,13 @@
 
     /** @hide */
     public static final String EXTRA_NETWORK_TEMPLATE = "network_template";
-    /** @hide */
-    public static final String EXTRA_SUB_ID = "sub_id";
+
+    /**
+     * An int extra specifying a subscription ID.
+     *
+     * @see android.telephony.SubscriptionInfo#getSubscriptionId
+     */
+    public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
 
     /**
      * Activity Action: Modify Airplane mode settings using a voice command.
@@ -916,6 +921,9 @@
      * In some cases, a matching Activity may not exist, so ensure you
      * safeguard against this.
      * <p>
+     * The subscription ID of the subscription for which available network operators should be
+     * displayed may be optionally specified with {@link #EXTRA_SUB_ID}.
+     * <p>
      * Input: Nothing.
      * <p>
      * Output: Nothing.
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 07455c1..e35bb92 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -154,6 +154,21 @@
     }
 
     /**
+     * Returns a {@link TextLinks} that may be applied to the text to annotate it with links
+     * information.
+     *
+     * @param text the text to generate annotations for
+     * @param options configuration for link generation. If null, defaults will be used.
+     *
+     * @throws IllegalArgumentException if text is null
+     */
+    @WorkerThread
+    default TextLinks generateLinks(
+            @NonNull CharSequence text, @Nullable TextLinks.Options options) {
+        return new TextLinks.Builder(text.toString()).build();
+    }
+
+    /**
      * Logs a TextClassifier event.
      *
      * @param source the text classifier used to generate this event
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 2799f2b..9c4fd57 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -209,6 +209,32 @@
     }
 
     @Override
+    public TextLinks generateLinks(
+            @NonNull CharSequence text, @Nullable TextLinks.Options options) {
+        Preconditions.checkNotNull(text);
+        final String textString = text.toString();
+        final TextLinks.Builder builder = new TextLinks.Builder(textString);
+        try {
+            LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
+            final SmartSelection smartSelection = getSmartSelection(defaultLocales);
+            final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString);
+            for (SmartSelection.AnnotatedSpan span : annotations) {
+                final Map<String, Float> entityScores = new HashMap<>();
+                final SmartSelection.ClassificationResult[] results = span.getClassification();
+                for (int i = 0; i < results.length; i++) {
+                    entityScores.put(results[i].mCollection, results[i].mScore);
+                }
+                builder.addLink(new TextLinks.TextLink(
+                        textString, span.getStartIndex(), span.getEndIndex(), entityScores));
+            }
+        } catch (Throwable t) {
+            // Avoid throwing from this method. Log the error.
+            Log.e(LOG_TAG, "Error getting links info.", t);
+        }
+        return builder.build();
+    }
+
+    @Override
     public void logEvent(String source, String event) {
         if (LOG_TAG.equals(source)) {
             mMetricsLogger.count(event, 1);
@@ -680,8 +706,8 @@
                     intents.add(new Intent(Intent.ACTION_SENDTO)
                             .setData(Uri.parse(String.format("mailto:%s", text))));
                     intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
-                                    .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
-                                    .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
+                            .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+                            .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
                     break;
                 case TextClassifier.TYPE_PHONE:
                     intents.add(new Intent(Intent.ACTION_DIAL)
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
new file mode 100644
index 0000000..f3cc827
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 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.NonNull;
+import android.annotation.Nullable;
+import android.os.LocaleList;
+import android.text.SpannableString;
+import android.text.style.ClickableSpan;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * A collection of links, representing subsequences of text and the entity types (phone number,
+ * address, url, etc) they may be.
+ */
+public final class TextLinks {
+    private final String mFullText;
+    private final List<TextLink> mLinks;
+
+    private TextLinks(String fullText, Collection<TextLink> links) {
+        mFullText = fullText;
+        mLinks = Collections.unmodifiableList(new ArrayList<>(links));
+    }
+
+    /**
+     * Returns an unmodifiable Collection of the links.
+     */
+    public Collection<TextLink> getLinks() {
+        return mLinks;
+    }
+
+    /**
+     * Annotates the given text with the generated links. It will fail if the provided text doesn't
+     * match the original text used to crete the TextLinks.
+     *
+     * @param text the text to apply the links to. Must match the original text.
+     * @param spanFactory a factory to generate spans from TextLinks. Will use a default if null.
+     *
+     * @return Success or failure.
+     */
+    public boolean apply(
+            @NonNull SpannableString text,
+            @Nullable Function<TextLink, ClickableSpan> spanFactory) {
+        Preconditions.checkNotNull(text);
+        if (!mFullText.equals(text.toString())) {
+            return false;
+        }
+
+        if (spanFactory == null) {
+            spanFactory = DEFAULT_SPAN_FACTORY;
+        }
+        for (TextLink link : mLinks) {
+            final ClickableSpan span = spanFactory.apply(link);
+            if (span != null) {
+                text.setSpan(span, link.getStart(), link.getEnd(), 0);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * A link, identifying a substring of text and possible entity types for it.
+     */
+    public static final class TextLink {
+        private final EntityConfidence<String> mEntityScores;
+        private final String mOriginalText;
+        private final int mStart;
+        private final int mEnd;
+
+        /**
+         * Create a new TextLink.
+         *
+         * @throws IllegalArgumentException if entityScores is null or empty.
+         */
+        public TextLink(String originalText, int start, int end, Map<String, Float> entityScores) {
+            Preconditions.checkNotNull(originalText);
+            Preconditions.checkNotNull(entityScores);
+            Preconditions.checkArgument(!entityScores.isEmpty());
+            Preconditions.checkArgument(start <= end);
+            mOriginalText = originalText;
+            mStart = start;
+            mEnd = end;
+            mEntityScores = new EntityConfidence<>();
+
+            for (Map.Entry<String, Float> entry : entityScores.entrySet()) {
+                mEntityScores.setEntityType(entry.getKey(), entry.getValue());
+            }
+        }
+
+        /**
+         * Returns the start index of this link in the original text.
+         *
+         * @return the start index.
+         */
+        public int getStart() {
+            return mStart;
+        }
+
+        /**
+         * Returns the end index of this link in the original text.
+         *
+         * @return the end index.
+         */
+        public int getEnd() {
+            return mEnd;
+        }
+
+        /**
+         * Returns the number of entity types that have confidence scores.
+         *
+         * @return the entity count.
+         */
+        public int getEntityCount() {
+            return mEntityScores.getEntities().size();
+        }
+
+        /**
+         * Returns the entity type at a given index. Entity types are sorted by confidence.
+         *
+         * @return the entity type at the provided index.
+         */
+        @NonNull public @TextClassifier.EntityType String getEntity(int index) {
+            return mEntityScores.getEntities().get(index);
+        }
+
+        /**
+         * Returns the confidence score for a particular entity type.
+         *
+         * @param entityType the entity type.
+         */
+        public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore(
+                @TextClassifier.EntityType String entityType) {
+            return mEntityScores.getConfidenceScore(entityType);
+        }
+    }
+
+    /**
+     * Optional input parameters for generating TextLinks.
+     */
+    public static final class Options {
+        private final LocaleList mLocaleList;
+
+        private Options(LocaleList localeList) {
+            this.mLocaleList = localeList;
+        }
+
+        /**
+         * Builder to construct Options.
+         */
+        public static final class Builder {
+            private LocaleList mLocaleList;
+
+            /**
+             * Sets the LocaleList to use.
+             *
+             * @return this Builder.
+             */
+            public Builder setLocaleList(@Nullable LocaleList localeList) {
+                this.mLocaleList = localeList;
+                return this;
+            }
+
+            /**
+             * Builds the Options object.
+             */
+            public Options build() {
+                return new Options(mLocaleList);
+            }
+        }
+        public @Nullable LocaleList getDefaultLocales() {
+            return mLocaleList;
+        }
+    };
+
+    /**
+     * A function to create spans from TextLinks.
+     *
+     * Applies only to TextViews.
+     * We can hide this until we are convinced we want it to be part of the public API.
+     *
+     * @hide
+     */
+    public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY =
+            new Function<TextLink, ClickableSpan>() {
+        @Override
+        public ClickableSpan apply(TextLink textLink) {
+            // TODO: Implement.
+            throw new UnsupportedOperationException("Not yet implemented");
+        }
+    };
+
+    /**
+     * A builder to construct a TextLinks instance.
+     */
+    public static final class Builder {
+        private final String mFullText;
+        private final Collection<TextLink> mLinks;
+
+        /**
+         * Create a new TextLinks.Builder.
+         *
+         * @param fullText The full text that links will be added to.
+         */
+        public Builder(@NonNull String fullText) {
+            mFullText = Preconditions.checkNotNull(fullText);
+            mLinks = new ArrayList<>();
+        }
+
+        /**
+         * Adds a TextLink.
+         *
+         * @return this instance.
+         */
+        public Builder addLink(TextLink link) {
+            Preconditions.checkNotNull(link);
+            mLinks.add(link);
+            return this;
+        }
+
+        /**
+         * Constructs a TextLinks instance.
+         *
+         * @return the constructed TextLinks.
+         */
+        public TextLinks build() {
+            return new TextLinks(mFullText, mLinks);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 7870333..09f7282 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -176,8 +176,4 @@
         }
         return false;
     }
-
-    public int getLayoutHeight() {
-        return getLayout().getHeight();
-    }
 }
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
new file mode 100644
index 0000000..792f921
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -0,0 +1,393 @@
+/*
+ * 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.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {
+    private static Pools.SimplePool<MessagingGroup> sInstancePool
+            = new Pools.SynchronizedPool<>(10);
+    private MessagingLinearLayout mMessageContainer;
+    private ImageFloatingTextView mSenderName;
+    private ImageView mAvatarView;
+    private String mAvatarSymbol = "";
+    private int mLayoutColor;
+    private CharSequence mAvatarName = "";
+    private Icon mAvatarIcon;
+    private ColorFilter mMessageBackgroundFilter;
+    private int mTextColor;
+    private List<MessagingMessage> mMessages;
+    private ArrayList<MessagingMessage> mAddedMessages = new ArrayList<>();
+    private boolean mFirstLayout;
+    private boolean mIsHidingAnimated;
+
+    public MessagingGroup(@NonNull Context context) {
+        super(context);
+    }
+
+    public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mMessageContainer = findViewById(R.id.group_message_container);
+        mSenderName = findViewById(R.id.message_name);
+        mSenderName.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+        mAvatarView = findViewById(R.id.message_icon);
+    }
+
+    public void setSender(CharSequence sender) {
+        if (sender == null) {
+            mAvatarView.setVisibility(GONE);
+            mSenderName.setVisibility(GONE);
+            setGravity(Gravity.END);
+            mMessageBackgroundFilter = new PorterDuffColorFilter(mLayoutColor,
+                    PorterDuff.Mode.SRC_ATOP);
+            mTextColor = NotificationColorUtil.isColorLight(mLayoutColor) ? getNormalTextColor()
+                    : Color.WHITE;
+        } else {
+            mSenderName.setText(sender);
+            mAvatarView.setVisibility(VISIBLE);
+            mSenderName.setVisibility(VISIBLE);
+            setGravity(Gravity.START);
+            mMessageBackgroundFilter = null;
+            mTextColor = getNormalTextColor();
+        }
+    }
+
+    private int getNormalTextColor() {
+        return mContext.getColor(R.color.notification_primary_text_color_light);
+    }
+
+    public void setAvatar(Icon icon) {
+        mAvatarIcon = icon;
+        mAvatarView.setImageIcon(icon);
+        mAvatarSymbol = "";
+        mLayoutColor = 0;
+        mAvatarName = "";
+    }
+
+    static MessagingGroup createGroup(MessagingLinearLayout layout) {;
+        MessagingGroup createdGroup = sInstancePool.acquire();
+        if (createdGroup == null) {
+            createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate(
+                    R.layout.notification_template_messaging_group, layout,
+                    false);
+            createdGroup.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+        }
+        layout.addView(createdGroup);
+        return createdGroup;
+    }
+
+    public void removeMessage(MessagingMessage messagingMessage) {
+        mMessageContainer.removeView(messagingMessage);
+        Runnable recycleRunnable = () -> {
+            mMessageContainer.removeTransientView(messagingMessage);
+            messagingMessage.recycle();
+            if (mMessageContainer.getChildCount() == 0
+                    && mMessageContainer.getTransientViewCount() == 0) {
+                ViewParent parent = getParent();
+                if (parent instanceof ViewGroup) {
+                    ((ViewGroup) parent).removeView(MessagingGroup.this);
+                }
+                setAvatar(null);
+                mAvatarView.setAlpha(1.0f);
+                mAvatarView.setTranslationY(0.0f);
+                mSenderName.setAlpha(1.0f);
+                mSenderName.setTranslationY(0.0f);
+                sInstancePool.release(MessagingGroup.this);
+            }
+        };
+        if (isShown()) {
+            mMessageContainer.addTransientView(messagingMessage, 0);
+            performRemoveAnimation(messagingMessage, recycleRunnable);
+            if (mMessageContainer.getChildCount() == 0) {
+                removeGroupAnimated(null);
+            }
+        } else {
+            recycleRunnable.run();
+        }
+
+    }
+
+    private void removeGroupAnimated(Runnable endAction) {
+        MessagingPropertyAnimator.fadeOut(mAvatarView, null);
+        MessagingPropertyAnimator.startLocalTranslationTo(mAvatarView,
+                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+        MessagingPropertyAnimator.fadeOut(mSenderName, null);
+        MessagingPropertyAnimator.startLocalTranslationTo(mSenderName,
+                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+        boolean endActionTriggered = false;
+        for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+            View child = mMessageContainer.getChildAt(i);
+            if (child.getVisibility() == View.GONE) {
+                continue;
+            }
+            final ViewGroup.LayoutParams lp = child.getLayoutParams();
+            if (lp instanceof MessagingLinearLayout.LayoutParams
+                    && ((MessagingLinearLayout.LayoutParams) lp).hide
+                    && !((MessagingLinearLayout.LayoutParams) lp).visibleBefore) {
+                continue;
+            }
+            Runnable childEndAction = endActionTriggered ? null : endAction;
+            performRemoveAnimation(child, childEndAction);
+            endActionTriggered = true;
+        }
+        if (!endActionTriggered && endAction != null) {
+            endAction.run();
+        }
+    }
+
+    public void performRemoveAnimation(View message,
+            Runnable recycleRunnable) {
+        MessagingPropertyAnimator.fadeOut(message, recycleRunnable);
+        MessagingPropertyAnimator.startLocalTranslationTo(message,
+                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+    }
+
+    public CharSequence getSenderName() {
+        return mSenderName.getText();
+    }
+
+    public void setSenderVisible(boolean visible) {
+        mSenderName.setVisibility(visible ? VISIBLE : GONE);
+    }
+
+    public static void dropCache() {
+        sInstancePool = new Pools.SynchronizedPool<>(10);
+    }
+
+    @Override
+    public int getMeasuredType() {
+        boolean hasNormal = false;
+        for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+            View child = mMessageContainer.getChildAt(i);
+            if (child instanceof MessagingLinearLayout.MessagingChild) {
+                int type = ((MessagingLinearLayout.MessagingChild) child).getMeasuredType();
+                if (type == MEASURED_TOO_SMALL) {
+                    if (hasNormal) {
+                        return MEASURED_SHORTENED;
+                    } else {
+                        return MEASURED_TOO_SMALL;
+                    }
+                } else if (type == MEASURED_SHORTENED) {
+                    return MEASURED_SHORTENED;
+                } else {
+                    hasNormal = true;
+                }
+            }
+        }
+        return MEASURED_NORMAL;
+    }
+
+    @Override
+    public int getConsumedLines() {
+        int result = 0;
+        for (int i = 0; i < mMessageContainer.getChildCount(); i++) {
+            View child = mMessageContainer.getChildAt(i);
+            if (child instanceof MessagingLinearLayout.MessagingChild) {
+                result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines();
+            }
+        }
+        // A group is usually taking up quite some space with the padding and the name, let's add 1
+        return result + 1;
+    }
+
+    @Override
+    public void setMaxDisplayedLines(int lines) {
+        mMessageContainer.setMaxDisplayedLines(lines);
+    }
+
+    @Override
+    public void hideAnimated() {
+        setIsHidingAnimated(true);
+        removeGroupAnimated(() -> setIsHidingAnimated(false));
+    }
+
+    @Override
+    public boolean isHidingAnimated() {
+        return mIsHidingAnimated;
+    }
+
+    private void setIsHidingAnimated(boolean isHiding) {
+        ViewParent parent = getParent();
+        mIsHidingAnimated = isHiding;
+        invalidate();
+        if (parent instanceof ViewGroup) {
+            ((ViewGroup) parent).invalidate();
+        }
+    }
+
+    public Icon getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol,
+            int layoutColor) {
+        if (mAvatarName.equals(avatarName) && mAvatarSymbol.equals(avatarSymbol)
+                && layoutColor == mLayoutColor) {
+            return mAvatarIcon;
+        }
+        return null;
+    }
+
+    public void setCreatedAvatar(Icon cachedIcon, CharSequence avatarName, String avatarSymbol,
+            int layoutColor) {
+        if (!mAvatarName.equals(avatarName) || !mAvatarSymbol.equals(avatarSymbol)
+                || layoutColor != mLayoutColor) {
+            setAvatar(cachedIcon);
+            mAvatarSymbol = avatarSymbol;
+            mLayoutColor = layoutColor;
+            mAvatarName = avatarName;
+        }
+    }
+
+    public void setLayoutColor(int layoutColor) {
+        mLayoutColor = layoutColor;
+    }
+
+    public void setMessages(List<MessagingMessage> group) {
+        // Let's now make sure all children are added and in the correct order
+        for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) {
+            MessagingMessage message = group.get(messageIndex);
+            if (message.getGroup() != this) {
+                message.setMessagingGroup(this);
+                ViewParent parent = mMessageContainer.getParent();
+                if (parent instanceof ViewGroup) {
+                    ((ViewGroup) parent).removeView(message);
+                }
+                mMessageContainer.addView(message, messageIndex);
+                mAddedMessages.add(message);
+            }
+            if (messageIndex != mMessageContainer.indexOfChild(message)) {
+                mMessageContainer.removeView(message);
+                mMessageContainer.addView(message, messageIndex);
+            }
+            // Let's make sure the message color is correct
+            Drawable targetDrawable = message.getBackground();
+
+            if (targetDrawable != null) {
+                targetDrawable.mutate().setColorFilter(mMessageBackgroundFilter);
+            }
+            message.setTextColor(mTextColor);
+        }
+        mMessages = group;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (!mAddedMessages.isEmpty()) {
+            getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    for (MessagingMessage message : mAddedMessages) {
+                        if (!message.isShown()) {
+                            continue;
+                        }
+                        MessagingPropertyAnimator.fadeIn(message);
+                        if (!mFirstLayout) {
+                            MessagingPropertyAnimator.startLocalTranslationFrom(message,
+                                    message.getHeight(), MessagingLayout.LINEAR_OUT_SLOW_IN);
+                        }
+                    }
+                    mAddedMessages.clear();
+                    getViewTreeObserver().removeOnPreDrawListener(this);
+                    return true;
+                }
+            });
+        }
+        mFirstLayout = false;
+    }
+
+    /**
+     * Calculates the group compatibility between this and another group.
+     *
+     * @param otherGroup the other group to compare it with
+     *
+     * @return 0 if the groups are totally incompatible or 1 + the number of matching messages if
+     *         they match.
+     */
+    public int calculateGroupCompatibility(MessagingGroup otherGroup) {
+        if (TextUtils.equals(getSenderName(),otherGroup.getSenderName())) {
+            int result = 1;
+            for (int i = 0; i < mMessages.size() && i < otherGroup.mMessages.size(); i++) {
+                MessagingMessage ownMessage = mMessages.get(mMessages.size() - 1 - i);
+                MessagingMessage otherMessage = otherGroup.mMessages.get(
+                        otherGroup.mMessages.size() - 1 - i);
+                if (!ownMessage.sameAs(otherMessage)) {
+                    return result;
+                }
+                result++;
+            }
+            return result;
+        }
+        return 0;
+    }
+
+    public View getSender() {
+        return mSenderName;
+    }
+
+    public View getAvatar() {
+        return mAvatarView;
+    }
+
+    public MessagingLinearLayout getMessageContainer() {
+        return mMessageContainer;
+    }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
new file mode 100644
index 0000000..2acdc01
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -0,0 +1,444 @@
+/*
+ * 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.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.RemotableViewMethod;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
+ * messages and adapts the layout accordingly.
+ */
+@RemoteViews.RemoteView
+public class MessagingLayout extends FrameLayout {
+
+    private static final float COLOR_SHIFT_AMOUNT = 60;
+    private static final Consumer<MessagingMessage> REMOVE_MESSAGE
+            = MessagingMessage::removeMessage;
+    public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
+    public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+    public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+    public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR
+            = new MessagingPropertyAnimator();
+    private List<MessagingMessage> mMessages = new ArrayList<>();
+    private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
+    private MessagingLinearLayout mMessagingLinearLayout;
+    private View mContractedMessage;
+    private boolean mShowHistoricMessages;
+    private ArrayList<MessagingGroup> mGroups = new ArrayList<>();
+    private TextView mTitleView;
+    private int mLayoutColor;
+    private int mAvatarSize;
+    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private Paint mTextPaint = new Paint();
+    private CharSequence mConversationTitle;
+    private Icon mLargeIcon;
+    private boolean mIsOneToOne;
+    private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
+
+    public MessagingLayout(@NonNull Context context) {
+        super(context);
+    }
+
+    public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mMessagingLinearLayout = findViewById(R.id.notification_messaging);
+        mMessagingLinearLayout.setMessagingLayout(this);
+        // We still want to clip, but only on the top, since views can temporarily out of bounds
+        // during transitions.
+        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+        Rect rect = new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
+        mMessagingLinearLayout.setClipBounds(rect);
+        mTitleView = findViewById(R.id.title);
+        mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+        mTextPaint.setTextAlign(Paint.Align.CENTER);
+        mTextPaint.setAntiAlias(true);
+    }
+
+    @RemotableViewMethod
+    public void setLargeIcon(Icon icon) {
+        mLargeIcon = icon;
+    }
+
+    @RemotableViewMethod
+    public void setData(Bundle extras) {
+        Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+        List<Notification.MessagingStyle.Message> newMessages
+                = Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
+        Parcelable[] histMessages = extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
+        List<Notification.MessagingStyle.Message> newHistoricMessages
+                = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
+        mConversationTitle = null;
+        TextView headerText = findViewById(R.id.header_text);
+        if (headerText != null) {
+            mConversationTitle = headerText.getText();
+        }
+        bind(newMessages, newHistoricMessages);
+    }
+
+    private void bind(List<Notification.MessagingStyle.Message> newMessages,
+            List<Notification.MessagingStyle.Message> newHistoricMessages) {
+
+        List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
+                true /* isHistoric */);
+        List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
+        addMessagesToGroups(historicMessages, messages);
+
+        // Let's remove the remaining messages
+        mMessages.forEach(REMOVE_MESSAGE);
+        mHistoricMessages.forEach(REMOVE_MESSAGE);
+
+        mMessages = messages;
+        mHistoricMessages = historicMessages;
+
+        updateContractedMessage();
+        updateHistoricMessageVisibility();
+        updateTitleAndNamesDisplay();
+    }
+
+    private void updateTitleAndNamesDisplay() {
+        ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
+        ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();
+        for (int i = 0; i < mGroups.size(); i++) {
+            MessagingGroup group = mGroups.get(i);
+            CharSequence senderName = group.getSenderName();
+            if (TextUtils.isEmpty(senderName)) {
+                continue;
+            }
+            boolean visible = !mIsOneToOne;
+            group.setSenderVisible(visible);
+            if ((visible || mLargeIcon == null) && !uniqueNames.containsKey(senderName)) {
+                char c = senderName.charAt(0);
+                if (uniqueCharacters.containsKey(c)) {
+                    // this character was already used, lets make it more unique. We first need to
+                    // resolve the existing character if it exists
+                    CharSequence existingName = uniqueCharacters.get(c);
+                    if (existingName != null) {
+                        uniqueNames.put(existingName, findNameSplit((String) existingName));
+                        uniqueCharacters.put(c, null);
+                    }
+                    uniqueNames.put(senderName, findNameSplit((String) senderName));
+                } else {
+                    uniqueNames.put(senderName, Character.toString(c));
+                    uniqueCharacters.put(c, senderName);
+                }
+            }
+        }
+
+        // Now that we have the correct symbols, let's look what we have cached
+        ArrayMap<CharSequence, Icon> cachedAvatars = new ArrayMap<>();
+        for (int i = 0; i < mGroups.size(); i++) {
+            // Let's now set the avatars
+            MessagingGroup group = mGroups.get(i);
+            CharSequence senderName = group.getSenderName();
+            if (TextUtils.isEmpty(senderName) || (mIsOneToOne && mLargeIcon != null)) {
+                continue;
+            }
+            String symbol = uniqueNames.get(senderName);
+            Icon cachedIcon = group.getAvatarSymbolIfMatching(senderName,
+                    symbol, mLayoutColor);
+            if (cachedIcon != null) {
+                cachedAvatars.put(senderName, cachedIcon);
+            }
+        }
+
+        for (int i = 0; i < mGroups.size(); i++) {
+            // Let's now set the avatars
+            MessagingGroup group = mGroups.get(i);
+            CharSequence senderName = group.getSenderName();
+            if (TextUtils.isEmpty(senderName)) {
+                continue;
+            }
+            if (mIsOneToOne && mLargeIcon != null) {
+                group.setAvatar(mLargeIcon);
+            } else {
+                Icon cachedIcon = cachedAvatars.get(senderName);
+                if (cachedIcon == null) {
+                    cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName),
+                            mLayoutColor);
+                    cachedAvatars.put(senderName, cachedIcon);
+                }
+                group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName),
+                        mLayoutColor);
+            }
+        }
+    }
+
+    public Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
+        Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        float radius = mAvatarSize / 2.0f;
+        int color = findColor(senderName, layoutColor);
+        mPaint.setColor(color);
+        canvas.drawCircle(radius, radius, radius, mPaint);
+        boolean needDarkText  = ColorUtils.calculateLuminance(color) > 0.5f;
+        mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
+        mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.75f : mAvatarSize * 0.4f);
+        int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2)) ;
+        canvas.drawText(symbol, radius, yPos, mTextPaint);
+        return Icon.createWithBitmap(bitmap);
+    }
+
+    private int findColor(CharSequence senderName, int layoutColor) {
+        double luminance = NotificationColorUtil.calculateLuminance(layoutColor);
+        float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
+
+        // we need to offset the range if the luminance is too close to the borders
+        shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
+        shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
+        return NotificationColorUtil.getShiftedColor(layoutColor,
+                (int) (shift * COLOR_SHIFT_AMOUNT));
+    }
+
+    private String findNameSplit(String existingName) {
+        String[] split = existingName.split(" ");
+        if (split.length > 1) {
+            return Character.toString(split[0].charAt(0))
+                    + Character.toString(split[1].charAt(0));
+        }
+        return existingName.substring(0, 1);
+    }
+
+    @RemotableViewMethod
+    public void setLayoutColor(int color) {
+        mLayoutColor = color;
+    }
+
+    @RemotableViewMethod
+    public void setIsOneToOne(boolean oneToOne) {
+        mIsOneToOne = oneToOne;
+    }
+
+    private void addMessagesToGroups(List<MessagingMessage> historicMessages,
+            List<MessagingMessage> messages) {
+        // Let's first find our groups!
+        List<List<MessagingMessage>> groups = new ArrayList<>();
+        List<CharSequence> senders = new ArrayList<>();
+
+        // Lets first find the groups
+        findGroups(historicMessages, messages, groups, senders);
+
+        // Let's now create the views and reorder them accordingly
+        createGroupViews(groups, senders);
+    }
+
+    private void createGroupViews(List<List<MessagingMessage>> groups, List<CharSequence> senders) {
+        mGroups.clear();
+        for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
+            List<MessagingMessage> group = groups.get(groupIndex);
+            MessagingGroup newGroup = null;
+            // we'll just take the first group that exists or create one there is none
+            for (int messageIndex = group.size() - 1; messageIndex >= 0; messageIndex--) {
+                MessagingMessage message = group.get(messageIndex);
+                newGroup = message.getGroup();
+                if (newGroup != null) {
+                    break;
+                }
+            }
+            if (newGroup == null) {
+                newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
+                mAddedGroups.add(newGroup);
+            }
+            newGroup.setLayoutColor(mLayoutColor);
+            newGroup.setSender(senders.get(groupIndex));
+            mGroups.add(newGroup);
+
+            if (mMessagingLinearLayout.indexOfChild(newGroup) != groupIndex) {
+                mMessagingLinearLayout.removeView(newGroup);
+                mMessagingLinearLayout.addView(newGroup, groupIndex);
+            }
+            newGroup.setMessages(group);
+        }
+    }
+
+    private void findGroups(List<MessagingMessage> historicMessages,
+            List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
+            List<CharSequence> senders) {
+        CharSequence currentSender = null;
+        List<MessagingMessage> currentGroup = null;
+        int histSize = historicMessages.size();
+        for (int i = 0; i < histSize + messages.size(); i++) {
+            MessagingMessage message;
+            if (i < histSize) {
+                message = historicMessages.get(i);
+            } else {
+                message = messages.get(i - histSize);
+            }
+            boolean isNewGroup = currentGroup == null;
+            CharSequence sender = message.getMessage().getSender();
+            isNewGroup |= !TextUtils.equals(sender, currentSender);
+            if (isNewGroup) {
+                currentGroup = new ArrayList<>();
+                groups.add(currentGroup);
+                senders.add(sender);
+                currentSender = sender;
+            }
+            currentGroup.add(message);
+        }
+    }
+
+    private void updateContractedMessage() {
+        for (int i = mMessages.size() - 1; i >= 0; i--) {
+            MessagingMessage m = mMessages.get(i);
+            // Incoming messages have a non-empty sender.
+            if (!TextUtils.isEmpty(m.getMessage().getSender())) {
+                mContractedMessage = m;
+                return;
+            }
+        }
+        if (!mMessages.isEmpty()) {
+            // No incoming messages, fall back to outgoing message
+            mContractedMessage = mMessages.get(mMessages.size() - 1);
+            return;
+        }
+        mContractedMessage = null;
+    }
+
+    /**
+     * Creates new messages, reusing existing ones if they are available.
+     *
+     * @param newMessages the messages to parse.
+     */
+    private List<MessagingMessage> createMessages(
+            List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
+        List<MessagingMessage> result = new ArrayList<>();;
+        for (int i = 0; i < newMessages.size(); i++) {
+            Notification.MessagingStyle.Message m = newMessages.get(i);
+            MessagingMessage message = findAndRemoveMatchingMessage(m);
+            if (message == null) {
+                message = MessagingMessage.createMessage(this, m);
+                message.addOnLayoutChangeListener(MESSAGING_PROPERTY_ANIMATOR);
+            }
+            message.setIsHistoric(historic);
+            result.add(message);
+        }
+        return result;
+    }
+
+    private MessagingMessage findAndRemoveMatchingMessage(Notification.MessagingStyle.Message m) {
+        for (int i = 0; i < mMessages.size(); i++) {
+            MessagingMessage existing = mMessages.get(i);
+            if (existing.sameAs(m)) {
+                mMessages.remove(i);
+                return existing;
+            }
+        }
+        for (int i = 0; i < mHistoricMessages.size(); i++) {
+            MessagingMessage existing = mHistoricMessages.get(i);
+            if (existing.sameAs(m)) {
+                mHistoricMessages.remove(i);
+                return existing;
+            }
+        }
+        return null;
+    }
+
+    public void showHistoricMessages(boolean show) {
+        mShowHistoricMessages = show;
+        updateHistoricMessageVisibility();
+    }
+
+    private void updateHistoricMessageVisibility() {
+        for (int i = 0; i < mHistoricMessages.size(); i++) {
+            MessagingMessage existing = mHistoricMessages.get(i);
+            existing.setVisibility(mShowHistoricMessages ? VISIBLE : GONE);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (!mAddedGroups.isEmpty()) {
+            getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    for (MessagingGroup group : mAddedGroups) {
+                        if (!group.isShown()) {
+                            continue;
+                        }
+                        MessagingPropertyAnimator.fadeIn(group.getAvatar());
+                        MessagingPropertyAnimator.fadeIn(group.getSender());
+                        MessagingPropertyAnimator.startLocalTranslationFrom(group,
+                                group.getHeight(), LINEAR_OUT_SLOW_IN);
+                    }
+                    mAddedGroups.clear();
+                    getViewTreeObserver().removeOnPreDrawListener(this);
+                    return true;
+                }
+            });
+        }
+    }
+
+    public View getContractedMessage() {
+        return mContractedMessage;
+    }
+
+    public MessagingLinearLayout getMessagingLinearLayout() {
+        return mMessagingLinearLayout;
+    }
+
+    public ArrayList<MessagingGroup> getMessagingGroups() {
+        return mGroups;
+    }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index 70473a0..f0ef370 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -36,28 +36,14 @@
 @RemoteViews.RemoteView
 public class MessagingLinearLayout extends ViewGroup {
 
-    private static final int NOT_MEASURED_BEFORE = -1;
     /**
      * Spacing to be applied between views.
      */
     private int mSpacing;
 
-    /**
-     * The maximum height allowed.
-     */
-    private int mMaxHeight;
+    private int mMaxDisplayedLines = Integer.MAX_VALUE;
 
-    private int mIndentLines;
-
-    /**
-     * Id of the child that's also visible in the contracted layout.
-     */
-    private int mContractedChildId;
-    /**
-     * The last measured with in a layout pass if it was measured before or
-     * {@link #NOT_MEASURED_BEFORE} if this is the first layout pass.
-     */
-    private int mLastMeasuredWidth = NOT_MEASURED_BEFORE;
+    private MessagingLayout mMessagingLayout;
 
     public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -79,7 +65,6 @@
         a.recycle();
     }
 
-
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // This is essentially a bottom-up linear layout that only adds children that fit entirely
@@ -91,118 +76,67 @@
                 break;
         }
         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-        boolean recalculateVisibility = mLastMeasuredWidth == NOT_MEASURED_BEFORE
-                || getMeasuredHeight() != targetHeight
-                || mLastMeasuredWidth != widthSize;
-
-        final int count = getChildCount();
-        if (recalculateVisibility) {
-            // We only need to recalculate the view visibilities if the view wasn't measured already
-            // in this pass, otherwise we may drop messages here already since we are measured
-            // exactly with what we returned before, which was optimized already with the
-            // line-indents.
-            for (int i = 0; i < count; ++i) {
-                final View child = getChildAt(i);
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                lp.hide = true;
-            }
-
-            int totalHeight = mPaddingTop + mPaddingBottom;
-            boolean first = true;
-
-            // Starting from the bottom: we measure every view as if it were the only one. If it still
-
-            // fits, we take it, otherwise we stop there.
-            for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
-                if (getChildAt(i).getVisibility() == GONE) {
-                    continue;
-                }
-                final View child = getChildAt(i);
-                LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
-                ImageFloatingTextView textChild = null;
-                if (child instanceof ImageFloatingTextView) {
-                    // Pretend we need the image padding for all views, we don't know which
-                    // one will end up needing to do this (might end up not using all the space,
-                    // but calculating this exactly would be more expensive).
-                    textChild = (ImageFloatingTextView) child;
-                    textChild.setNumIndentLines(mIndentLines == 2 ? 3 : mIndentLines);
-                }
-
-                int spacing = first ? 0 : mSpacing;
-                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
-                        - mPaddingTop - mPaddingBottom + spacing);
-
-                final int childHeight = child.getMeasuredHeight();
-                int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
-                        lp.bottomMargin + spacing);
-                first = false;
-                boolean measuredTooSmall = false;
-                if (textChild != null) {
-                    measuredTooSmall = childHeight < textChild.getLayoutHeight()
-                            + textChild.getPaddingTop() + textChild.getPaddingBottom();
-                }
-
-                if (newHeight <= targetHeight && !measuredTooSmall) {
-                    totalHeight = newHeight;
-                    lp.hide = false;
-                } else {
-                    break;
-                }
-            }
-        }
 
         // Now that we know which views to take, fix up the indents and see what width we get.
         int measuredWidth = mPaddingLeft + mPaddingRight;
-        int imageLines = mIndentLines;
-        // Need to redo the height because it may change due to changing indents.
-        int totalHeight = mPaddingTop + mPaddingBottom;
-        boolean first = true;
-        for (int i = 0; i < count; i++) {
+        final int count = getChildCount();
+        int totalHeight;
+        for (int i = 0; i < count; ++i) {
             final View child = getChildAt(i);
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-            if (child.getVisibility() == GONE || lp.hide) {
-                continue;
-            }
-
-            if (child instanceof ImageFloatingTextView) {
-                ImageFloatingTextView textChild = (ImageFloatingTextView) child;
-                if (imageLines == 2 && textChild.getLineCount() > 2) {
-                    // HACK: If we need indent for two lines, and they're coming from the same
-                    // view, we need extra spacing to compensate for the lack of margins,
-                    // so add an extra line of indent.
-                    imageLines = 3;
-                }
-                boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines));
-                if (changed || !recalculateVisibility) {
-                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
-                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
-                            lp.width);
-                    // we want to measure it at most as high as it is currently, otherwise we'll
-                    // drop later lines
-                    final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
-                            targetHeight - child.getMeasuredHeight(), lp.height);
-
-                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);;
-                }
-                imageLines -= textChild.getLineCount();
-            }
-
-            measuredWidth = Math.max(measuredWidth,
-                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
-                            + mPaddingLeft + mPaddingRight);
-            totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() +
-                    lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing));
-            first = false;
+            lp.hide = true;
         }
 
+        totalHeight = mPaddingTop + mPaddingBottom;
+        boolean first = true;
+        int linesRemaining = mMaxDisplayedLines;
+
+        // Starting from the bottom: we measure every view as if it were the only one. If it still
+        // fits, we take it, otherwise we stop there.
+        for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
+            if (getChildAt(i).getVisibility() == GONE) {
+                continue;
+            }
+            final View child = getChildAt(i);
+            LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
+            MessagingChild messagingChild = null;
+            if (child instanceof MessagingChild) {
+                messagingChild = (MessagingChild) child;
+                messagingChild.setMaxDisplayedLines(linesRemaining);
+            }
+            int spacing = first ? 0 : mSpacing;
+            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
+                    - mPaddingTop - mPaddingBottom + spacing);
+
+            final int childHeight = child.getMeasuredHeight();
+            int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
+                    lp.bottomMargin + spacing);
+            first = false;
+            int measureType = MessagingChild.MEASURED_NORMAL;
+            if (messagingChild != null) {
+                measureType = messagingChild.getMeasuredType();
+                linesRemaining -= messagingChild.getConsumedLines();
+            }
+            boolean isShortened = measureType == MessagingChild.MEASURED_SHORTENED;
+            boolean isTooSmall = measureType == MessagingChild.MEASURED_TOO_SMALL;
+            if (newHeight <= targetHeight && !isTooSmall) {
+                totalHeight = newHeight;
+                measuredWidth = Math.max(measuredWidth,
+                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
+                                + mPaddingLeft + mPaddingRight);
+                lp.hide = false;
+                if (isShortened || linesRemaining <= 0) {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
 
         setMeasuredDimension(
                 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
                         widthMeasureSpec),
-                resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight),
-                        heightMeasureSpec));
-        mLastMeasuredWidth = widthSize;
+                Math.max(getSuggestedMinimumHeight(), totalHeight));
     }
 
     @Override
@@ -221,14 +155,23 @@
         childTop = mPaddingTop;
 
         boolean first = true;
-
+        final boolean shown = isShown();
         for (int i = 0; i < count; i++) {
             final View child = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-            if (child.getVisibility() == GONE || lp.hide) {
+            if (child.getVisibility() == GONE) {
                 continue;
             }
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            MessagingChild messagingChild = (MessagingChild) child;
+            if (lp.hide) {
+                if (shown && lp.visibleBefore) {
+                    messagingChild.hideAnimated();
+                }
+                lp.visibleBefore = false;
+                continue;
+            } else {
+                lp.visibleBefore = true;
+            }
 
             final int childWidth = child.getMeasuredWidth();
             final int childHeight = child.getMeasuredHeight();
@@ -251,14 +194,16 @@
 
             first = false;
         }
-        mLastMeasuredWidth = NOT_MEASURED_BEFORE;
     }
 
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
         if (lp.hide) {
-            return true;
+            MessagingChild messagingChild = (MessagingChild) child;
+            if (!messagingChild.isHidingAnimated()) {
+                return true;
+            }
         }
         return super.drawChild(canvas, child, drawingTime);
     }
@@ -284,31 +229,37 @@
     }
 
     /**
-     * Sets how many lines should be indented to avoid a floating image.
+     * Sets how many lines should be displayed at most
      */
     @RemotableViewMethod
-    public void setNumIndentLines(int numberLines) {
-        mIndentLines = numberLines;
+    public void setMaxDisplayedLines(int numberLines) {
+        mMaxDisplayedLines = numberLines;
     }
 
-    /**
-     * Set id of the child that's also visible in the contracted layout.
-     */
-    @RemotableViewMethod
-    public void setContractedChildId(int contractedChildId) {
-        mContractedChildId = contractedChildId;
+    public void setMessagingLayout(MessagingLayout layout) {
+        mMessagingLayout = layout;
     }
 
-    /**
-     * Get id of the child that's also visible in the contracted layout.
-     */
-    public int getContractedChildId() {
-        return mContractedChildId;
+    public MessagingLayout getMessagingLayout() {
+        return mMessagingLayout;
+    }
+
+    public interface MessagingChild {
+        int MEASURED_NORMAL = 0;
+        int MEASURED_SHORTENED = 1;
+        int MEASURED_TOO_SMALL = 2;
+
+        int getMeasuredType();
+        int getConsumedLines();
+        void setMaxDisplayedLines(int lines);
+        void hideAnimated();
+        boolean isHidingAnimated();
     }
 
     public static class LayoutParams extends MarginLayoutParams {
 
-        boolean hide = false;
+        public boolean hide = false;
+        public boolean visibleBefore = false;
 
         public LayoutParams(Context c, AttributeSet attrs) {
             super(c, attrs);
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
new file mode 100644
index 0000000..f09621f
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -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
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.util.Objects;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingMessage extends ImageFloatingTextView implements
+        MessagingLinearLayout.MessagingChild {
+
+    private static Pools.SimplePool<MessagingMessage> sInstancePool
+            = new Pools.SynchronizedPool<>(10);
+    private Notification.MessagingStyle.Message mMessage;
+    private MessagingGroup mGroup;
+    private boolean mIsHistoric;
+    private boolean mIsHidingAnimated;
+
+    public MessagingMessage(@NonNull Context context) {
+        super(context);
+    }
+
+    public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    private void setMessage(Notification.MessagingStyle.Message message) {
+        mMessage = message;
+        setText(message.getText());
+    }
+
+    public Notification.MessagingStyle.Message getMessage() {
+        return mMessage;
+    }
+
+    boolean sameAs(Notification.MessagingStyle.Message message) {
+        if (!Objects.equals(message.getText(), mMessage.getText())) {
+            return false;
+        }
+        if (!Objects.equals(message.getSender(), mMessage.getSender())) {
+            return false;
+        }
+        if (!Objects.equals(message.getTimestamp(), mMessage.getTimestamp())) {
+            return false;
+        }
+        return true;
+    }
+
+    boolean sameAs(MessagingMessage message) {
+        return sameAs(message.getMessage());
+    }
+
+    static MessagingMessage createMessage(MessagingLayout layout,
+            Notification.MessagingStyle.Message m) {
+        MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
+        MessagingMessage createdMessage = sInstancePool.acquire();
+        if (createdMessage == null) {
+            createdMessage = (MessagingMessage) LayoutInflater.from(layout.getContext()).inflate(
+                    R.layout.notification_template_messaging_message, messagingLinearLayout,
+                    false);
+        }
+        createdMessage.setMessage(m);
+        return createdMessage;
+    }
+
+    public void removeMessage() {
+        mGroup.removeMessage(this);
+    }
+
+    public void recycle() {
+        mGroup = null;
+        mMessage = null;
+        setAlpha(1.0f);
+        setTranslationY(0);
+        sInstancePool.release(this);
+    }
+
+    public void setMessagingGroup(MessagingGroup group) {
+        mGroup = group;
+    }
+
+    public static void dropCache() {
+        sInstancePool = new Pools.SynchronizedPool<>(10);
+    }
+
+    public void setIsHistoric(boolean isHistoric) {
+        mIsHistoric = isHistoric;
+    }
+
+    public MessagingGroup getGroup() {
+        return mGroup;
+    }
+
+    @Override
+    public int getMeasuredType() {
+        boolean measuredTooSmall = getMeasuredHeight()
+                < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+        if (measuredTooSmall) {
+            return MEASURED_TOO_SMALL;
+        } else {
+            Layout layout = getLayout();
+            if (layout == null) {
+                return MEASURED_TOO_SMALL;
+            }
+            if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
+                return MEASURED_SHORTENED;
+            } else {
+                return MEASURED_NORMAL;
+            }
+        }
+    }
+
+    @Override
+    public void hideAnimated() {
+        setIsHidingAnimated(true);
+        mGroup.performRemoveAnimation(this, () -> setIsHidingAnimated(false));
+    }
+
+    private void setIsHidingAnimated(boolean isHiding) {
+        ViewParent parent = getParent();
+        mIsHidingAnimated = isHiding;
+        invalidate();
+        if (parent instanceof ViewGroup) {
+            ((ViewGroup) parent).invalidate();
+        }
+    }
+
+    @Override
+    public boolean isHidingAnimated() {
+        return mIsHidingAnimated;
+    }
+
+    @Override
+    public void setMaxDisplayedLines(int lines) {
+        setMaxLines(lines);
+    }
+
+    @Override
+    public int getConsumedLines() {
+        return getLineCount();
+    }
+
+    public int getLayoutHeight() {
+        Layout layout = getLayout();
+        if (layout == null) {
+            return 0;
+        }
+        return layout.getHeight();
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+}
diff --git a/core/java/com/android/internal/widget/MessagingPropertyAnimator.java b/core/java/com/android/internal/widget/MessagingPropertyAnimator.java
new file mode 100644
index 0000000..7c3ab7f
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingPropertyAnimator.java
@@ -0,0 +1,233 @@
+/*
+ * 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.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.IntProperty;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import com.android.internal.R;
+
+/**
+ * A listener that automatically starts animations when the layout bounds change.
+ */
+public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
+    static final long APPEAR_ANIMATION_LENGTH = 210;
+    private static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+    public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+    private static final int TAG_LOCAL_TRANSLATION_ANIMATOR = R.id.tag_local_translation_y_animator;
+    private static final int TAG_LOCAL_TRANSLATION_Y = R.id.tag_local_translation_y;
+    private static final int TAG_LAYOUT_TOP = R.id.tag_layout_top;
+    private static final int TAG_ALPHA_ANIMATOR = R.id.tag_alpha_animator;
+    private static final ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS =
+            view -> view.getId() == com.android.internal.R.id.notification_messaging;
+    private static final IntProperty<View> LOCAL_TRANSLATION_Y =
+            new IntProperty<View>("localTranslationY") {
+                @Override
+                public void setValue(View object, int value) {
+                    setLocalTranslationY(object, value);
+                }
+
+                @Override
+                public Integer get(View object) {
+                    return getLocalTranslationY(object);
+                }
+            };
+
+    @Override
+    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+            int oldTop, int oldRight, int oldBottom) {
+        int oldHeight = oldBottom - oldTop;
+        Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
+        if (layoutTop != null) {
+            oldTop = layoutTop;
+        }
+        int topChange = oldTop - top;
+        if (oldHeight == 0 || topChange == 0 || !v.isShown() || isGone(v)) {
+            // First layout
+            return;
+        }
+        if (layoutTop != null) {
+            v.setTagInternal(TAG_LAYOUT_TOP, top);
+        }
+        int newHeight = bottom - top;
+        int heightDifference = oldHeight - newHeight;
+        // Only add the difference if the height changes and it's getting smaller
+        heightDifference = Math.max(heightDifference, 0);
+        startLocalTranslationFrom(v, topChange + heightDifference + getLocalTranslationY(v));
+    }
+
+    private boolean isGone(View view) {
+        if (view.getVisibility() == View.GONE) {
+            return true;
+        }
+        final ViewGroup.LayoutParams lp = view.getLayoutParams();
+        if (lp instanceof MessagingLinearLayout.LayoutParams
+                && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+            return true;
+        }
+        return false;
+    }
+
+    public static void startLocalTranslationFrom(View v, int startTranslation) {
+        startLocalTranslationFrom(v, startTranslation, MessagingLayout.FAST_OUT_SLOW_IN);
+    }
+
+    public static void startLocalTranslationFrom(View v, int startTranslation,
+            Interpolator interpolator) {
+        startLocalTranslation(v, startTranslation, 0, interpolator);
+    }
+
+    public static void startLocalTranslationTo(View v, int endTranslation,
+            Interpolator interpolator) {
+        startLocalTranslation(v, getLocalTranslationY(v), endTranslation, interpolator);
+    }
+
+    public static int getLocalTranslationY(View v) {
+        Integer tag = (Integer) v.getTag(TAG_LOCAL_TRANSLATION_Y);
+        if (tag == null) {
+            return 0;
+        }
+        return tag;
+    }
+
+    private static void setLocalTranslationY(View v, int value) {
+        v.setTagInternal(TAG_LOCAL_TRANSLATION_Y, value);
+        updateTopAndBottom(v);
+    }
+
+    private static void updateTopAndBottom(View v) {
+        int layoutTop = (int) v.getTag(TAG_LAYOUT_TOP);
+        int localTranslation = getLocalTranslationY(v);
+        int height = v.getHeight();
+        v.setTop(layoutTop + localTranslation);
+        v.setBottom(layoutTop + height + localTranslation);
+    }
+
+    private static void startLocalTranslation(final View v, int start, int end,
+            Interpolator interpolator) {
+        ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR);
+        if (existing != null) {
+            existing.cancel();
+        }
+        ObjectAnimator animator = ObjectAnimator.ofInt(v, LOCAL_TRANSLATION_Y, start, end);
+        Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
+        if (layoutTop == null) {
+            layoutTop = v.getTop();
+            v.setTagInternal(TAG_LAYOUT_TOP, layoutTop);
+        }
+        setLocalTranslationY(v, start);
+        animator.setInterpolator(interpolator);
+        animator.setDuration(APPEAR_ANIMATION_LENGTH);
+        animator.addListener(new AnimatorListenerAdapter() {
+            public boolean mCancelled;
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, null);
+                setClippingDeactivated(v, false);
+                if (!mCancelled) {
+                    setLocalTranslationY(v, 0);
+                    v.setTagInternal(TAG_LAYOUT_TOP, null);
+                }
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+        });
+        setClippingDeactivated(v, true);
+        v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, animator);
+        animator.start();
+    }
+
+    public static void fadeIn(final View v) {
+        ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_ALPHA_ANIMATOR);
+        if (existing != null) {
+            existing.cancel();
+        }
+        if (v.getVisibility() == View.INVISIBLE) {
+            v.setVisibility(View.VISIBLE);
+        }
+        ObjectAnimator animator = ObjectAnimator.ofFloat(v, View.ALPHA,
+                0.0f, 1.0f);
+        v.setAlpha(0.0f);
+        animator.setInterpolator(ALPHA_IN);
+        animator.setDuration(APPEAR_ANIMATION_LENGTH);
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                v.setTagInternal(TAG_ALPHA_ANIMATOR, null);
+                updateLayerType(v, false /* animating */);
+            }
+        });
+        updateLayerType(v, true /* animating */);
+        v.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
+        animator.start();
+    }
+
+    private static void updateLayerType(View view, boolean animating) {
+        if (view.hasOverlappingRendering() && animating) {
+            view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) {
+            view.setLayerType(View.LAYER_TYPE_NONE, null);
+        }
+    }
+
+    public static void fadeOut(final View view, Runnable endAction) {
+        ObjectAnimator existing = (ObjectAnimator) view.getTag(TAG_ALPHA_ANIMATOR);
+        if (existing != null) {
+            existing.cancel();
+        }
+        ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA,
+                view.getAlpha(), 0.0f);
+        animator.setInterpolator(ALPHA_OUT);
+        animator.setDuration(APPEAR_ANIMATION_LENGTH);
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                view.setTagInternal(TAG_ALPHA_ANIMATOR, null);
+                updateLayerType(view, false /* animating */);
+                if (endAction != null) {
+                    endAction.run();
+                }
+            }
+        });
+        updateLayerType(view, true /* animating */);
+        view.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
+        animator.start();
+    }
+
+    public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
+        ViewClippingUtil.setClippingDeactivated(transformedView, deactivated,
+                CLIPPING_PARAMETERS);
+    }
+
+    public static boolean isAnimatingTranslation(View v) {
+        return v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR) != null;
+    }
+
+    public static boolean isAnimatingAlpha(View v) {
+        return v.getTag(TAG_ALPHA_ANIMATOR) != null;
+    }
+}
diff --git a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
new file mode 100644
index 0000000..e352b45
--- /dev/null
+++ b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
@@ -0,0 +1,68 @@
+/*
+ * 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.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+/**
+ * A LinearLayout that sets it's height again after the last measure pass. This is needed for
+ * MessagingLayouts where groups need to be able to snap it's height to.
+ */
+@RemoteViews.RemoteView
+public class RemeasuringLinearLayout extends LinearLayout {
+
+    public RemeasuringLinearLayout(Context context) {
+        super(context);
+    }
+
+    public RemeasuringLinearLayout(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public RemeasuringLinearLayout(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public RemeasuringLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int count = getChildCount();
+        int height = 0;
+        for (int i = 0; i < count; ++i) {
+            final View child = getChildAt(i);
+            if (child == null || child.getVisibility() == View.GONE) {
+                continue;
+            }
+
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            height = Math.max(height, height + child.getMeasuredHeight() + lp.topMargin +
+                    lp.bottomMargin);
+        }
+        setMeasuredDimension(getMeasuredWidth(), height);
+    }
+}
diff --git a/core/java/com/android/internal/widget/ViewClippingUtil.java b/core/java/com/android/internal/widget/ViewClippingUtil.java
new file mode 100644
index 0000000..59bbed4
--- /dev/null
+++ b/core/java/com/android/internal/widget/ViewClippingUtil.java
@@ -0,0 +1,108 @@
+/*
+ * 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.widget;
+
+import android.util.ArraySet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import com.android.internal.R;
+
+/**
+ * A utility class that allows to clip views and their parents to allow for better transitions
+ */
+public class ViewClippingUtil {
+    private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
+    private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
+    private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
+
+    public static void setClippingDeactivated(final View transformedView, boolean deactivated,
+        ClippingParameters clippingParameters) {
+        if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
+            return;
+        }
+        if (!(transformedView.getParent() instanceof ViewGroup)) {
+            return;
+        }
+        ViewGroup parent = (ViewGroup) transformedView.getParent();
+        while (true) {
+            if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
+                return;
+            }
+            ArraySet<View> clipSet = (ArraySet<View>) parent.getTag(CLIP_CLIPPING_SET);
+            if (clipSet == null) {
+                clipSet = new ArraySet<>();
+                parent.setTagInternal(CLIP_CLIPPING_SET, clipSet);
+            }
+            Boolean clipChildren = (Boolean) parent.getTag(CLIP_CHILDREN_TAG);
+            if (clipChildren == null) {
+                clipChildren = parent.getClipChildren();
+                parent.setTagInternal(CLIP_CHILDREN_TAG, clipChildren);
+            }
+            Boolean clipToPadding = (Boolean) parent.getTag(CLIP_TO_PADDING);
+            if (clipToPadding == null) {
+                clipToPadding = parent.getClipToPadding();
+                parent.setTagInternal(CLIP_TO_PADDING, clipToPadding);
+            }
+            if (!deactivated) {
+                clipSet.remove(transformedView);
+                if (clipSet.isEmpty()) {
+                    parent.setClipChildren(clipChildren);
+                    parent.setClipToPadding(clipToPadding);
+                    parent.setTagInternal(CLIP_CLIPPING_SET, null);
+                    clippingParameters.onClippingStateChanged(parent, true);
+                }
+            } else {
+                clipSet.add(transformedView);
+                parent.setClipChildren(false);
+                parent.setClipToPadding(false);
+                clippingParameters.onClippingStateChanged(parent, false);
+            }
+            if (clippingParameters.shouldFinish(parent)) {
+                return;
+            }
+            final ViewParent viewParent = parent.getParent();
+            if (viewParent instanceof ViewGroup) {
+                parent = (ViewGroup) viewParent;
+            } else {
+                return;
+            }
+        }
+    }
+
+    public interface ClippingParameters {
+        /**
+         * Should we stop clipping at this view? If true is returned, {@param view} is the last view
+         * where clipping is activated / deactivated.
+         */
+        boolean shouldFinish(View view);
+
+        /**
+         * Is it allowed to enable clipping on this view.
+         */
+        default boolean isClippingEnablingAllowed(View view) {
+            return !MessagingPropertyAnimator.isAnimatingTranslation(view);
+        }
+
+        /**
+         * A method that is called whenever the view starts clipping again / stops clipping to the
+         * children and padding.
+         */
+        default void onClippingStateChanged(View view, boolean isClipping) {};
+    }
+}
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 9e6985c..1a19a40 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -36,6 +36,7 @@
 #include <hwui/Typeface.h>
 #include <utils/FatVector.h>
 #include <minikin/FontFamily.h>
+#include <minikin/LocaleList.h>
 
 #include <memory>
 
@@ -43,9 +44,10 @@
 
 struct NativeFamilyBuilder {
     NativeFamilyBuilder(uint32_t langId, int variant)
-        : langId(langId), variant(variant), allowUnsupportedFont(false) {}
+        : langId(langId), variant(static_cast<minikin::FontVariant>(variant)),
+          allowUnsupportedFont(false) {}
     uint32_t langId;
-    int variant;
+    minikin::FontVariant variant;
     bool allowUnsupportedFont;
     std::vector<minikin::Font> fonts;
     std::vector<minikin::FontVariation> axes;
@@ -55,10 +57,9 @@
     NativeFamilyBuilder* builder;
     if (langs != nullptr) {
         ScopedUtfChars str(env, langs);
-        builder = new NativeFamilyBuilder(
-                minikin::FontStyle::registerLocaleList(str.c_str()), variant);
+        builder = new NativeFamilyBuilder(minikin::registerLocaleList(str.c_str()), variant);
     } else {
-        builder = new NativeFamilyBuilder(minikin::FontStyle::registerLocaleList(""), variant);
+        builder = new NativeFamilyBuilder(minikin::registerLocaleList(""), variant);
     }
     return reinterpret_cast<jlong>(builder);
 }
@@ -121,14 +122,14 @@
             std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex,
                     builder->axes);
 
-    int weight = givenWeight / 100;
+    int weight = givenWeight;
     bool italic = givenItalic == 1;
     if (givenWeight == RESOLVE_BY_FONT_TABLE || givenItalic == RESOLVE_BY_FONT_TABLE) {
         int os2Weight;
         bool os2Italic;
         if (!minikin::FontFamily::analyzeStyle(minikinFont, &os2Weight, &os2Italic)) {
             ALOGE("analyzeStyle failed. Using default style");
-            os2Weight = 4;
+            os2Weight = 400;
             os2Italic = false;
         }
         if (givenWeight == RESOLVE_BY_FONT_TABLE) {
@@ -139,7 +140,8 @@
         }
     }
 
-    builder->fonts.push_back(minikin::Font(minikinFont, minikin::FontStyle(weight, italic)));
+    builder->fonts.push_back(minikin::Font(minikinFont,
+            minikin::FontStyle(weight, static_cast<minikin::FontSlant>(italic))));
     builder->axes.clear();
     return true;
 }
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index e8e3f57..5f32d37 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -41,6 +41,7 @@
 #include <hwui/Paint.h>
 #include <hwui/Typeface.h>
 #include <minikin/GraphemeBreak.h>
+#include <minikin/LocaleList.h>
 #include <minikin/Measurement.h>
 #include <unicode/utf16.h>
 
@@ -546,9 +547,9 @@
     static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
         Paint* obj = reinterpret_cast<Paint*>(objHandle);
         ScopedUtfChars localesChars(env, locales);
-        jint minikinLangListId = minikin::FontStyle::registerLocaleList(localesChars.c_str());
-        obj->setMinikinLangListId(minikinLangListId);
-        return minikinLangListId;
+        jint minikinLocaleListId = minikin::registerLocaleList(localesChars.c_str());
+        obj->setMinikinLocaleListId(minikinLocaleListId);
+        return minikinLocaleListId;
     }
 
     static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle, jstring settings) {
@@ -580,7 +581,7 @@
         // restore the original settings.
         paint->setTextSkewX(saveSkewX);
         paint->setFakeBoldText(savefakeBold);
-        if (paint->getFontVariant() == minikin::VARIANT_ELEGANT) {
+        if (paint->getFontVariant() == minikin::FontVariant::ELEGANT) {
             SkScalar size = paint->getTextSize();
             metrics->fTop = -size * kElegantTop / 2048;
             metrics->fBottom = -size * kElegantBottom / 2048;
@@ -871,20 +872,20 @@
         obj->setTextAlign(align);
     }
 
-    static void setTextLocalesByMinikinLangListId(jlong objHandle,
-            jint minikinLangListId) {
+    static void setTextLocalesByMinikinLocaleListId(jlong objHandle,
+            jint minikinLocaleListId) {
         Paint* obj = reinterpret_cast<Paint*>(objHandle);
-        obj->setMinikinLangListId(minikinLangListId);
+        obj->setMinikinLocaleListId(minikinLocaleListId);
     }
 
     static jboolean isElegantTextHeight(jlong paintHandle) {
         Paint* obj = reinterpret_cast<Paint*>(paintHandle);
-        return obj->getFontVariant() == minikin::VARIANT_ELEGANT;
+        return obj->getFontVariant() == minikin::FontVariant::ELEGANT;
     }
 
     static void setElegantTextHeight(jlong paintHandle, jboolean aa) {
         Paint* obj = reinterpret_cast<Paint*>(paintHandle);
-        obj->setFontVariant(aa ? minikin::VARIANT_ELEGANT : minikin::VARIANT_DEFAULT);
+        obj->setFontVariant(aa ? minikin::FontVariant::ELEGANT : minikin::FontVariant::DEFAULT);
     }
 
     static jfloat getTextSize(jlong paintHandle) {
@@ -1080,8 +1081,8 @@
     {"nSetTypeface","(JJ)V", (void*) PaintGlue::setTypeface},
     {"nGetTextAlign","(J)I", (void*) PaintGlue::getTextAlign},
     {"nSetTextAlign","(JI)V", (void*) PaintGlue::setTextAlign},
-    {"nSetTextLocalesByMinikinLangListId","(JI)V",
-            (void*) PaintGlue::setTextLocalesByMinikinLangListId},
+    {"nSetTextLocalesByMinikinLocaleListId","(JI)V",
+            (void*) PaintGlue::setTextLocalesByMinikinLocaleListId},
     {"nIsElegantTextHeight","(J)Z", (void*) PaintGlue::isElegantTextHeight},
     {"nSetElegantTextHeight","(JZ)V", (void*) PaintGlue::setElegantTextHeight},
     {"nGetTextSize","(J)F", (void*) PaintGlue::getTextSize},
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index d5f2a5c..3e4073f 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -83,7 +83,7 @@
 
 static jint Typeface_getWeight(JNIEnv* env, jobject obj, jlong faceHandle) {
     Typeface* face = reinterpret_cast<Typeface*>(faceHandle);
-    return face->fStyle.getWeight() * 100;
+    return face->fStyle.weight;
 }
 
 static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray,
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index f0ac79a..d18c172 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -554,18 +554,6 @@
     }
 }
 
-static void android_os_Parcel_clearFileDescriptor(JNIEnv* env, jclass clazz, jobject object)
-{
-    if (object == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return;
-    }
-    int fd = jniGetFDFromFileDescriptor(env, object);
-    if (fd >= 0) {
-        jniSetFileDescriptorOfFD(env, object, -1);
-    }
-}
-
 static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
 {
     Parcel* parcel = new Parcel();
@@ -811,7 +799,6 @@
     {"openFileDescriptor",        "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_openFileDescriptor},
     {"dupFileDescriptor",         "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_dupFileDescriptor},
     {"closeFileDescriptor",       "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_closeFileDescriptor},
-    {"clearFileDescriptor",       "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_clearFileDescriptor},
 
     {"nativeCreate",              "()J", (void*)android_os_Parcel_create},
     {"nativeFreeBuffer",          "(J)J", (void*)android_os_Parcel_freeBuffer},
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index c1419ba..ac79249 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -67,6 +67,18 @@
             return width - get(mIndents, lineNo);
         }
 
+        float getMinLineWidth() override {
+            // A simpler algorithm would have been simply looping until the larger of
+            // mFirstLineCount and mIndents.size()-mOffset, but that does unnecessary calculations
+            // when mFirstLineCount is large. Instead, we measure the first line, all the lines that
+            // have an indent, and the first line after firstWidth ends and restWidth starts.
+            float minWidth = std::min(getLineWidth(0), getLineWidth(mFirstLineCount));
+            for (size_t lineNo = 1; lineNo + mOffset < mIndents.size(); lineNo++) {
+                minWidth = std::min(minWidth, getLineWidth(lineNo));
+            }
+            return minWidth;
+        }
+
         float getLeftPadding(size_t lineNo) override {
             return get(mLeftPaddings, lineNo);
         }
@@ -126,19 +138,17 @@
 class StyleRun : public Run {
     public:
         StyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
-                std::shared_ptr<minikin::FontCollection>&& collection,
-                minikin::FontStyle&& style, bool isRtl)
+                std::shared_ptr<minikin::FontCollection>&& collection, bool isRtl)
             : Run(start, end), mPaint(std::move(paint)), mCollection(std::move(collection)),
-              mStyle(std::move(style)), mIsRtl(isRtl) {}
+              mIsRtl(isRtl) {}
 
         void addTo(minikin::LineBreaker* lineBreaker) override {
-            lineBreaker->addStyleRun(&mPaint, mCollection, mStyle, mStart, mEnd, mIsRtl);
+            lineBreaker->addStyleRun(&mPaint, mCollection, mStart, mEnd, mIsRtl);
         }
 
     private:
         minikin::MinikinPaint mPaint;
         std::shared_ptr<minikin::FontCollection> mCollection;
-        minikin::FontStyle mStyle;
         const bool mIsRtl;
 };
 
@@ -167,10 +177,9 @@
               mRightPaddings(std::move(rightPaddings)) {}
 
         void addStyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
-                         std::shared_ptr<minikin::FontCollection> collection,
-                         minikin::FontStyle&& style, bool isRtl) {
+                         std::shared_ptr<minikin::FontCollection> collection, bool isRtl) {
             mRuns.emplace_back(std::make_unique<StyleRun>(
-                    start, end, std::move(paint), std::move(collection), std::move(style), isRtl));
+                    start, end, std::move(paint), std::move(collection), isRtl));
         }
 
         void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) {
@@ -323,15 +332,9 @@
 static void nAddStyleRun(jlong nativePtr, jlong nativePaint, jint start, jint end, jboolean isRtl) {
     StaticLayoutNative* builder = toNative(nativePtr);
     Paint* paint = reinterpret_cast<Paint*>(nativePaint);
-    const Typeface* typeface = paint->getAndroidTypeface();
-    minikin::MinikinPaint minikinPaint;
-    const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface);
-    minikin::FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, paint,
-            typeface);
-
-    builder->addStyleRun(
-        start, end, std::move(minikinPaint), resolvedTypeface->fFontCollection, std::move(style),
-        isRtl);
+    const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
+    minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
+    builder->addStyleRun(start, end, std::move(minikinPaint), typeface->fFontCollection, isRtl);
 }
 
 // CriticalNative
@@ -339,7 +342,7 @@
         jfloat width) {
     StaticLayoutNative* builder = toNative(nativePtr);
     Paint* paint = reinterpret_cast<Paint*>(nativePaint);
-    builder->addReplacementRun(start, end, width, paint->getMinikinLangListId());
+    builder->addReplacementRun(start, end, width, paint->getMinikinLocaleListId());
 }
 
 static const JNINativeMethod gMethods[] = {
diff --git a/core/res/res/drawable/ic_reply_notification_large.xml b/core/res/res/drawable/ic_reply_notification_large.xml
new file mode 100644
index 0000000..e75afdd
--- /dev/null
+++ b/core/res/res/drawable/ic_reply_notification_large.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:inset="8dp">
+    <vector android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24.0"
+            android:viewportWidth="24.0">
+        <path
+            android:fillColor="#FFFFFFFF"
+            android:pathData="M10.0,9.0L10.0,5.0l-7.0,7.0 7.0,7.0l0.0,-4.1c5.0,0.0 8.5,1.6 11.0,5.1 -1.0,-5.0 -4.0,-10.0 -11.0,-11.0z"/>
+        <path
+            android:fillColor="#00000000"
+            android:pathData="M0 0h24v24H0z"/>
+    </vector>
+</inset>
diff --git a/core/res/res/drawable/messaging_message_background.xml b/core/res/res/drawable/messaging_message_background.xml
new file mode 100644
index 0000000..8a2096a
--- /dev/null
+++ b/core/res/res/drawable/messaging_message_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle"
+    android:tint="#14000000">
+    <corners android:radius="4dp" />
+    <padding android:bottom="6dp"
+        android:left="8dp"
+        android:right="8dp"
+        android:top="6dp" />
+</shape>
diff --git a/core/res/res/layout/chooser_row.xml b/core/res/res/layout/chooser_row.xml
index 6c1271d..cf81260 100644
--- a/core/res/res/layout/chooser_row.xml
+++ b/core/res/res/layout/chooser_row.xml
@@ -22,8 +22,7 @@
               android:layout_height="100dp"
               android:gravity="start|top"
               android:paddingStart="@dimen/chooser_grid_padding"
-              android:paddingEnd="@dimen/chooser_grid_padding"
-              android:weightSum="4">
+              android:paddingEnd="@dimen/chooser_grid_padding">
 
 </LinearLayout>
 
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index fd5154a..f72230b 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -14,13 +14,17 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.MessagingLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/status_bar_latest_event_content"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:tag="messaging"
     >
-    <include layout="@layout/notification_template_header" />
+    <include layout="@layout/notification_template_header"
+             android:layout_width="wrap_content"
+             android:layout_height="@dimen/notification_header_height"
+             android:layout_marginEnd="56dp"/>
     <LinearLayout
             android:id="@+id/notification_action_list_margin_target"
             android:layout_width="match_parent"
@@ -39,7 +43,6 @@
             android:paddingEnd="@dimen/notification_content_margin_end"
             android:minHeight="@dimen/notification_min_content_height"
             android:layout_marginBottom="@dimen/notification_content_margin_bottom"
-            android:clipToPadding="false"
             android:orientation="vertical"
             >
             <include layout="@layout/notification_template_part_line1"
@@ -50,31 +53,14 @@
                 android:id="@+id/notification_messaging"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:spacing="@dimen/notification_messaging_spacing" >
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text0"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text1"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text2"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text3"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text4"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text5"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text6"
-                    style="@style/Widget.Material.Notification.MessagingText"
-                    />
-            </com.android.internal.widget.MessagingLinearLayout>
+                android:layout_marginTop="8dp"
+                android:spacing="@dimen/notification_messaging_spacing" />
         </LinearLayout>
     </LinearLayout>
     <include layout="@layout/notification_material_action_list" />
-    <include layout="@layout/notification_template_right_icon" />
-</FrameLayout>
+    <include layout="@layout/notification_template_right_icon"
+             android:layout_width="wrap_content"
+             android:layout_height="wrap_content"
+             android:layout_marginTop="18dp"
+             android:layout_gravity="top|end"/>
+</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml
new file mode 100644
index 0000000..8973ceb
--- /dev/null
+++ b/core/res/res/layout/notification_template_messaging_group.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<com.android.internal.widget.MessagingGroup
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/status_bar_latest_event_content"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal" >
+    <ImageView
+        android:id="@+id/message_icon"
+        android:layout_width="@dimen/messaging_avatar_size"
+        android:layout_height="@dimen/messaging_avatar_size"
+        android:layout_marginEnd="8dp"
+        android:scaleType="centerCrop"
+        android:importantForAccessibility="no" />
+    <com.android.internal.widget.RemeasuringLinearLayout
+        android:id="@+id/message_group_and_sender_container"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <com.android.internal.widget.MessagingLinearLayout
+            android:id="@+id/group_message_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:spacing="2dp"
+            android:layout_weight="1"/>
+        <com.android.internal.widget.ImageFloatingTextView
+            android:id="@+id/message_name"
+            style="@style/Widget.Material.Notification.MessagingName"
+            android:layout_width="wrap_content"
+            android:paddingStart="8dp"
+            android:paddingEnd="8dp"
+            android:paddingTop="2dp"
+        />
+    </com.android.internal.widget.RemeasuringLinearLayout>
+</com.android.internal.widget.MessagingGroup>
diff --git a/core/res/res/layout/notification_template_messaging_message.xml b/core/res/res/layout/notification_template_messaging_message.xml
new file mode 100644
index 0000000..ab6466c
--- /dev/null
+++ b/core/res/res/layout/notification_template_messaging_message.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<com.android.internal.widget.MessagingMessage
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/message_text"
+    style="@style/Widget.Material.Notification.MessagingText"
+/>
diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml
index d379256..8fb2887 100644
--- a/core/res/res/layout/notification_template_right_icon.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -19,12 +19,12 @@
              android:id="@+id/right_icon_container"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
+             android:layout_marginTop="36dp"
              android:layout_gravity="top|end">
     <ImageView android:id="@+id/right_icon"
                android:layout_width="@dimen/notification_right_icon_size"
                android:layout_height="@dimen/notification_right_icon_size"
                android:layout_gravity="top|end"
-               android:layout_marginTop="36dp"
                android:layout_marginEnd="@dimen/notification_content_margin_end"
                android:scaleType="centerCrop"
                android:importantForAccessibility="no" />
@@ -32,7 +32,7 @@
                android:layout_width="16dp"
                android:layout_height="16dp"
                android:layout_gravity="top|end"
-               android:layout_marginTop="64dp"
+               android:layout_marginTop="28dp"
                android:layout_marginEnd="12dp"
                android:background="@drawable/notification_reply_background"
                android:src="@drawable/ic_reply_notification"
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 57312e7..2389d4d 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -146,7 +146,7 @@
     <string name="httpErrorConnect" msgid="8714273236364640549">"Tidak dapat tersambung ke server."</string>
     <string name="httpErrorIO" msgid="2340558197489302188">"Tidak dapat berkomunikasi dengan server. Coba lagi nanti."</string>
     <string name="httpErrorTimeout" msgid="4743403703762883954">"Sambungan ke server terputus."</string>
-    <string name="httpErrorRedirectLoop" msgid="8679596090392779516">"Laman ini berisi terlalu banyak pengalihan server."</string>
+    <string name="httpErrorRedirectLoop" msgid="8679596090392779516">"Halaman ini berisi terlalu banyak pengalihan server."</string>
     <string name="httpErrorUnsupportedScheme" msgid="5015730812906192208">"Protokol tidak didukung."</string>
     <string name="httpErrorFailedSslHandshake" msgid="96549606000658641">"Tidak dapat membuat sambungan aman."</string>
     <string name="httpErrorBadUrl" msgid="3636929722728881972">"Tidak dapat membuka halaman karena URL tidak valid."</string>
@@ -295,7 +295,7 @@
     <string name="capability_title_canControlMagnification" msgid="3593493281059424855">"Mengontrol perbesaran layar"</string>
     <string name="capability_desc_canControlMagnification" msgid="4791858203568383773">"Mengontrol tingkat zoom dan pemosisian layar."</string>
     <string name="capability_title_canPerformGestures" msgid="7418984730362576862">"Melakukan isyarat"</string>
-    <string name="capability_desc_canPerformGestures" msgid="8296373021636981249">"Dapat mengetuk, menggesek, mencubit, dan melakukan isyarat lainnya."</string>
+    <string name="capability_desc_canPerformGestures" msgid="8296373021636981249">"Dapat mengetuk, menggeser, mencubit, dan melakukan isyarat lainnya."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="6309568287512278670">"Gestur sidik jari"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="7102111919385702482">"Dapat merekam gestur yang dilakukan di sensor sidik jari perangkat."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"nonaktifkan atau ubah bilah status"</string>
@@ -318,8 +318,8 @@
     <string name="permdesc_receiveMms" msgid="533019437263212260">"Memungkinkan aplikasi menerima dan memproses pesan MMS. Ini artinya aplikasi dapat memantau atau menghapus pesan yang dikirim ke perangkat Anda tanpa menunjukkannya kepada Anda."</string>
     <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"membaca pesan siaran seluler"</string>
     <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Mengizinkan aplikasi membaca pesan siaran seluler yang diterima perangkat Anda. Notifikasi siaran seluler dikirimkan di beberapa lokasi untuk memperingatkan Anda tentang situasi darurat. Aplikasi berbahaya dapat mengganggu kinerja atau operasi perangkat Anda saat siaran seluler darurat diterima."</string>
-    <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"baca umpan langganan"</string>
-    <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"Mengizinkan apl mendapatkan detail tentang umpan yang saat ini sedang disinkronkan."</string>
+    <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"baca feed langganan"</string>
+    <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"Mengizinkan apl mendapatkan detail tentang feed yang saat ini sedang disinkronkan."</string>
     <string name="permlab_sendSms" msgid="7544599214260982981">"mengirim dan melihat pesan SMS"</string>
     <string name="permdesc_sendSms" msgid="7094729298204937667">"Memungkinkan aplikasi mengirim pesan SMS. Izin ini dapat mengakibatkan biaya tak terduga. Aplikasi berbahaya dapat membebankan biaya kepada Anda dengan mengirim pesan tanpa konfirmasi dari Anda."</string>
     <string name="permlab_readSms" msgid="8745086572213270480">"membaca pesan teks (SMS atau MMS) Anda"</string>
@@ -802,11 +802,11 @@
     <string name="factorytest_not_system" msgid="4435201656767276723">"Tindakan FACTORY_TEST hanya didukung untuk paket yang terpasang pada /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Tidak ada paket yang memberikan tindakan FACTORY_TEST."</string>
     <string name="factorytest_reboot" msgid="6320168203050791643">"Mulai ulang"</string>
-    <string name="js_dialog_title" msgid="1987483977834603872">"Laman pada \"<xliff:g id="TITLE">%s</xliff:g>\" menyatakan:"</string>
+    <string name="js_dialog_title" msgid="1987483977834603872">"Halaman pada \"<xliff:g id="TITLE">%s</xliff:g>\" menyatakan:"</string>
     <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string>
     <string name="js_dialog_before_unload_title" msgid="2619376555525116593">"Konfirmasi Navigasi"</string>
-    <string name="js_dialog_before_unload_positive_button" msgid="3112752010600484130">"Keluar dari Laman ini"</string>
-    <string name="js_dialog_before_unload_negative_button" msgid="5614861293026099715">"Tetap di Laman ini"</string>
+    <string name="js_dialog_before_unload_positive_button" msgid="3112752010600484130">"Keluar dari Halaman ini"</string>
+    <string name="js_dialog_before_unload_negative_button" msgid="5614861293026099715">"Tetap di Halaman ini"</string>
     <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nYakin ingin beranjak dari halaman ini?"</string>
     <string name="save_password_label" msgid="6860261758665825069">"Konfirmasi"</string>
     <string name="double_tap_toast" msgid="4595046515400268881">"Kiat: Ketuk dua kali untuk memperbesar dan memperkecil."</string>
@@ -1042,7 +1042,7 @@
     <string name="force_close" msgid="8346072094521265605">"Oke"</string>
     <string name="report" msgid="4060218260984795706">"Laporkan"</string>
     <string name="wait" msgid="7147118217226317732">"Tunggu"</string>
-    <string name="webpage_unresponsive" msgid="3272758351138122503">"Laman ini tidak menanggapi.\n\nApakah Anda ingin menutupnya?"</string>
+    <string name="webpage_unresponsive" msgid="3272758351138122503">"Halaman ini tidak menanggapi.\n\nApakah Anda ingin menutupnya?"</string>
     <string name="launch_warning_title" msgid="1547997780506713581">"Apl dialihkan"</string>
     <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang berjalan."</string>
     <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> telah diluncurkan aslinya."</string>
@@ -1371,7 +1371,7 @@
     <string name="shareactionprovider_share_with" msgid="806688056141131819">"Berbagi dengan"</string>
     <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"Berbagi dengan <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="content_description_sliding_handle" msgid="415975056159262248">"Gagang geser. Sentuh &amp; tahan."</string>
-    <string name="description_target_unlock_tablet" msgid="3833195335629795055">"Gesek untuk membuka kunci."</string>
+    <string name="description_target_unlock_tablet" msgid="3833195335629795055">"Geser untuk membuka kunci."</string>
     <string name="action_bar_home_description" msgid="5293600496601490216">"Navigasi ke beranda"</string>
     <string name="action_bar_up_description" msgid="2237496562952152589">"Navigasi naik"</string>
     <string name="action_menu_overflow_description" msgid="2295659037509008453">"Opsi lainnya"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 96a9eda..af37300 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -36,7 +36,7 @@
     <string name="serviceDisabled" msgid="1937553226592516411">"सेवा अक्षम केली गेली आहे."</string>
     <string name="serviceRegistered" msgid="6275019082598102493">"नोंदणी यशस्वी झाली."</string>
     <string name="serviceErased" msgid="1288584695297200972">"मिटवणे यशस्वी झाले."</string>
-    <string name="passwordIncorrect" msgid="7612208839450128715">"अयोग्य संकेतशब्द."</string>
+    <string name="passwordIncorrect" msgid="7612208839450128715">"अयोग्य पासवर्ड."</string>
     <string name="mmiComplete" msgid="8232527495411698359">"MMI पूर्ण."</string>
     <string name="badPin" msgid="9015277645546710014">"आपण टाइप केलेला जुना पिन योग्य नाही."</string>
     <string name="badPuk" msgid="5487257647081132201">"आपण टाइप केलेला PUK योग्य नाही."</string>
@@ -59,7 +59,7 @@
     <string name="CfMmi" msgid="5123218989141573515">"कॉल फॉरवर्डिंग"</string>
     <string name="CwMmi" msgid="9129678056795016867">"कॉल प्रतीक्षा"</string>
     <string name="BaMmi" msgid="455193067926770581">"कॉल सोडून"</string>
-    <string name="PwdMmi" msgid="7043715687905254199">"संकेतशब्द बदल"</string>
+    <string name="PwdMmi" msgid="7043715687905254199">"पासवर्ड बदल"</string>
     <string name="PinMmi" msgid="3113117780361190304">"पिन बदल"</string>
     <string name="CnipMmi" msgid="3110534680557857162">"कॉल करण्‍याचा नंबर आहे"</string>
     <string name="CnirMmi" msgid="3062102121430548731">"कॉल करणारे नंबर प्रतिबंधित"</string>
@@ -192,9 +192,9 @@
     <string name="reboot_to_update_title" msgid="6212636802536823850">"Android सिस्टम अपडेट"</string>
     <string name="reboot_to_update_prepare" msgid="6305853831955310890">"अपडेट करण्याची तयारी करत आहे…"</string>
     <string name="reboot_to_update_package" msgid="3871302324500927291">"अपडेट पॅकेज प्रक्रिया करत आहे…"</string>
-    <string name="reboot_to_update_reboot" msgid="6428441000951565185">"रीस्टार्ट करीत आहे..."</string>
+    <string name="reboot_to_update_reboot" msgid="6428441000951565185">"रीस्टार्ट करत आहे..."</string>
     <string name="reboot_to_reset_title" msgid="4142355915340627490">"फॅक्‍टरी डेटा रीसेट"</string>
-    <string name="reboot_to_reset_message" msgid="2432077491101416345">"रीस्टार्ट करीत आहे..."</string>
+    <string name="reboot_to_reset_message" msgid="2432077491101416345">"रीस्टार्ट करत आहे..."</string>
     <string name="shutdown_progress" msgid="2281079257329981203">"बंद होत आहे…"</string>
     <string name="shutdown_confirm" product="tablet" msgid="3385745179555731470">"आपला टॅबलेट बंद होईल."</string>
     <string name="shutdown_confirm" product="tv" msgid="476672373995075359">"आपला टीव्ही बंद होईल."</string>
@@ -215,7 +215,7 @@
     <string name="bugreport_title" msgid="2667494803742548533">"बग रीपोर्ट घ्या"</string>
     <string name="bugreport_message" msgid="398447048750350456">"ई-मेल संदेश म्हणून पाठविण्यासाठी, हे तुमच्या सद्य डिव्हाइस स्थितीविषयी माहिती संकलित करेल. बग रीपोर्ट सुरू करण्यापासून तो पाठविण्यापर्यंत थोडा वेळ लागेल; कृपया धीर धरा."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"परस्परसंवादी अहवाल"</string>
-    <string name="bugreport_option_interactive_summary" msgid="229299488536107968">"बहुतांश प्रसंगांमध्ये याचा वापर करा. ते आपल्याला अहवालाच्या प्रगतीचा मागोवा घेण्याची, समस्येविषयी आणखी तपाशील प्रविष्ट करण्याची आणि स्क्रीनशॉट घेण्याची अनुमती देते. ते कदाचित अहवाल देण्यासाठी बराच वेळ घेणारे कमी-वापरलेले विभाग वगळू शकते."</string>
+    <string name="bugreport_option_interactive_summary" msgid="229299488536107968">"बहुतांश प्रसंगांमध्ये याचा वापर करा. ते आपल्याला अहवालाच्या प्रगतीचा मागोवा घेण्याची, समस्येविषयी आणखी तपाशील एंटर करण्याची आणि स्क्रीनशॉट घेण्याची अनुमती देते. ते कदाचित अहवाल देण्यासाठी बराच वेळ घेणारे कमी-वापरलेले विभाग वगळू शकते."</string>
     <string name="bugreport_option_full_title" msgid="6354382025840076439">"संपूर्ण अहवाल"</string>
     <string name="bugreport_option_full_summary" msgid="7210859858969115745">"तुमचे डिव्हाइस प्रतिसाद देत नाही किंवा खूप धीमे असते किंवा तुम्हाला सर्व अहवाल विभागांची आवश्यकता असते तेव्हा कमीतकमी सिस्टम हस्तक्षेपासाठी या पर्यायाचा वापर करा. तुम्हाला आणखी तपशील एंटर करण्याची किंवा अतिरिक्त स्क्रीनशॉट घेण्याची अनुमती देत नाही."</string>
     <plurals name="bugreport_countdown" formatted="false" msgid="6878900193900090368">
@@ -461,13 +461,13 @@
     <string name="permdesc_changeWimaxState" product="tv" msgid="6022307083934827718">"WiMAX नेटवर्कवरून टीव्ही कनेक्ट करण्यासाठी आणि त्यावरून टीव्ही डिस्कनेक्ट करण्यासाठी अॅपला अनुमती देते."</string>
     <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"WiMAX नेटवर्कवर फोन कनेक्ट करण्यास आणि त्यावरून फोन डिस्कनेक्ट करण्यास अॅप ला अनुमती देते."</string>
     <string name="permlab_bluetooth" msgid="6127769336339276828">"ब्लूटूथ डीव्हाइससह जोडा"</string>
-    <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"टॅबलेटवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन स्थापित करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string>
-    <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"टीव्हीवर ब्लूटूथचे कॉंफिगरेशन पाहण्यासाठी आणि जोडलेल्या डीव्हाइससह कनेक्शन स्थापित करण्यासाठी आणि स्वीकारण्यासाठी अॅपला अनुमती देते."</string>
-    <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"फोनवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन स्थापित करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string>
+    <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"टॅबलेटवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string>
+    <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"टीव्हीवर ब्लूटूथचे कॉंफिगरेशन पाहण्यासाठी आणि जोडलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी अॅपला अनुमती देते."</string>
+    <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"फोनवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string>
     <string name="permlab_nfc" msgid="4423351274757876953">"फील्ड जवळील कम्युनिकेशन नियंत्रित करा"</string>
     <string name="permdesc_nfc" msgid="7120611819401789907">"फील्ड जवळील कम्युनिकेशन (NFC) टॅग, कार्डे आणि वाचक यांच्यासह संवाद करण्यासाठी अॅपला अनुमती देते."</string>
     <string name="permlab_disableKeyguard" msgid="3598496301486439258">"आपले स्क्रीन लॉक अक्षम करा"</string>
-    <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"कीलॉक आणि कोणतीही संबद्ध संकेतशब्द सुरक्षितता अक्षम करण्यासाठी अॅप ला अनुमती देते. उदाहरणार्थ, येणारा फोन कॉल प्राप्त करताना फोन कीलॉक अक्षम करतो, नंतर जेव्हा कॉल समाप्त होतो तेव्हा तो कीलॉक पुन्हा-सक्षम करतो."</string>
+    <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"कीलॉक आणि कोणतीही संबद्ध पासवर्ड सुरक्षितता अक्षम करण्यासाठी अॅप ला अनुमती देते. उदाहरणार्थ, येणारा फोन कॉल प्राप्त करताना फोन कीलॉक अक्षम करतो, नंतर जेव्हा कॉल समाप्त होतो तेव्हा तो कीलॉक पुन्हा-सक्षम करतो."</string>
     <string name="permlab_manageFingerprint" msgid="5640858826254575638">"फिंगरप्रिंट हार्डवेअर व्यवस्थापित करा"</string>
     <string name="permdesc_manageFingerprint" msgid="178208705828055464">"वापर करण्याकरिता फिंगरप्रिंट टेम्पलेट जोडण्यासाठी आणि हटविण्यासाठी पद्धती रद्द करण्यास अॅपला अनुमती देते."</string>
     <string name="permlab_useFingerprint" msgid="3150478619915124905">"फिंगरप्रिंट हार्डवेअर वापरा"</string>
@@ -550,15 +550,15 @@
     <string name="permdesc_bindCarrierServices" msgid="1391552602551084192">"वाहक सेवांवर प्रतिबद्ध करण्यासाठी होल्डरला अनुमती देते. सामान्य अॅप्ससाठी कधीही आवश्यकता नसावी."</string>
     <string name="permlab_access_notification_policy" msgid="4247510821662059671">"व्यत्यय आणू नका अॅक्सेस करा"</string>
     <string name="permdesc_access_notification_policy" msgid="3296832375218749580">"व्यत्यय आणू नका कॉन्फिगरेशन वाचण्यासाठी आणि लिहिण्यासाठी अॅपला अनुमती देते."</string>
-    <string name="policylab_limitPassword" msgid="4497420728857585791">"संकेतशब्द नियम सेट करा"</string>
+    <string name="policylab_limitPassword" msgid="4497420728857585791">"पासवर्ड नियम सेट करा"</string>
     <string name="policydesc_limitPassword" msgid="2502021457917874968">"स्क्रीन लॉक पासवर्ड आणि पिन मध्ये अनुमती दिलेले लांबी आणि वर्ण नियंत्रित करा."</string>
     <string name="policylab_watchLogin" msgid="5091404125971980158">"स्क्रीन अनलॉक प्रयत्नांचे परीक्षण करा"</string>
-    <string name="policydesc_watchLogin" product="tablet" msgid="3215729294215070072">"टाइप केलेल्या अयोग्य संकेतशब्दांच्या अंकांचे परीक्षण करा. स्क्रीन अनलॉक केली जाते, तेव्हा टॅबलेट लॉक करा किंवा बरेच संकेतशब्द टाइप केले असल्यास टॅबलेटचा सर्व डेटा मिटवा."</string>
-    <string name="policydesc_watchLogin" product="TV" msgid="2707817988309890256">"स्क्रीन अनलॉक करताना टाइप केलेल्या चुकीच्या संकेतशब्दांच्या संख्येचे परीक्षण करा आणि टीव्ही लॉक करा किंवा अनेक चुकीचे संकेतशब्द टाइप केले असल्यास टीव्हीचा सर्व डेटा मिटवा."</string>
-    <string name="policydesc_watchLogin" product="default" msgid="5712323091846761073">"टाइप केलेल्या अयोग्य संकेतशब्दांच्या अंकांचे परीक्षण करा. स्क्रीन अनलॉक केली जाते, तेव्हा फोन लॉक करा किंवा बरेच संकेतशब्द टाइप केले असल्यास फोनचा सर्व डेटा मिटवा."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="4280246270601044505">"स्क्रीन अनलॉक करताना टाइप केलेल्या चुकीच्या संकेतशब्दांच्या संख्येचे परीक्षण करा आणि टॅबलेट लॉक करा किंवा अनेक चुकीचे संकेतशब्द टाइप केले असल्यास या वापरकर्त्याचा सर्व डेटा मिटवा."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="TV" msgid="3484832653564483250">"स्क्रीन अनलॉक करताना टाइप केलेल्या चुकीच्या संकेतशब्दांच्या संख्येचे परीक्षण करा आणि टीव्ही लॉक करा किंवा अनेक चुकीचे संकेतशब्द टाइप केले असल्यास या वापरकर्त्याचा सर्व डेटा मिटवा."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="2185480427217127147">"टाइप केलेल्या अयोग्य संकेतशब्दांच्या अंकांचे परीक्षण करा. स्क्रीन अनलॉक केली जाते, तेव्हा फोन लॉक करा किंवा बरेच संकेतशब्द टाइप केले असल्यास या वापरकर्त्याचा सर्व डेटा मिटवा."</string>
+    <string name="policydesc_watchLogin" product="tablet" msgid="3215729294215070072">"टाइप केलेल्या अयोग्य पासवर्डांच्या अंकांचे परीक्षण करा. स्क्रीन अनलॉक केली जाते, तेव्हा टॅबलेट लॉक करा किंवा बरेच पासवर्ड टाइप केले असल्यास टॅबलेटचा सर्व डेटा मिटवा."</string>
+    <string name="policydesc_watchLogin" product="TV" msgid="2707817988309890256">"स्क्रीन अनलॉक करताना टाइप केलेल्या चुकीच्या पासवर्डांच्या संख्येचे परीक्षण करा आणि टीव्ही लॉक करा किंवा अनेक चुकीचे पासवर्ड टाइप केले असल्यास टीव्हीचा सर्व डेटा मिटवा."</string>
+    <string name="policydesc_watchLogin" product="default" msgid="5712323091846761073">"टाइप केलेल्या अयोग्य पासवर्डांच्या अंकांचे परीक्षण करा. स्क्रीन अनलॉक केली जाते, तेव्हा फोन लॉक करा किंवा बरेच पासवर्ड टाइप केले असल्यास फोनचा सर्व डेटा मिटवा."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="4280246270601044505">"स्क्रीन अनलॉक करताना टाइप केलेल्या चुकीच्या पासवर्डांच्या संख्येचे परीक्षण करा आणि टॅबलेट लॉक करा किंवा अनेक चुकीचे पासवर्ड टाइप केले असल्यास या वापरकर्त्याचा सर्व डेटा मिटवा."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="TV" msgid="3484832653564483250">"स्क्रीन अनलॉक करताना टाइप केलेल्या चुकीच्या पासवर्डांच्या संख्येचे परीक्षण करा आणि टीव्ही लॉक करा किंवा अनेक चुकीचे पासवर्ड टाइप केले असल्यास या वापरकर्त्याचा सर्व डेटा मिटवा."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="2185480427217127147">"टाइप केलेल्या अयोग्य पासवर्डांच्या अंकांचे परीक्षण करा. स्क्रीन अनलॉक केली जाते, तेव्हा फोन लॉक करा किंवा बरेच पासवर्ड टाइप केले असल्यास या वापरकर्त्याचा सर्व डेटा मिटवा."</string>
     <string name="policylab_resetPassword" msgid="4934707632423915395">"स्क्रीन लॉक बदला"</string>
     <string name="policydesc_resetPassword" msgid="1278323891710619128">"स्क्रीन लॉक बदला."</string>
     <string name="policylab_forceLock" msgid="2274085384704248431">"स्क्रीन लॉक करा"</string>
@@ -573,8 +573,8 @@
     <string name="policydesc_wipeData_secondaryUser" product="default" msgid="6787904546711590238">"कोणत्याही चेतावणी शिवाय या वापरकर्त्याचा या फोनवरील डेटा मिटवा."</string>
     <string name="policylab_setGlobalProxy" msgid="2784828293747791446">"डिव्हाइस समग्र प्रॉक्सी सेट करा"</string>
     <string name="policydesc_setGlobalProxy" msgid="8459859731153370499">"धोरण सक्षम असताना वापरण्यासाठी डिव्हाइस समग्र प्रॉक्सी सेट करा. फक्त डिव्हाइस मालक समग्र प्रॉक्सी सेट करु शकतो."</string>
-    <string name="policylab_expirePassword" msgid="5610055012328825874">"स्क्रीन लॉक संकेतशब्द कालबाह्यता सेट करा"</string>
-    <string name="policydesc_expirePassword" msgid="5367525762204416046">"लॉक-स्क्रीन संकेतशब्द किती वारंवार बदलणे आवश्यक आहे ते बदला."</string>
+    <string name="policylab_expirePassword" msgid="5610055012328825874">"स्क्रीन लॉक पासवर्ड कालबाह्यता सेट करा"</string>
+    <string name="policydesc_expirePassword" msgid="5367525762204416046">"लॉक-स्क्रीन पासवर्ड किती वारंवार बदलणे आवश्यक आहे ते बदला."</string>
     <string name="policylab_encryptedStorage" msgid="8901326199909132915">"स्टोरेज एंक्रिप्शन सेट करा"</string>
     <string name="policydesc_encryptedStorage" msgid="2637732115325316992">"स्टोअर केलेला अॅप डेटा एंक्रिप्ट केला जाणे आवश्यक आहे."</string>
     <string name="policylab_disableCamera" msgid="6395301023152297826">"कॅमेरे अक्षम करा"</string>
@@ -699,8 +699,8 @@
     <string name="keyguard_password_enter_puk_code" msgid="4800725266925845333">"PUK आणि नवीन पिन कोड टाइप करा"</string>
     <string name="keyguard_password_enter_puk_prompt" msgid="1341112146710087048">"PUK कोड"</string>
     <string name="keyguard_password_enter_pin_prompt" msgid="8027680321614196258">"नवीन पिन कोड"</string>
-    <string name="keyguard_password_entry_touch_hint" msgid="2644215452200037944"><font size="17">"संकेतशब्द टाइप करण्यासाठी टॅप करा"</font></string>
-    <string name="keyguard_password_enter_password_code" msgid="1054721668279049780">"अनलॉक करण्यासाठी संकेतशब्द टाइप करा"</string>
+    <string name="keyguard_password_entry_touch_hint" msgid="2644215452200037944"><font size="17">"पासवर्ड टाइप करण्यासाठी टॅप करा"</font></string>
+    <string name="keyguard_password_enter_password_code" msgid="1054721668279049780">"अनलॉक करण्यासाठी पासवर्ड टाइप करा"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="6391755146112503443">"अनलॉक करण्यासाठी पिन टाइप करा"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"अयोग्य पिन कोड."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"अनलॉक करण्यासाठी, मेनू दाबा नंतर 0."</string>
@@ -739,7 +739,7 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"सिम कार्ड लॉक केलेले आहे."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"सिम कार्ड अनलॉक करत आहे…"</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6481623830344107222">"तुम्ही आपला अनलॉक पॅटर्न <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने काढला. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
-    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="2725973286239344555">"आपण आपला संकेतशब्द <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने टाइप केला आहे. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="2725973286239344555">"आपण आपला पासवर्ड <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने टाइप केला आहे. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
     <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6216672706545696955">"आपण आपला पिन <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने टाइप केला आहे. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="9191611984625460820">"तुम्ही आपला अनलॉक पॅटर्न <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा चुकीचा रेखांकित केला आहे. <xliff:g id="NUMBER_1">%2$d</xliff:g> अधिक अयशस्वी प्रयत्नांनंतर, तुमच्याला आपले Google साइन इन वापरून आपला टॅब्लेट अनलॉक करण्यास सांगितले जाईल.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="5316664559603394684">"तुम्ही <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा आपला अनलॉक पॅटर्न अयोग्यरीत्या काढला आहे. आणखी <xliff:g id="NUMBER_1">%2$d</xliff:g> अयशस्वी प्रयत्नांनंतर, तुमच्याला आपले Google साइन इन वापरून आपला टीव्ही अनलॉक करण्यास सांगितले जाईल.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकंदांनी पुन्हा प्रयत्न करा."</string>
@@ -756,10 +756,10 @@
     <string name="lockscreen_glogin_too_many_attempts" msgid="2751368605287288808">"बरेच पॅटर्न प्रयत्न"</string>
     <string name="lockscreen_glogin_instructions" msgid="3931816256100707784">"अनलॉक करण्यासाठी, आपल्या Google खात्यासह साइन इन करा."</string>
     <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"वापरकर्तानाव (ईमेल)"</string>
-    <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"संकेतशब्द"</string>
+    <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"पासवर्ड"</string>
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"साइन इन करा"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"अवैध वापरकर्तानाव किंवा पासवर्ड."</string>
-    <string name="lockscreen_glogin_account_recovery_hint" msgid="1696924763690379073">"आपले वापरकर्तानाव किंवा संकेतशब्द विसरलात?\n "<b>"google.com/accounts/recovery"</b>" ला भेट द्या."</string>
+    <string name="lockscreen_glogin_account_recovery_hint" msgid="1696924763690379073">"आपले वापरकर्तानाव किंवा पासवर्ड विसरलात?\n "<b>"google.com/accounts/recovery"</b>" ला भेट द्या."</string>
     <string name="lockscreen_glogin_checking_password" msgid="7114627351286933867">"तपासत आहे..."</string>
     <string name="lockscreen_unlock_label" msgid="737440483220667054">"अनलॉक करा"</string>
     <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"ध्वनी सुरु"</string>
@@ -788,7 +788,7 @@
     <string name="keyguard_accessibility_pattern_unlock" msgid="1490840706075246612">"पॅटर्न अनलॉक."</string>
     <string name="keyguard_accessibility_face_unlock" msgid="4817282543351718535">"चेहरा अनलॉक."</string>
     <string name="keyguard_accessibility_pin_unlock" msgid="2469687111784035046">"पिन अनलॉक."</string>
-    <string name="keyguard_accessibility_password_unlock" msgid="7675777623912155089">"संकेतशब्द अनलॉक."</string>
+    <string name="keyguard_accessibility_password_unlock" msgid="7675777623912155089">"पासवर्ड अनलॉक."</string>
     <string name="keyguard_accessibility_pattern_area" msgid="7679891324509597904">"पॅटर्न क्षेत्र."</string>
     <string name="keyguard_accessibility_slide_area" msgid="6736064494019979544">"स्लाइड क्षेत्र."</string>
     <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
@@ -840,7 +840,7 @@
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"आपल्या व्हॉइसमेल इनबॉक्समध्ये संदेश जोडण्यासाठी अॅप ला अनुमती देते."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ब्राउझर भौगोलिक स्थान परवानग्या सुधारित करा"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ब्राउझरच्या भौगोलिक स्थान परवानग्या सुधारित करण्यासाठी अॅप ला अनुमती देते. दुर्भावनापूर्ण अॅप्स यादृच्छिक वेबसाइटवर स्थान माहिती पाठविण्यास अनुमती देण्यासाठी याचा वापर करू शकतात."</string>
-    <string name="save_password_message" msgid="767344687139195790">"ब्राउझरने हा संकेतशब्द लक्षात ठेवावा असे आपण इच्छिता?"</string>
+    <string name="save_password_message" msgid="767344687139195790">"ब्राउझरने हा पासवर्ड लक्षात ठेवावा असे आपण इच्छिता?"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"आत्ता नाही"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"लक्षात ठेवा"</string>
     <string name="save_password_never" msgid="8274330296785855105">"कधीही नाही"</string>
@@ -994,7 +994,7 @@
     <string name="yes" msgid="5362982303337969312">"ठीक"</string>
     <string name="no" msgid="5141531044935541497">"रद्द करा"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"लक्ष द्या"</string>
-    <string name="loading" msgid="7933681260296021180">"लोड करीत आहे..."</string>
+    <string name="loading" msgid="7933681260296021180">"लोड करत आहे..."</string>
     <string name="capital_on" msgid="1544682755514494298">"चालू"</string>
     <string name="capital_off" msgid="6815870386972805832">"बंद"</string>
     <string name="whichApplication" msgid="4533185947064773386">"याचा वापर करून क्रिया पूर्ण करा"</string>
@@ -1058,9 +1058,9 @@
     <string name="android_upgrading_fstrim" msgid="8036718871534640010">"संचयन ऑप्टिमाइझ करत आहे."</string>
     <string name="android_upgrading_notification_title" msgid="8428357096969413169">"Android अपडेट संपवत आहे..."</string>
     <string name="android_upgrading_notification_body" msgid="5761201379457064286">"श्रेणीसुधारणा पूर्ण होईपर्यंत काही अॅप्स योग्यरित्या कार्य करणार नाहीत"</string>
-    <string name="app_upgrading_toast" msgid="3008139776215597053">"<xliff:g id="APPLICATION">%1$s</xliff:g> श्रेणीसुधारित करीत आहे…"</string>
+    <string name="app_upgrading_toast" msgid="3008139776215597053">"<xliff:g id="APPLICATION">%1$s</xliff:g> श्रेणीसुधारित करत आहे…"</string>
     <string name="android_upgrading_apk" msgid="7904042682111526169">"<xliff:g id="NUMBER_1">%2$d</xliff:g> पैकी <xliff:g id="NUMBER_0">%1$d</xliff:g> अॅप ऑप्टिमाइझ करत आहे."</string>
-    <string name="android_preparing_apk" msgid="8162599310274079154">"<xliff:g id="APPNAME">%1$s</xliff:g> तयार करीत आहे."</string>
+    <string name="android_preparing_apk" msgid="8162599310274079154">"<xliff:g id="APPNAME">%1$s</xliff:g> तयार करत आहे."</string>
     <string name="android_upgrading_starting_apps" msgid="451464516346926713">"अॅप्स प्रारंभ करत आहे."</string>
     <string name="android_upgrading_complete" msgid="1405954754112999229">"बूट समाप्त होत आहे."</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> चालत आहे"</string>
@@ -1074,7 +1074,7 @@
     <string name="dump_heap_notification" msgid="2618183274836056542">"<xliff:g id="PROC">%1$s</xliff:g> ने मेमेरी मर्यादा वाढविली"</string>
     <string name="dump_heap_notification_detail" msgid="6901391084243999274">"हीप डंप संकलित केला गेला आहे; सामायिक करण्यासाठी टॅप करा"</string>
     <string name="dump_heap_title" msgid="5864292264307651673">"हीप डंप सामायिक करायचे?"</string>
-    <string name="dump_heap_text" msgid="4809417337240334941">"<xliff:g id="PROC">%1$s</xliff:g> प्रक्रियेने त्याची <xliff:g id="SIZE">%2$s</xliff:g> ची प्रक्रिया मेमरी मर्यादा ओलांडली आहे. त्याच्या विकासकासह सामायिक करण्यासाठी आपल्याकरिता हीप डंप उपलब्ध आहे. सावधगिरी बाळगा: या हीप डंपमध्ये आपली कोणतीही वैयक्तिक माहिती असू शकते ज्यात अॅप्लिकेशन प्रवेश करू शकतो."</string>
+    <string name="dump_heap_text" msgid="4809417337240334941">"<xliff:g id="PROC">%1$s</xliff:g> प्रक्रियेने त्याची <xliff:g id="SIZE">%2$s</xliff:g> ची प्रक्रिया मेमरी मर्यादा ओलांडली आहे. त्याच्या विकासकासह सामायिक करण्यासाठी तुमच्यासाठी हीप डंप उपलब्ध आहे. सावधगिरी बाळगा: या हीप डंपमध्ये आपली कोणतीही वैयक्तिक माहिती असू शकते ज्यात अॅप्लिकेशन प्रवेश करू शकतो."</string>
     <string name="sendText" msgid="5209874571959469142">"मजकुरासाठी क्रिया निवडा"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"रिंगर व्हॉल्यूम"</string>
     <string name="volume_music" msgid="5421651157138628171">"मीडिया व्हॉल्यूम"</string>
@@ -1185,7 +1185,7 @@
     <string name="perm_costs_money" msgid="4902470324142151116">"यासाठी आपले पैसे खर्च होऊ शकतात"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"ठीक"</string>
     <string name="usb_charging_notification_title" msgid="6895185153353640787">"USB हे डिव्हाइस चार्ज करत आहे"</string>
-    <string name="usb_supplying_notification_title" msgid="5310642257296510271">"USB संलग्न केलेल्या डिव्हाइसला पॉवरचा पुरवठा करीत आहे"</string>
+    <string name="usb_supplying_notification_title" msgid="5310642257296510271">"USB संलग्न केलेल्या डिव्हाइसला पॉवरचा पुरवठा करत आहे"</string>
     <string name="usb_mtp_notification_title" msgid="8396264943589760855">"स्थानांतरणासाठी USB"</string>
     <string name="usb_ptp_notification_title" msgid="1347328437083192112">"फोटो स्थानांतरणासाठी USB"</string>
     <string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI साठी USB"</string>
@@ -1210,11 +1210,11 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="alert_windows_notification_channel_group_name" msgid="1463953341148606396">"इतर अ‍ॅप्सवर दाखवा"</string>
-    <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> इतर अॅप्सवर प्रदर्शित करीत आहे"</string>
-    <string name="alert_windows_notification_title" msgid="3697657294867638947">"<xliff:g id="NAME">%s</xliff:g> अन्‍य अॅप्सवर प्रदर्शित करीत आहे"</string>
+    <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> इतर अॅप्सवर प्रदर्शित करत आहे"</string>
+    <string name="alert_windows_notification_title" msgid="3697657294867638947">"<xliff:g id="NAME">%s</xliff:g> अन्‍य अॅप्सवर प्रदर्शित करत आहे"</string>
     <string name="alert_windows_notification_message" msgid="8917232109522912560">"<xliff:g id="NAME">%s</xliff:g> ने हे वैशिष्ट्य वापरू नये असे आपण इच्छित असल्यास, सेटिंग्ज उघडण्यासाठी टॅप करा आणि ते बंद करा."</string>
     <string name="alert_windows_notification_turn_off_action" msgid="3367294525884949878">"बंद करा"</string>
-    <string name="ext_media_checking_notification_title" msgid="5734005953288045806">"<xliff:g id="NAME">%s</xliff:g> तयार करीत आहे"</string>
+    <string name="ext_media_checking_notification_title" msgid="5734005953288045806">"<xliff:g id="NAME">%s</xliff:g> तयार करत आहे"</string>
     <string name="ext_media_checking_notification_message" msgid="4747432538578886744">"त्रुटींसाठी तपासत आहे"</string>
     <string name="ext_media_new_notification_message" msgid="7589986898808506239">"नवीन <xliff:g id="NAME">%s</xliff:g> आढळले"</string>
     <string name="ext_media_ready_notification_message" msgid="4083398150380114462">"फोटो आणि मीडिया स्थानांतरित करण्‍यासाठी"</string>
@@ -1250,7 +1250,7 @@
     <string name="ext_media_status_unmountable" msgid="805594039236667894">"दूषित झाले"</string>
     <string name="ext_media_status_unsupported" msgid="4691436711745681828">"समर्थित नसलेले"</string>
     <string name="ext_media_status_ejecting" msgid="5463887263101234174">"बाहेर काढत आहे…"</string>
-    <string name="ext_media_status_formatting" msgid="1085079556538644861">"फॉर्मेट करीत आहे..."</string>
+    <string name="ext_media_status_formatting" msgid="1085079556538644861">"फॉर्मेट करत आहे..."</string>
     <string name="ext_media_status_missing" msgid="5638633895221670766">"घातले नाही"</string>
     <string name="activity_list_empty" msgid="1675388330786841066">"कोणत्याही जुळणाऱ्या अॅक्टिव्हिटी आढळल्या नाहीत."</string>
     <string name="permlab_route_media_output" msgid="6243022988998972085">"मीडिया आउटपुट मार्गस्थ करा"</string>
@@ -1365,7 +1365,7 @@
     <string name="keyboardview_keycode_done" msgid="1992571118466679775">"पूर्ण झाले"</string>
     <string name="keyboardview_keycode_mode_change" msgid="4547387741906537519">"मोड बदल"</string>
     <string name="keyboardview_keycode_shift" msgid="2270748814315147690">"Shift"</string>
-    <string name="keyboardview_keycode_enter" msgid="2985864015076059467">"प्रविष्ट करा"</string>
+    <string name="keyboardview_keycode_enter" msgid="2985864015076059467">"एंटर करा"</string>
     <string name="activitychooserview_choose_application" msgid="2125168057199941199">"एक अ‍ॅप निवडा"</string>
     <string name="activitychooserview_choose_application_error" msgid="8624618365481126668">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> लाँच करू शकलो नाही"</string>
     <string name="shareactionprovider_share_with" msgid="806688056141131819">"यांच्यासह सामायिक करा"</string>
@@ -1438,7 +1438,7 @@
     <string name="media_route_chooser_extended_settings" msgid="87015534236701604">"सेटिंग्ज"</string>
     <string name="media_route_controller_disconnect" msgid="8966120286374158649">"‍डिस्कनेक्ट करा"</string>
     <string name="media_route_status_scanning" msgid="7279908761758293783">"स्कॅन करत आहे..."</string>
-    <string name="media_route_status_connecting" msgid="6422571716007825440">"कनेक्ट करीत आहे..."</string>
+    <string name="media_route_status_connecting" msgid="6422571716007825440">"कनेक्ट करत आहे..."</string>
     <string name="media_route_status_available" msgid="6983258067194649391">"उपलब्ध"</string>
     <string name="media_route_status_not_available" msgid="6739899962681886401">"उपलब्ध नाही"</string>
     <string name="media_route_status_in_use" msgid="4533786031090198063">"वापरात आहे"</string>
@@ -1449,18 +1449,18 @@
     <string name="display_manager_overlay_display_secure_suffix" msgid="6022119702628572080">", सुरक्षित"</string>
     <string name="kg_forgot_pattern_button_text" msgid="8852021467868220608">"पॅटर्न विसरलात"</string>
     <string name="kg_wrong_pattern" msgid="1850806070801358830">"चुकीचा पॅटर्न"</string>
-    <string name="kg_wrong_password" msgid="2333281762128113157">"चुकीचा संकेतशब्द"</string>
+    <string name="kg_wrong_password" msgid="2333281762128113157">"चुकीचा पासवर्ड"</string>
     <string name="kg_wrong_pin" msgid="1131306510833563801">"चुकीचा पिन"</string>
     <plurals name="kg_too_many_failed_attempts_countdown" formatted="false" msgid="8790651267324125694">
       <item quantity="one"><xliff:g id="NUMBER">%d</xliff:g> सेकंदात पुन्‍हा प्रयत्न करा.</item>
       <item quantity="other"><xliff:g id="NUMBER">%d</xliff:g> सेकंदांत पुन्‍हा प्रयत्न करा.</item>
     </plurals>
     <string name="kg_pattern_instructions" msgid="398978611683075868">"तुमचा पॅटर्न काढा"</string>
-    <string name="kg_sim_pin_instructions" msgid="2319508550934557331">"सिम पिन प्रविष्ट करा"</string>
-    <string name="kg_pin_instructions" msgid="2377242233495111557">"पिन प्रविष्ट करा"</string>
-    <string name="kg_password_instructions" msgid="5753646556186936819">"संकेतशब्द प्रविष्ट करा"</string>
-    <string name="kg_puk_enter_puk_hint" msgid="453227143861735537">"सिम आता अक्षम केले आहे. सुरु ठेवण्यासाठी PUK कोड प्रविष्ट करा. तपशीलांसाठी वाहकाशी संपर्क साधा."</string>
-    <string name="kg_puk_enter_pin_hint" msgid="7871604527429602024">"इच्छित पिन कोड प्रविष्ट करा"</string>
+    <string name="kg_sim_pin_instructions" msgid="2319508550934557331">"सिम पिन एंटर करा"</string>
+    <string name="kg_pin_instructions" msgid="2377242233495111557">"पिन एंटर करा"</string>
+    <string name="kg_password_instructions" msgid="5753646556186936819">"पासवर्ड एंटर करा"</string>
+    <string name="kg_puk_enter_puk_hint" msgid="453227143861735537">"सिम आता अक्षम केले आहे. सुरु ठेवण्यासाठी PUK कोड एंटर करा. तपशीलांसाठी वाहकाशी संपर्क साधा."</string>
+    <string name="kg_puk_enter_pin_hint" msgid="7871604527429602024">"इच्छित पिन कोड एंटर करा"</string>
     <string name="kg_enter_confirm_pin_hint" msgid="325676184762529976">"इच्छित पिन कोड ची पुष्टी करा"</string>
     <string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"सिम कार्ड अनलॉक करत आहे…"</string>
     <string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"अयोग्य पिन कोड."</string>
@@ -1471,13 +1471,13 @@
     <string name="kg_login_too_many_attempts" msgid="6486842094005698475">"बरेच पॅटर्न प्रयत्न"</string>
     <string name="kg_login_instructions" msgid="1100551261265506448">"अनलॉक करण्यासाठी, आपल्या Google खात्यासह साइन इन करा."</string>
     <string name="kg_login_username_hint" msgid="5718534272070920364">"वापरकर्तानाव (ईमेल)"</string>
-    <string name="kg_login_password_hint" msgid="9057289103827298549">"संकेतशब्द"</string>
+    <string name="kg_login_password_hint" msgid="9057289103827298549">"पासवर्ड"</string>
     <string name="kg_login_submit_button" msgid="5355904582674054702">"साइन इन करा"</string>
     <string name="kg_login_invalid_input" msgid="5754664119319872197">"अवैध वापरकर्तानाव किंवा पासवर्ड."</string>
-    <string name="kg_login_account_recovery_hint" msgid="5690709132841752974">"आपले वापरकर्तानाव किंवा संकेतशब्द विसरलात?\n "<b>"google.com/accounts/recovery"</b>" ला भेट द्या."</string>
+    <string name="kg_login_account_recovery_hint" msgid="5690709132841752974">"आपले वापरकर्तानाव किंवा पासवर्ड विसरलात?\n "<b>"google.com/accounts/recovery"</b>" ला भेट द्या."</string>
     <string name="kg_login_checking_password" msgid="1052685197710252395">"खाते तपासत आहे…"</string>
     <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"आपण आपला पिन <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने टाइप केला आहे. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
-    <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"आपण आपला संकेतशब्द <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने टाइप केला आहे. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
+    <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"आपण आपला पासवर्ड <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने टाइप केला आहे. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
     <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"तुम्ही आपला अनलॉक पॅटर्न <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने काढला. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="1575557200627128949">"आपण <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा टॅबलेट अनलॉक करण्याचा अयोग्यपणे प्रयत्न केला. <xliff:g id="NUMBER_1">%2$d</xliff:g> आणखी अयशस्वी प्रयत्नांनंतर, टॅबलेट फॅक्टरी डीफॉल्टवर रीसेट केला जाईल आणि वापरकर्ता डेटा गमावेल."</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="5621231220154419413">"आपण <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा टीव्ही अनलॉक करण्याचा अयोग्यरित्या प्रयत्न केला. आणखी <xliff:g id="NUMBER_1">%2$d</xliff:g> अयशस्वी प्रयत्नांनंतर, टीव्ही फॅक्टरी डीफॉल्टवर रीसेट केला जाईल आणि सर्व वापरकर्ता डेटा गमावेल."</string>
@@ -1502,7 +1502,7 @@
     <string name="accessibility_magnification_chooser_text" msgid="1227146738764986237">"मोठे करणे"</string>
     <string name="user_switched" msgid="3768006783166984410">"वर्तमान वापरकर्ता <xliff:g id="NAME">%1$s</xliff:g>."</string>
     <string name="user_switching_message" msgid="2871009331809089783">"<xliff:g id="NAME">%1$s</xliff:g> वर स्विच करत आहे…"</string>
-    <string name="user_logging_out_message" msgid="8939524935808875155">"<xliff:g id="NAME">%1$s</xliff:g> लॉग आउट करीत आहे…"</string>
+    <string name="user_logging_out_message" msgid="8939524935808875155">"<xliff:g id="NAME">%1$s</xliff:g> लॉग आउट करत आहे…"</string>
     <string name="owner_name" msgid="2716755460376028154">"मालक"</string>
     <string name="error_message_title" msgid="4510373083082500195">"एरर"</string>
     <string name="error_message_change_not_allowed" msgid="1238035947357923497">"या बदलास आपल्या प्रशासकाद्वारे अनुमती नाही"</string>
@@ -1598,7 +1598,7 @@
     <string name="print_service_installed_title" msgid="2246317169444081628">"<xliff:g id="NAME">%s</xliff:g> सेवा स्‍थापित केली"</string>
     <string name="print_service_installed_message" msgid="5897362931070459152">"सक्षम करण्यासाठी टॅप करा"</string>
     <string name="restr_pin_enter_admin_pin" msgid="8641662909467236832">"प्रशासक पिन एंटर करा"</string>
-    <string name="restr_pin_enter_pin" msgid="3395953421368476103">"पिन प्रविष्ट करा"</string>
+    <string name="restr_pin_enter_pin" msgid="3395953421368476103">"पिन एंटर करा"</string>
     <string name="restr_pin_incorrect" msgid="8571512003955077924">"चुकीचा"</string>
     <string name="restr_pin_enter_old_pin" msgid="1462206225512910757">"वर्तमान पिन"</string>
     <string name="restr_pin_enter_new_pin" msgid="5959606691619959184">"नवीन पिन"</string>
@@ -1626,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2 रे कार्य <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3 रे कार्य <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"हा स्क्रीन अनपिन करण्यासाठी, मागे आणि अवलोकन बटणांना स्पर्श करून धरून ठेवा"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"हे अ‍ॅप अनपिन केले जाऊ शकत नाही"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"स्क्रीन पिन केली"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"स्क्रीन अनपिन केली"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"अनपिन करण्‍यापूर्वी पिन साठी विचारा"</string>
@@ -1771,7 +1770,7 @@
     <string name="autofill_save_title_with_3types" msgid="6943161834231458441">"&lt;b&gt;<xliff:g id="LABEL">%4$s</xliff:g>&lt;/b&gt;मध्ये <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> आणि <xliff:g id="TYPE_2">%3$s</xliff:g> सेव्ह करायची?"</string>
     <string name="autofill_save_yes" msgid="6398026094049005921">"सेव्ह करा"</string>
     <string name="autofill_save_no" msgid="2625132258725581787">"नाही, नको"</string>
-    <string name="autofill_save_type_password" msgid="5288448918465971568">"संकेतशब्द"</string>
+    <string name="autofill_save_type_password" msgid="5288448918465971568">"पासवर्ड"</string>
     <string name="autofill_save_type_address" msgid="4936707762193009542">"पत्ता"</string>
     <string name="autofill_save_type_credit_card" msgid="7127694776265563071">"क्रेडिट कार्ड"</string>
     <string name="autofill_save_type_username" msgid="239040540379769562">"वापरकर्तानाव"</string>
@@ -1782,14 +1781,10 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"आणीबाणी संदेश चाचणी"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"प्रत्युत्तर द्या"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
-    <skip />
-    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
-    <skip />
-    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
-    <skip />
-    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
-    <skip />
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"व्‍हॉइसची सिमला अनुमती नाही"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"सिममध्‍ये व्‍हॉइसची तरतूद नाही"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"व्‍हॉइसची सिमला अनुमती नाही"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"व्‍हॉइसची फोनला अनुमती नाही"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"पॉपअप विंडो"</string>
     <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
     <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"या शॉर्टकटला नवीनतम अॅपची आवश्यकता आहे"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 429ecf1..bf0f46a 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -282,7 +282,7 @@
     <string name="permgrouprequest_camera" msgid="810824326507258410">"படங்களை எடுக்கவும் வீடியோவைப் பதிவுசெய்யவும், &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;க்கு அணுகல் வழங்கவும்"</string>
     <string name="permgrouplab_phone" msgid="5229115638567440675">"ஃபோன்"</string>
     <string name="permgroupdesc_phone" msgid="6234224354060641055">"யாரையும் தொலைபேசியில் அழைக்கலாம்"</string>
-    <string name="permgrouprequest_phone" msgid="7084161459732093690">"மொபைல் அழைப்புகளைச் செய்யவும், அவற்றை நிர்வகிக்கவும், &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;க்கு அனுமதி வழங்கவும்"</string>
+    <string name="permgrouprequest_phone" msgid="7084161459732093690">"மொபைல் அழைப்புகளைச் செய்யவும், அவற்றை நிர்வகிக்கவும், &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;க்கு அனுமதி வழங்கவா"</string>
     <string name="permgrouplab_sensors" msgid="416037179223226722">"உடல் சென்சார்கள்"</string>
     <string name="permgroupdesc_sensors" msgid="7147968539346634043">"உங்கள் உடல் இயக்கம் பற்றி உணர்விகள் கூறும் தகவலைப் பார்க்கலாம்"</string>
     <string name="permgrouprequest_sensors" msgid="8631146669524259656">"உடலியக்கக் குறிகள் பற்றிய உணர்வித் தரவை அணுக, &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;க்கு அனுமதி வழங்கவும்"</string>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 947fcf1..946216c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -605,6 +605,8 @@
     <!-- The size of the right icon image when on low ram -->
     <dimen name="notification_right_icon_size_low_ram">40dp</dimen>
 
+    <dimen name="messaging_avatar_size">24dp</dimen>
+
     <!-- Max width/height of the autofill data set picker as a fraction of the screen width/height -->
     <dimen name="autofill_dataset_picker_max_size">90%</dimen>
 
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index cd3624d..b40117e 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -139,6 +139,27 @@
   <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW}. -->
   <item type="id" name="accessibilityActionMoveWindow" />
 
+  <!-- A tag used to save an animator in local y translation -->
+  <item type="id" name="tag_local_translation_y_animator" />
+
+  <!-- A tag used to save the local translation y -->
+  <item type="id" name="tag_local_translation_y" />
+
+  <!-- A tag used to save the original top of a view -->
+  <item type="id" name="tag_layout_top" />
+
+  <!-- A tag used to save an animator in alpha -->
+  <item type="id" name="tag_alpha_animator" />
+
+  <!-- A tag used to save the clip children in a tag -->
+  <item type="id" name="clip_children_tag" />
+
+  <!-- A tag used to save the set of deactivated children that clip -->
+  <item type="id" name="clip_children_set_tag" />
+
+  <!-- A tag used to save the clip to padding in a tag -->
+  <item type="id" name="clip_to_padding_tag" />
+
   <!-- Action used to manually trigger an autofill request -->
   <item type="id" name="autofill" />
 
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index cddf99a..2cd4dcb 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -505,8 +505,17 @@
         <item name="layout_width">match_parent</item>
         <item name="layout_height">wrap_content</item>
         <item name="ellipsize">end</item>
-        <item name="visibility">gone</item>
         <item name="textAppearance">@style/TextAppearance.Material.Notification</item>
+        <item name="background">@drawable/messaging_message_background</item>
+    </style>
+
+    <style name="Widget.Material.Notification.MessagingName" parent="Widget.Material.Light.TextView">
+        <item name="layout_width">wrap_content</item>
+        <item name="layout_height">wrap_content</item>
+        <item name="ellipsize">end</item>
+        <item name="textAppearance">@style/TextAppearance.Material.Notification</item>
+        <item name="textColor">@color/notification_primary_text_color_light</item>
+        <item name="textSize">12sp</item>
     </style>
 
     <!-- Widget Styles -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 32758e8..fd0012d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3105,6 +3105,22 @@
   <java-symbol type="dimen" name="chooser_service_spacing" />
   <java-symbol type="bool" name="config_showSysuiShutdown" />
 
+  <java-symbol type="layout" name="notification_template_messaging_message" />
+  <java-symbol type="layout" name="notification_template_messaging_group" />
+  <java-symbol type="id" name="message_text" />
+  <java-symbol type="id" name="message_name" />
+  <java-symbol type="id" name="message_icon" />
+  <java-symbol type="id" name="group_message_container" />
+  <java-symbol type="id" name="tag_local_translation_y_animator" />
+  <java-symbol type="id" name="tag_local_translation_y" />
+  <java-symbol type="id" name="tag_layout_top" />
+  <java-symbol type="id" name="tag_alpha_animator" />
+  <java-symbol type="id" name="clip_children_set_tag" />
+  <java-symbol type="id" name="clip_to_padding_tag" />
+  <java-symbol type="id" name="clip_children_tag" />
+  <java-symbol type="drawable" name="ic_reply_notification_large" />
+  <java-symbol type="dimen" name="messaging_avatar_size" />
+
   <java-symbol type="integer" name="config_stableDeviceDisplayWidth" />
   <java-symbol type="integer" name="config_stableDeviceDisplayHeight" />
   <java-symbol type="bool" name="config_display_no_service_when_sim_unready" />
diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java
index 6fa28b1..27b7f9e 100644
--- a/core/tests/coretests/src/android/net/UriTest.java
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -187,6 +187,11 @@
         uri = Uri.parse("http://localhost");
         assertEquals("localhost", uri.getHost());
         assertEquals(-1, uri.getPort());
+
+        uri = Uri.parse("http://a:a@example.com:a@example2.com/path");
+        assertEquals("a:a@example.com:a@example2.com", uri.getAuthority());
+        assertEquals("example2.com", uri.getHost());
+        assertEquals(-1, uri.getPort());
     }
 
     @SmallTest
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index 2a6c22e..41686fa 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -18,6 +18,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 
 import android.os.LocaleList;
@@ -32,6 +34,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Collection;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TextClassificationManagerTest {
@@ -174,6 +178,23 @@
     }
 
     @Test
+    public void testGenerateLinks() {
+        if (isTextClassifierDisabled()) return;
+
+        checkGenerateLinksFindsLink(
+                "The number is +12122537077. See you tonight!",
+                "+12122537077",
+                TextClassifier.TYPE_PHONE);
+
+        checkGenerateLinksFindsLink(
+                "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you tonight!",
+                "1600 Amphitheater Parkway, Mountain View, CA",
+                TextClassifier.TYPE_ADDRESS);
+
+        // TODO: Add more entity types when the model supports them.
+    }
+
+    @Test
     public void testSetTextClassifier() {
         TextClassifier classifier = mock(TextClassifier.class);
         mTcm.setTextClassifier(classifier);
@@ -184,6 +205,24 @@
         return mClassifier == TextClassifier.NO_OP;
     }
 
+    private void checkGenerateLinksFindsLink(String text, String classifiedText, String type) {
+        assertTrue(text.contains(classifiedText));
+        int startIndex = text.indexOf(classifiedText);
+        int endIndex = startIndex + classifiedText.length();
+
+        Collection<TextLinks.TextLink> links = mClassifier.generateLinks(text, null).getLinks();
+        for (TextLinks.TextLink link : links) {
+            if (text.subSequence(link.getStart(), link.getEnd()).equals(classifiedText)) {
+                assertEquals(type, link.getEntity(0));
+                assertEquals(startIndex, link.getStart());
+                assertEquals(endIndex, link.getEnd());
+                assertTrue(link.getConfidenceScore(type) > .9);
+                return;
+            }
+        }
+        fail(); // Subsequence was not identified.
+    }
+
     private static Matcher<TextSelection> isTextSelection(
             final int startIndex, final int endIndex, final String type) {
         return new BaseMatcher<TextSelection>() {
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index bbdbdb1..0245570 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -49,6 +49,11 @@
 
 import static org.hamcrest.Matchers.anyOf;
 import static org.hamcrest.Matchers.is;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.Activity;
 import android.app.Instrumentation;
@@ -633,15 +638,21 @@
 
     @Test
     public void testSetSelectionAndActionMode() throws Throwable {
+        final TextView textView = mActivity.findViewById(R.id.textview);
+        final ActionMode.Callback amCallback = mock(ActionMode.Callback.class);
+        when(amCallback.onCreateActionMode(any(ActionMode.class), any(Menu.class)))
+                .thenReturn(true);
+        when(amCallback.onPrepareActionMode(any(ActionMode.class), any(Menu.class)))
+                .thenReturn(true);
+        textView.setCustomSelectionActionModeCallback(amCallback);
+
         final String text = "abc def";
         onView(withId(R.id.textview)).perform(replaceText(text));
-
-        final TextView textView = mActivity.findViewById(R.id.textview);
         mActivityRule.runOnUiThread(
                 () -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
         mInstrumentation.waitForIdleSync();
         // Don't automatically start action mode.
-        // TODO: Implement assertActionModeNotStarted()
+        verify(amCallback, never()).onCreateActionMode(any(ActionMode.class), any(Menu.class));
         // Make sure that "Select All" is included in the selection action mode when the entire text
         // is not selected.
         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
diff --git a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
index dfe8511..3919fdd 100644
--- a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
@@ -23,11 +23,11 @@
 import android.content.Context;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
+import android.text.Layout;
 import android.view.LayoutInflater;
 import android.view.View.MeasureSpec;
 
 import com.android.frameworks.coretests.R;
-import com.google.common.base.Function;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -52,33 +52,28 @@
 
     @Test
     public void testSingleChild() {
-        FakeImageFloatingTextView child = fakeChild((i) -> 3);
+        FakeImageFloatingTextView child = fakeChild(3);
 
-        mView.setNumIndentLines(2);
         mView.addView(child);
 
         mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
         mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
 
-        assertEquals(3, child.getNumIndentLines());
         assertFalse(child.isHidden());
         assertEquals(150, mView.getMeasuredHeight());
     }
 
     @Test
     public void testLargeSmall() {
-        FakeImageFloatingTextView child1 = fakeChild((i) -> 3);
-        FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+        FakeImageFloatingTextView child1 = fakeChild(3);
+        FakeImageFloatingTextView child2 = fakeChild(1);
 
-        mView.setNumIndentLines(2);
         mView.addView(child1);
         mView.addView(child2);
 
         mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
         mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
 
-        assertEquals(3, child1.getNumIndentLines());
-        assertEquals(0, child2.getNumIndentLines());
         assertFalse("child1 should not be hidden", child1.isHidden());
         assertFalse("child2 should not be hidden", child2.isHidden());
         assertEquals(205, mView.getMeasuredHeight());
@@ -86,18 +81,15 @@
 
     @Test
     public void testSmallSmall() {
-        FakeImageFloatingTextView child1 = fakeChild((i) -> 1);
-        FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+        FakeImageFloatingTextView child1 = fakeChild(1);
+        FakeImageFloatingTextView child2 = fakeChild(1);
 
-        mView.setNumIndentLines(2);
         mView.addView(child1);
         mView.addView(child2);
 
         mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
         mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
 
-        assertEquals(2, child1.getNumIndentLines());
-        assertEquals(1, child2.getNumIndentLines());
         assertFalse("child1 should not be hidden", child1.isHidden());
         assertFalse("child2 should not be hidden", child2.isHidden());
         assertEquals(105, mView.getMeasuredHeight());
@@ -105,17 +97,15 @@
 
     @Test
     public void testLargeLarge() {
-        FakeImageFloatingTextView child1 = fakeChild((i) -> 7);
-        FakeImageFloatingTextView child2 = fakeChild((i) -> 7);
+        FakeImageFloatingTextView child1 = fakeChild(7);
+        FakeImageFloatingTextView child2 = fakeChild(7);
 
-        mView.setNumIndentLines(2);
         mView.addView(child1);
         mView.addView(child2);
 
         mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
         mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
 
-        assertEquals(3, child2.getNumIndentLines());
         assertTrue("child1 should be hidden", child1.isHidden());
         assertFalse("child2 should not be hidden", child2.isHidden());
         assertEquals(350, mView.getMeasuredHeight());
@@ -123,10 +113,9 @@
 
     @Test
     public void testLargeSmall_largeWrapsWith3indentbutNotFullHeight_andHitsMax() {
-        FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 7 : 6);
-        FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+        FakeImageFloatingTextView child1 = fakeChild(7);
+        FakeImageFloatingTextView child2 = fakeChild(1);
 
-        mView.setNumIndentLines(2);
         mView.addView(child1);
         mView.addView(child2);
 
@@ -135,51 +124,18 @@
 
         assertFalse("child1 should not be hidden", child1.isHidden());
         assertFalse("child2 should not be hidden", child2.isHidden());
-        assertEquals(355, mView.getMeasuredHeight());
-        assertEquals(3, child1.getNumIndentLines());
-        assertEquals(0, child2.getNumIndentLines());
+        assertEquals(355, mView.getMeasuredHeight());;
     }
 
-    @Test
-    public void testLargeSmall_largeWrapsWith3indentbutnot3() {
-        FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 4 : 3);
-        FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
-
-        mView.setNumIndentLines(2);
-        mView.addView(child1);
-        mView.addView(child2);
-
-        mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
-        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
-
-        assertFalse("child1 should not be hidden", child1.isHidden());
-        assertFalse("child2 should not be hidden", child2.isHidden());
-        assertEquals(255, mView.getMeasuredHeight());
-        assertEquals(3, child1.getNumIndentLines());
-        assertEquals(0, child2.getNumIndentLines());
-    }
-
-    private class FakeImageFloatingTextView extends ImageFloatingTextView {
+    private class FakeImageFloatingTextView extends MessagingMessage {
 
         public static final int LINE_HEIGHT = 50;
-        private final Function<Integer, Integer> mLinesForIndent;
-        private int mNumIndentLines;
+        private final int mNumLines;
 
         public FakeImageFloatingTextView(Context context,
-                Function<Integer, Integer> linesForIndent) {
+                int linesForIndent) {
             super(context, null, 0, 0);
-            mLinesForIndent = linesForIndent;
-        }
-
-        @Override
-        public boolean setNumIndentLines(int lines) {
-            boolean changed = (mNumIndentLines != lines);
-            mNumIndentLines = lines;
-            return changed;
-        }
-
-        public int getNumIndentLines() {
-            return mNumIndentLines;
+            mNumLines = linesForIndent;
         }
 
         @Override
@@ -195,6 +151,20 @@
                             heightMeasureSpec)));
         }
 
+        public int getMeasuredType() {
+            boolean measuredTooSmall = getMeasuredHeight()
+                    < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+            if (measuredTooSmall) {
+                return MEASURED_TOO_SMALL;
+            } else {
+                if (getMeasuredHeight() == getDesiredHeight()) {
+                    return MEASURED_NORMAL;
+                } else {
+                    return MEASURED_SHORTENED;
+                }
+            }
+        }
+
         private int clampToMultiplesOfLineHeight(int size) {
             if (size <= LINE_HEIGHT) {
                 return size;
@@ -204,7 +174,7 @@
 
         @Override
         public int getLineCount() {
-            return mLinesForIndent.apply(mNumIndentLines);
+            return mNumLines;
         }
 
         public int getDesiredHeight() {
@@ -229,7 +199,7 @@
         }
     }
 
-    private FakeImageFloatingTextView fakeChild(Function<Integer,Integer> linesForIndent) {
-        return new FakeImageFloatingTextView(mContext, linesForIndent);
+    private FakeImageFloatingTextView fakeChild(int numLines) {
+        return new FakeImageFloatingTextView(mContext, numLines);
     }
 }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 82aa803..f7e0b46 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -328,6 +328,7 @@
     <privapp-permissions package="com.android.systemui">
         <permission name="android.permission.BATTERY_STATS"/>
         <permission name="android.permission.BIND_APPWIDGET"/>
+        <permission name="android.permission.BIND_SLICE" />
         <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
         <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 1a06a56..317144a 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -88,7 +88,7 @@
      * A map from a string representation of the LocaleList to Minikin's language list ID.
      */
     @GuardedBy("sCacheLock")
-    private static final HashMap<String, Integer> sMinikinLangListIdCache = new HashMap<>();
+    private static final HashMap<String, Integer> sMinikinLocaleListIdCache = new HashMap<>();
 
     /**
      * @hide
@@ -1445,16 +1445,16 @@
 
     private void syncTextLocalesWithMinikin() {
         final String languageTags = mLocales.toLanguageTags();
-        final Integer minikinLangListId;
+        final Integer minikinLocaleListId;
         synchronized (sCacheLock) {
-            minikinLangListId = sMinikinLangListIdCache.get(languageTags);
-            if (minikinLangListId == null) {
+            minikinLocaleListId = sMinikinLocaleListIdCache.get(languageTags);
+            if (minikinLocaleListId == null) {
                 final int newID = nSetTextLocales(mNativePaint, languageTags);
-                sMinikinLangListIdCache.put(languageTags, newID);
+                sMinikinLocaleListIdCache.put(languageTags, newID);
                 return;
             }
         }
-        nSetTextLocalesByMinikinLangListId(mNativePaint, minikinLangListId.intValue());
+        nSetTextLocalesByMinikinLocaleListId(mNativePaint, minikinLocaleListId.intValue());
     }
 
     /**
@@ -2918,8 +2918,8 @@
     @CriticalNative
     private static native void nSetTextAlign(long paintPtr, int align);
     @CriticalNative
-    private static native void nSetTextLocalesByMinikinLangListId(long paintPtr,
-            int mMinikinLangListId);
+    private static native void nSetTextLocalesByMinikinLocaleListId(long paintPtr,
+            int mMinikinLocaleListId);
     @CriticalNative
     private static native void nSetShadowLayer(long paintPtr,
             float radius, float dx, float dy, int color);
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index f739634..90fe0b7 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -26,39 +26,38 @@
 
 namespace android {
 
-minikin::FontStyle MinikinUtils::prepareMinikinPaint(minikin::MinikinPaint* minikinPaint,
-                                                     const Paint* paint, const Typeface* typeface) {
+minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint,
+                                                        const Typeface* typeface) {
     const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
     minikin::FontStyle resolved = resolvedFace->fStyle;
 
-    /* Prepare minikin FontStyle */
-    minikin::FontVariant minikinVariant = (paint->getFontVariant() == minikin::VARIANT_ELEGANT)
-                                                  ? minikin::VARIANT_ELEGANT
-                                                  : minikin::VARIANT_COMPACT;
-    const uint32_t langListId = paint->getMinikinLangListId();
-    minikin::FontStyle minikinStyle(langListId, minikinVariant, resolved.getWeight(),
-                                    resolved.getItalic());
+    const minikin::FontVariant minikinVariant =
+            (paint->getFontVariant() == minikin::FontVariant::ELEGANT)
+                    ? minikin::FontVariant::ELEGANT
+                    : minikin::FontVariant::COMPACT;
 
+    minikin::MinikinPaint minikinPaint;
     /* Prepare minikin Paint */
-    minikinPaint->size =
+    minikinPaint.size =
             paint->isLinearText() ? paint->getTextSize() : static_cast<int>(paint->getTextSize());
-    minikinPaint->scaleX = paint->getTextScaleX();
-    minikinPaint->skewX = paint->getTextSkewX();
-    minikinPaint->letterSpacing = paint->getLetterSpacing();
-    minikinPaint->wordSpacing = paint->getWordSpacing();
-    minikinPaint->paintFlags = MinikinFontSkia::packPaintFlags(paint);
-    minikinPaint->fontFeatureSettings = paint->getFontFeatureSettings();
-    minikinPaint->hyphenEdit = minikin::HyphenEdit(paint->getHyphenEdit());
-    return minikinStyle;
+    minikinPaint.scaleX = paint->getTextScaleX();
+    minikinPaint.skewX = paint->getTextSkewX();
+    minikinPaint.letterSpacing = paint->getLetterSpacing();
+    minikinPaint.wordSpacing = paint->getWordSpacing();
+    minikinPaint.paintFlags = MinikinFontSkia::packPaintFlags(paint);
+    minikinPaint.localeListId = paint->getMinikinLocaleListId();
+    minikinPaint.fontStyle = minikin::FontStyle(minikinVariant, resolved.weight, resolved.slant);
+    minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
+    minikinPaint.hyphenEdit = minikin::HyphenEdit(paint->getHyphenEdit());
+    return minikinPaint;
 }
 
 minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFlags,
                                        const Typeface* typeface, const uint16_t* buf, size_t start,
                                        size_t count, size_t bufSize) {
-    minikin::MinikinPaint minikinPaint;
-    minikin::FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, paint, typeface);
+    minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
     minikin::Layout layout;
-    layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint,
+    layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint,
                     Typeface::resolveDefault(typeface)->fFontCollection);
     return layout;
 }
@@ -66,11 +65,10 @@
 float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
                                 const Typeface* typeface, const uint16_t* buf, size_t start,
                                 size_t count, size_t bufSize, float* advances) {
-    minikin::MinikinPaint minikinPaint;
-    minikin::FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, paint, typeface);
+    minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
     const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface);
-    return minikin::Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinStyle,
-                                        minikinPaint, resolvedTypeface->fFontCollection, advances,
+    return minikin::Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinPaint,
+                                        resolvedTypeface->fFontCollection, advances,
                                         nullptr /* extent */, nullptr /* overhangs */);
 }
 
@@ -109,4 +107,4 @@
     SkPathMeasure measure(path, false);
     return align * (layout.getAdvance() - measure.getLength());
 }
-}
+}  // namespace android
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 8bb9179..7036cbe 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -34,9 +34,8 @@
 
 class MinikinUtils {
 public:
-    ANDROID_API static minikin::FontStyle prepareMinikinPaint(minikin::MinikinPaint* minikinPaint,
-                                                              const Paint* paint,
-                                                              const Typeface* typeface);
+    ANDROID_API static minikin::MinikinPaint prepareMinikinPaint(const Paint* paint,
+                                                                 const Typeface* typeface);
 
     ANDROID_API static minikin::Layout doLayout(const Paint* paint, minikin::Bidi bidiFlags,
                                                 const Typeface* typeface, const uint16_t* buf,
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index da7417a..76beb11 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -67,11 +67,11 @@
 
     std::string getFontFeatureSettings() const { return mFontFeatureSettings; }
 
-    void setMinikinLangListId(uint32_t minikinLangListId) {
-        mMinikinLangListId = minikinLangListId;
+    void setMinikinLocaleListId(uint32_t minikinLocaleListId) {
+        mMinikinLocaleListId = minikinLocaleListId;
     }
 
-    uint32_t getMinikinLangListId() const { return mMinikinLangListId; }
+    uint32_t getMinikinLocaleListId() const { return mMinikinLocaleListId; }
 
     void setFontVariant(minikin::FontVariant variant) { mFontVariant = variant; }
 
@@ -89,12 +89,13 @@
     float mLetterSpacing = 0;
     float mWordSpacing = 0;
     std::string mFontFeatureSettings;
-    uint32_t mMinikinLangListId;
+    uint32_t mMinikinLocaleListId;
     minikin::FontVariant mFontVariant;
     uint32_t mHyphenEdit = 0;
-    // The native Typeface object has the same lifetime of the Java Typeface object. The Java Paint
-    // object holds a strong reference to the Java Typeface object. Thus, following pointer can
-    // never be a dangling pointer. Note that nullptr is valid: it means the default typeface.
+    // The native Typeface object has the same lifetime of the Java Typeface
+    // object. The Java Paint object holds a strong reference to the Java Typeface
+    // object. Thus, following pointer can never be a dangling pointer. Note that
+    // nullptr is valid: it means the default typeface.
     const Typeface* mTypeface = nullptr;
 };
 
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index 4f2b3bb..94492c5 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -23,15 +23,15 @@
         , mLetterSpacing(0)
         , mWordSpacing(0)
         , mFontFeatureSettings()
-        , mMinikinLangListId(0)
-        , mFontVariant(minikin::VARIANT_DEFAULT) {}
+        , mMinikinLocaleListId(0)
+        , mFontVariant(minikin::FontVariant::DEFAULT) {}
 
 Paint::Paint(const Paint& paint)
         : SkPaint(paint)
         , mLetterSpacing(paint.mLetterSpacing)
         , mWordSpacing(paint.mWordSpacing)
         , mFontFeatureSettings(paint.mFontFeatureSettings)
-        , mMinikinLangListId(paint.mMinikinLangListId)
+        , mMinikinLocaleListId(paint.mMinikinLocaleListId)
         , mFontVariant(paint.mFontVariant)
         , mHyphenEdit(paint.mHyphenEdit)
         , mTypeface(paint.mTypeface) {}
@@ -41,8 +41,8 @@
         , mLetterSpacing(0)
         , mWordSpacing(0)
         , mFontFeatureSettings()
-        , mMinikinLangListId(0)
-        , mFontVariant(minikin::VARIANT_DEFAULT) {}
+        , mMinikinLocaleListId(0)
+        , mFontVariant(minikin::FontVariant::DEFAULT) {}
 
 Paint::~Paint() {}
 
@@ -51,7 +51,7 @@
     mLetterSpacing = other.mLetterSpacing;
     mWordSpacing = other.mWordSpacing;
     mFontFeatureSettings = other.mFontFeatureSettings;
-    mMinikinLangListId = other.mMinikinLangListId;
+    mMinikinLocaleListId = other.mMinikinLocaleListId;
     mFontVariant = other.mFontVariant;
     mHyphenEdit = other.mHyphenEdit;
     mTypeface = other.mTypeface;
@@ -62,7 +62,7 @@
     return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
            a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing &&
            a.mFontFeatureSettings == b.mFontFeatureSettings &&
-           a.mMinikinLangListId == b.mMinikinLangListId && a.mFontVariant == b.mFontVariant &&
+           a.mMinikinLocaleListId == b.mMinikinLocaleListId && a.mFontVariant == b.mFontVariant &&
            a.mHyphenEdit == b.mHyphenEdit && a.mTypeface == b.mTypeface;
 }
-}
+}  // namespace android
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index c798e66..e527adc 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -44,9 +44,8 @@
 }
 
 static minikin::FontStyle computeMinikinStyle(int weight, bool italic) {
-    // TODO: Better to use raw base weight value for font selection instead of dividing by 100.
-    const int minikinWeight = uirenderer::MathUtils::clamp((weight + 50) / 100, 1, 10);
-    return minikin::FontStyle(minikinWeight, italic);
+    return minikin::FontStyle(uirenderer::MathUtils::clamp(weight, 1, 1000),
+                              static_cast<minikin::FontSlant>(italic));
 }
 
 // Resolve the relative weight from the baseWeight and target style.
@@ -192,8 +191,8 @@
     hwTypeface->fFontCollection = collection;
     hwTypeface->fAPIStyle = Typeface::kNormal;
     hwTypeface->fBaseWeight = SkFontStyle::kNormal_Weight;
-    hwTypeface->fStyle = minikin::FontStyle(4 /* weight */, false /* italic */);
+    hwTypeface->fStyle = minikin::FontStyle();
 
     Typeface::setDefault(hwTypeface);
 }
-}
+}  // namespace android
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index aad1fd6..1fcc028 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -81,127 +81,139 @@
 
 TEST(TypefaceTest, createWithDifferentBaseWeight) {
     std::unique_ptr<Typeface> bold(Typeface::createWithDifferentBaseWeight(nullptr, 700));
-    EXPECT_EQ(7, bold->fStyle.getWeight());
-    EXPECT_FALSE(bold->fStyle.getItalic());
+    EXPECT_EQ(700, bold->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
     EXPECT_EQ(Typeface::kNormal, bold->fAPIStyle);
 
     std::unique_ptr<Typeface> light(Typeface::createWithDifferentBaseWeight(nullptr, 300));
-    EXPECT_EQ(3, light->fStyle.getWeight());
-    EXPECT_FALSE(light->fStyle.getItalic());
+    EXPECT_EQ(300, light->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, light->fStyle.slant);
     EXPECT_EQ(Typeface::kNormal, light->fAPIStyle);
 }
 
 TEST(TypefaceTest, createRelativeTest_fromRegular) {
     // In Java, Typeface.create(Typeface.DEFAULT, Typeface.NORMAL);
     std::unique_ptr<Typeface> normal(Typeface::createRelative(nullptr, Typeface::kNormal));
-    EXPECT_EQ(4, normal->fStyle.getWeight());
-    EXPECT_FALSE(normal->fStyle.getItalic());
+    EXPECT_EQ(400, normal->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, normal->fStyle.slant);
     EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.DEFAULT, Typeface.BOLD);
     std::unique_ptr<Typeface> bold(Typeface::createRelative(nullptr, Typeface::kBold));
-    EXPECT_EQ(7, bold->fStyle.getWeight());
-    EXPECT_FALSE(bold->fStyle.getItalic());
+    EXPECT_EQ(700, bold->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.DEFAULT, Typeface.ITALIC);
     std::unique_ptr<Typeface> italic(Typeface::createRelative(nullptr, Typeface::kItalic));
-    EXPECT_EQ(4, italic->fStyle.getWeight());
-    EXPECT_TRUE(italic->fStyle.getItalic());
+    EXPECT_EQ(400, italic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.DEFAULT, Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(Typeface::createRelative(nullptr, Typeface::kBoldItalic));
-    EXPECT_EQ(7, boldItalic->fStyle.getWeight());
-    EXPECT_TRUE(boldItalic->fStyle.getItalic());
+    EXPECT_EQ(700, boldItalic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
 TEST(TypefaceTest, createRelativeTest_BoldBase) {
     std::unique_ptr<Typeface> base(Typeface::createWithDifferentBaseWeight(nullptr, 700));
 
-    // In Java, Typeface.create(Typeface.create("sans-serif-bold"), Typeface.NORMAL);
+    // In Java, Typeface.create(Typeface.create("sans-serif-bold"),
+    // Typeface.NORMAL);
     std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
-    EXPECT_EQ(7, normal->fStyle.getWeight());
-    EXPECT_FALSE(normal->fStyle.getItalic());
+    EXPECT_EQ(700, normal->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, normal->fStyle.slant);
     EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
-    // In Java, Typeface.create(Typeface.create("sans-serif-bold"), Typeface.BOLD);
+    // In Java, Typeface.create(Typeface.create("sans-serif-bold"),
+    // Typeface.BOLD);
     std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
-    EXPECT_EQ(10, bold->fStyle.getWeight());
-    EXPECT_FALSE(bold->fStyle.getItalic());
+    EXPECT_EQ(1000, bold->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
-    // In Java, Typeface.create(Typeface.create("sans-serif-bold"), Typeface.ITALIC);
+    // In Java, Typeface.create(Typeface.create("sans-serif-bold"),
+    // Typeface.ITALIC);
     std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
-    EXPECT_EQ(7, italic->fStyle.getWeight());
-    EXPECT_TRUE(italic->fStyle.getItalic());
+    EXPECT_EQ(700, italic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
-    // In Java, Typeface.create(Typeface.create("sans-serif-bold"), Typeface.BOLD_ITALIC);
+    // In Java, Typeface.create(Typeface.create("sans-serif-bold"),
+    // Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createRelative(base.get(), Typeface::kBoldItalic));
-    EXPECT_EQ(10, boldItalic->fStyle.getWeight());
-    EXPECT_TRUE(boldItalic->fStyle.getItalic());
+    EXPECT_EQ(1000, boldItalic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
 TEST(TypefaceTest, createRelativeTest_LightBase) {
     std::unique_ptr<Typeface> base(Typeface::createWithDifferentBaseWeight(nullptr, 300));
 
-    // In Java, Typeface.create(Typeface.create("sans-serif-light"), Typeface.NORMAL);
+    // In Java, Typeface.create(Typeface.create("sans-serif-light"),
+    // Typeface.NORMAL);
     std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
-    EXPECT_EQ(3, normal->fStyle.getWeight());
-    EXPECT_FALSE(normal->fStyle.getItalic());
+    EXPECT_EQ(300, normal->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, normal->fStyle.slant);
     EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
-    // In Java, Typeface.create(Typeface.create("sans-serif-light"), Typeface.BOLD);
+    // In Java, Typeface.create(Typeface.create("sans-serif-light"),
+    // Typeface.BOLD);
     std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
-    EXPECT_EQ(6, bold->fStyle.getWeight());
-    EXPECT_FALSE(bold->fStyle.getItalic());
+    EXPECT_EQ(600, bold->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
-    // In Java, Typeface.create(Typeface.create("sans-serif-light"), Typeface.ITLIC);
+    // In Java, Typeface.create(Typeface.create("sans-serif-light"),
+    // Typeface.ITLIC);
     std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
-    EXPECT_EQ(3, italic->fStyle.getWeight());
-    EXPECT_TRUE(italic->fStyle.getItalic());
+    EXPECT_EQ(300, italic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
-    // In Java, Typeface.create(Typeface.create("sans-serif-light"), Typeface.BOLD_ITALIC);
+    // In Java, Typeface.create(Typeface.create("sans-serif-light"),
+    // Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createRelative(base.get(), Typeface::kBoldItalic));
-    EXPECT_EQ(6, boldItalic->fStyle.getWeight());
-    EXPECT_TRUE(boldItalic->fStyle.getItalic());
+    EXPECT_EQ(600, boldItalic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
 TEST(TypefaceTest, createRelativeTest_fromBoldStyled) {
     std::unique_ptr<Typeface> base(Typeface::createRelative(nullptr, Typeface::kBold));
 
-    // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD), Typeface.NORMAL);
+    // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
+    // Typeface.NORMAL);
     std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
-    EXPECT_EQ(4, normal->fStyle.getWeight());
-    EXPECT_FALSE(normal->fStyle.getItalic());
+    EXPECT_EQ(400, normal->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, normal->fStyle.slant);
     EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
-    // In Java Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD), Typeface.BOLD);
+    // In Java Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
+    // Typeface.BOLD);
     std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
-    EXPECT_EQ(7, bold->fStyle.getWeight());
-    EXPECT_FALSE(bold->fStyle.getItalic());
+    EXPECT_EQ(700, bold->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
-    // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD), Typeface.ITALIC);
+    // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
+    // Typeface.ITALIC);
     std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
-    EXPECT_EQ(4, normal->fStyle.getWeight());
-    EXPECT_TRUE(italic->fStyle.getItalic());
+    EXPECT_EQ(400, normal->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
-    // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD), Typeface.BOLD_ITALIC);
+    // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
+    // Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createRelative(base.get(), Typeface::kBoldItalic));
-    EXPECT_EQ(7, boldItalic->fStyle.getWeight());
-    EXPECT_TRUE(boldItalic->fStyle.getItalic());
+    EXPECT_EQ(700, boldItalic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
@@ -209,31 +221,35 @@
     std::unique_ptr<Typeface> base(Typeface::createRelative(nullptr, Typeface::kItalic));
 
     // In Java,
-    // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC), Typeface.NORMAL);
+    // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC),
+    // Typeface.NORMAL);
     std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
-    EXPECT_EQ(4, normal->fStyle.getWeight());
-    EXPECT_FALSE(normal->fStyle.getItalic());
+    EXPECT_EQ(400, normal->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, normal->fStyle.slant);
     EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
-    // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC), Typeface.BOLD);
+    // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT,
+    // Typeface.ITALIC), Typeface.BOLD);
     std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
-    EXPECT_EQ(7, bold->fStyle.getWeight());
-    EXPECT_FALSE(bold->fStyle.getItalic());
+    EXPECT_EQ(700, bold->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java,
-    // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC), Typeface.ITALIC);
+    // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC),
+    // Typeface.ITALIC);
     std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
-    EXPECT_EQ(4, italic->fStyle.getWeight());
-    EXPECT_TRUE(italic->fStyle.getItalic());
+    EXPECT_EQ(400, italic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
-    // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC), Typeface.BOLD_ITALIC);
+    // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC),
+    // Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createRelative(base.get(), Typeface::kBoldItalic));
-    EXPECT_EQ(7, boldItalic->fStyle.getWeight());
-    EXPECT_TRUE(boldItalic->fStyle.getItalic());
+    EXPECT_EQ(700, boldItalic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
@@ -245,8 +261,8 @@
     //     .setWeight(700).setItalic(false).build();
     // Typeface.create(typeface, Typeface.NORMAL);
     std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
-    EXPECT_EQ(4, normal->fStyle.getWeight());
-    EXPECT_FALSE(normal->fStyle.getItalic());
+    EXPECT_EQ(400, normal->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, normal->fStyle.slant);
     EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java,
@@ -254,8 +270,8 @@
     //     .setWeight(700).setItalic(false).build();
     // Typeface.create(typeface, Typeface.BOLD);
     std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
-    EXPECT_EQ(7, bold->fStyle.getWeight());
-    EXPECT_FALSE(bold->fStyle.getItalic());
+    EXPECT_EQ(700, bold->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java,
@@ -263,8 +279,8 @@
     //     .setWeight(700).setItalic(false).build();
     // Typeface.create(typeface, Typeface.ITALIC);
     std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
-    EXPECT_EQ(4, italic->fStyle.getWeight());
-    EXPECT_TRUE(italic->fStyle.getItalic());
+    EXPECT_EQ(400, italic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
@@ -273,89 +289,99 @@
     // Typeface.create(typeface, Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createRelative(base.get(), Typeface::kBoldItalic));
-    EXPECT_EQ(7, boldItalic->fStyle.getWeight());
-    EXPECT_TRUE(boldItalic->fStyle.getItalic());
+    EXPECT_EQ(700, boldItalic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
 TEST(TypefaceTest, createAbsolute) {
     // In Java,
-    // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(false)
+    // new
+    // Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(false)
     //     .build();
     std::unique_ptr<Typeface> regular(Typeface::createAbsolute(nullptr, 400, false));
-    EXPECT_EQ(4, regular->fStyle.getWeight());
-    EXPECT_FALSE(regular->fStyle.getItalic());
+    EXPECT_EQ(400, regular->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, regular->fStyle.slant);
     EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
     // In Java,
-    // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(false)
+    // new
+    // Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(false)
     //     .build();
     std::unique_ptr<Typeface> bold(Typeface::createAbsolute(nullptr, 700, false));
-    EXPECT_EQ(7, bold->fStyle.getWeight());
-    EXPECT_FALSE(bold->fStyle.getItalic());
+    EXPECT_EQ(700, bold->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java,
-    // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(true)
+    // new
+    // Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(true)
     //     .build();
     std::unique_ptr<Typeface> italic(Typeface::createAbsolute(nullptr, 400, true));
-    EXPECT_EQ(4, italic->fStyle.getWeight());
-    EXPECT_TRUE(italic->fStyle.getItalic());
+    EXPECT_EQ(400, italic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
-    // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(true)
+    // new
+    // Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(true)
     //     .build();
     std::unique_ptr<Typeface> boldItalic(Typeface::createAbsolute(nullptr, 700, true));
-    EXPECT_EQ(7, boldItalic->fStyle.getWeight());
-    EXPECT_TRUE(boldItalic->fStyle.getItalic());
+    EXPECT_EQ(700, boldItalic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 
     // In Java,
-    // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(1100).setItalic(true)
+    // new
+    // Typeface.Builder(invalid).setFallback("sans-serif").setWeight(1100).setItalic(true)
     //     .build();
     std::unique_ptr<Typeface> over1000(Typeface::createAbsolute(nullptr, 1100, false));
-    EXPECT_EQ(10, over1000->fStyle.getWeight());
-    EXPECT_FALSE(over1000->fStyle.getItalic());
+    EXPECT_EQ(1000, over1000->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, over1000->fStyle.slant);
     EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
 }
 
 TEST(TypefaceTest, createFromFamilies_Single) {
-    // In Java, new Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build();
+    // In Java, new
+    // Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build();
     std::unique_ptr<Typeface> regular(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoRegular), 400, false));
-    EXPECT_EQ(4, regular->fStyle.getWeight());
-    EXPECT_FALSE(regular->fStyle.getItalic());
+    EXPECT_EQ(400, regular->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, regular->fStyle.slant);
     EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
-    // In Java, new Typeface.Builder("Roboto-Bold.ttf").setWeight(700).setItalic(false).build();
+    // In Java, new
+    // Typeface.Builder("Roboto-Bold.ttf").setWeight(700).setItalic(false).build();
     std::unique_ptr<Typeface> bold(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold), 700, false));
-    EXPECT_EQ(7, bold->fStyle.getWeight());
-    EXPECT_FALSE(bold->fStyle.getItalic());
+    EXPECT_EQ(700, bold->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
-    // In Java, new Typeface.Builder("Roboto-Italic.ttf").setWeight(400).setItalic(true).build();
+    // In Java, new
+    // Typeface.Builder("Roboto-Italic.ttf").setWeight(400).setItalic(true).build();
     std::unique_ptr<Typeface> italic(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoItalic), 400, true));
-    EXPECT_EQ(4, italic->fStyle.getWeight());
-    EXPECT_TRUE(italic->fStyle.getItalic());
+    EXPECT_EQ(400, italic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
-    // new Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(700).setItalic(true).build();
+    // new
+    // Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(700).setItalic(true).build();
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBoldItalic), 700, true));
-    EXPECT_EQ(7, boldItalic->fStyle.getWeight());
-    EXPECT_TRUE(boldItalic->fStyle.getItalic());
+    EXPECT_EQ(700, boldItalic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
-    // new Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(1100).setItalic(false).build();
+    // new
+    // Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(1100).setItalic(false).build();
     std::unique_ptr<Typeface> over1000(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold), 1100, false));
-    EXPECT_EQ(10, over1000->fStyle.getWeight());
-    EXPECT_FALSE(over1000->fStyle.getItalic());
+    EXPECT_EQ(1000, over1000->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, over1000->fStyle.slant);
     EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
 }
 
@@ -363,30 +389,30 @@
     // In Java, new Typeface.Builder("Roboto-Regular.ttf").build();
     std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
             makeSingleFamlyVector(kRobotoRegular), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
-    EXPECT_EQ(4, regular->fStyle.getWeight());
-    EXPECT_FALSE(regular->fStyle.getItalic());
+    EXPECT_EQ(400, regular->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, regular->fStyle.slant);
     EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
     // In Java, new Typeface.Builder("Roboto-Bold.ttf").build();
     std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
             makeSingleFamlyVector(kRobotoBold), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
-    EXPECT_EQ(7, bold->fStyle.getWeight());
-    EXPECT_FALSE(bold->fStyle.getItalic());
+    EXPECT_EQ(700, bold->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, new Typeface.Builder("Roboto-Italic.ttf").build();
     std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
             makeSingleFamlyVector(kRobotoItalic), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
-    EXPECT_EQ(4, italic->fStyle.getWeight());
-    EXPECT_TRUE(italic->fStyle.getItalic());
+    EXPECT_EQ(400, italic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java, new Typeface.Builder("Roboto-BoldItalic.ttf").build();
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBoldItalic),
                                          RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
-    EXPECT_EQ(7, boldItalic->fStyle.getWeight());
-    EXPECT_TRUE(boldItalic->fStyle.getItalic());
+    EXPECT_EQ(700, boldItalic->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 }
 
@@ -396,8 +422,8 @@
             buildFamily(kRobotoBoldItalic)};
     std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
             std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
-    EXPECT_EQ(4, typeface->fStyle.getWeight());
-    EXPECT_FALSE(typeface->fStyle.getItalic());
+    EXPECT_EQ(400, typeface->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, typeface->fStyle.slant);
 }
 
 TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
@@ -405,8 +431,8 @@
             buildFamily(kRobotoBold), buildFamily(kRobotoItalic), buildFamily(kRobotoBoldItalic)};
     std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
             std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
-    EXPECT_EQ(7, typeface->fStyle.getWeight());
-    EXPECT_FALSE(typeface->fStyle.getItalic());
+    EXPECT_EQ(700, typeface->fStyle.weight);
+    EXPECT_EQ(minikin::FontSlant::UPRIGHT, typeface->fStyle.slant);
 }
 
 }  // namespace
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index 38fe879..386c4e0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -10,79 +10,89 @@
  */
 public abstract class AbstractPreferenceController {
 
-  protected final Context mContext;
+    protected final Context mContext;
 
-  public AbstractPreferenceController(Context context) {
-    mContext = context;
-  }
+    public AbstractPreferenceController(Context context) {
+        mContext = context;
+    }
 
-  /**
-   * Displays preference in this controller.
-   */
-  public void displayPreference(PreferenceScreen screen) {
-      if (isAvailable()) {
-          if (this instanceof Preference.OnPreferenceChangeListener) {
-              final Preference preference = screen.findPreference(getPreferenceKey());
-              preference.setOnPreferenceChangeListener(
-                      (Preference.OnPreferenceChangeListener) this);
-          }
-      } else {
-          removePreference(screen, getPreferenceKey());
-      }
-  }
+    /**
+     * Displays preference in this controller.
+     */
+    public void displayPreference(PreferenceScreen screen) {
+        if (isAvailable()) {
+            if (this instanceof Preference.OnPreferenceChangeListener) {
+                final Preference preference = screen.findPreference(getPreferenceKey());
+                preference.setOnPreferenceChangeListener(
+                        (Preference.OnPreferenceChangeListener) this);
+            }
+        } else {
+            removePreference(screen, getPreferenceKey());
+        }
+    }
 
-  /**
-   * Updates the current status of preference (summary, switch state, etc)
-   */
-  public void updateState(Preference preference) {
+    /**
+     * Updates the current status of preference (summary, switch state, etc)
+     */
+    public void updateState(Preference preference) {
 
-  }
+    }
 
-  /**
-   * Returns true if preference is available (should be displayed)
-   */
-  public abstract boolean isAvailable();
+    /**
+     * Returns true if preference is available (should be displayed)
+     */
+    public abstract boolean isAvailable();
 
-  /**
-   * Handles preference tree click
-   *
-   * @param preference the preference being clicked
-   * @return true if click is handled
-   */
-  public boolean handlePreferenceTreeClick(Preference preference) {
-      return false;
-  }
+    /**
+     * Handles preference tree click
+     *
+     * @param preference the preference being clicked
+     * @return true if click is handled
+     */
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        return false;
+    }
 
-  /**
-   * Returns the key for this preference.
-   */
-  public abstract String getPreferenceKey();
+    /**
+     * Returns the key for this preference.
+     */
+    public abstract String getPreferenceKey();
 
-  /**
-   * Removes preference from screen.
-   */
-  protected final void removePreference(PreferenceScreen screen, String key) {
-      findAndRemovePreference(screen, key);
-  }
+    /**
+     * Removes preference from screen.
+     */
+    protected final void removePreference(PreferenceScreen screen, String key) {
+        findAndRemovePreference(screen, key);
+    }
 
-  // finds the preference recursively and removes it from its parent
-  private boolean findAndRemovePreference(PreferenceGroup prefGroup, String key) {
-      final int preferenceCount = prefGroup.getPreferenceCount();
-      for (int i = 0; i < preferenceCount; i++) {
-          final Preference preference = prefGroup.getPreference(i);
-          final String curKey = preference.getKey();
+    /**
+     * Show/hide a preference.
+     */
+    protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) {
+        final Preference pref = group.findPreference(key);
+        if (pref != null) {
+            pref.setVisible(isVisible);
+        }
+    }
 
-          if (curKey != null && curKey.equals(key)) {
-              return prefGroup.removePreference(preference);
-          }
+    // finds the preference recursively and removes it from its parent
+    private boolean findAndRemovePreference(PreferenceGroup prefGroup, String key) {
+        final int preferenceCount = prefGroup.getPreferenceCount();
+        for (int i = 0; i < preferenceCount; i++) {
+            final Preference preference = prefGroup.getPreference(i);
+            final String curKey = preference.getKey();
 
-          if (preference instanceof PreferenceGroup) {
-              if (findAndRemovePreference((PreferenceGroup) preference, key)) {
-                  return true;
-              }
-          }
-      }
-      return false;
-  }
+            if (curKey != null && curKey.equals(key)) {
+                return prefGroup.removePreference(preference);
+            }
+
+            if (preference instanceof PreferenceGroup) {
+                if (findAndRemovePreference((PreferenceGroup) preference, key)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
 
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index f4c9bb3..4fe9d56 100755
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.graph;
 
-import android.animation.ArgbEvaluator;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
@@ -100,7 +99,7 @@
 
         final int N = levels.length();
         mColors = new int[2 * N];
-        for (int i=0; i < N; i++) {
+        for (int i = 0; i < N; i++) {
             mColors[2 * i] = levels.getInt(i, 0);
             if (colors.getType(i) == TypedValue.TYPE_ATTRIBUTE) {
                 mColors[2 * i + 1] = Utils.getColorAttr(context, colors.getThemeAttributeId(i, 0));
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index fdbbf14..dd55188 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -31,15 +31,17 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.SparseArray;
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.settingslib.R;
 import com.android.settingslib.TronUtils;
+import com.android.settingslib.TwoTargetPreference;
 import com.android.settingslib.Utils;
 import com.android.settingslib.wifi.AccessPoint.Speed;
 
-public class AccessPointPreference extends Preference {
+public class AccessPointPreference extends TwoTargetPreference {
 
     private static final int[] STATE_SECURED = {
             R.attr.state_encrypted
@@ -126,7 +128,6 @@
                           int iconResId, boolean forSavedNetworks, StateListDrawable frictionSld,
                           int level, IconInjector iconInjector) {
         super(context);
-        setWidgetLayoutResource(R.layout.access_point_friction_widget);
         mBadgeCache = cache;
         mAccessPoint = accessPoint;
         mForSavedNetworks = forSavedNetworks;
@@ -165,6 +166,20 @@
 
         ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
         bindFrictionImage(frictionImageView);
+        setDividerVisibility(view, View.GONE);
+    }
+
+    protected void setDividerVisibility(final PreferenceViewHolder view,
+            @View.Visibility int visibility) {
+        final View divider = view.findViewById(R.id.two_target_divider);
+        if (divider != null) {
+            divider.setVisibility(visibility);
+        }
+    }
+
+    @Override
+    protected int getSecondTargetResId() {
+        return R.layout.access_point_friction_widget;
     }
 
     protected void updateIcon(int level, Context context) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawable/UserIconDrawableTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java
similarity index 76%
rename from packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawable/UserIconDrawableTest.java
rename to packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java
index 5fdd114..508107c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawable/UserIconDrawableTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java
@@ -16,22 +16,19 @@
 
 package com.android.settingslib.drawable;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 
 import com.android.settingslib.R;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-import com.android.settingslib.TestConfig;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
 
-import static com.google.common.truth.Truth.assertThat;
-
-@RunWith(SettingsLibRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+@RunWith(AndroidJUnit4.class)
 public class UserIconDrawableTest {
 
     private UserIconDrawable mDrawable;
@@ -39,7 +36,7 @@
     @Test
     public void getConstantState_shouldNotBeNull() {
         final Bitmap b = BitmapFactory.decodeResource(
-                RuntimeEnvironment.application.getResources(),
+                InstrumentationRegistry.getTargetContext().getResources(),
                 R.drawable.home);
         mDrawable = new UserIconDrawable(100 /* size */).setIcon(b).bake();
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TestConfig.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TestConfig.java
index 3af9768..1f9070c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TestConfig.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TestConfig.java
@@ -16,8 +16,10 @@
 
 package com.android.settingslib;
 
+import android.os.Build;
+
 public class TestConfig {
-    public static final int SDK_VERSION = 25;
+    public static final int SDK_VERSION = Build.VERSION_CODES.O;
     public static final String MANIFEST_PATH =
             "frameworks/base/packages/SettingsLib/tests/robotests/AndroidManifest.xml";
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/PreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
similarity index 87%
rename from packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/PreferenceControllerTest.java
rename to packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
index 9d7cd11..c08bc2a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/PreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
@@ -15,22 +15,7 @@
  */
 package com.android.settingslib.core;
 
-import android.content.Context;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceGroup;
-import android.support.v7.preference.PreferenceManager;
-import android.support.v7.preference.PreferenceScreen;
-import com.android.settingslib.TestConfig;
-import com.android.settingslib.core.AbstractPreferenceController;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowApplication;
-import org.robolectric.RobolectricTestRunner;
-
+import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -38,22 +23,41 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceGroup;
+import android.support.v7.preference.PreferenceManager;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
 @RunWith(RobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
-public class PreferenceControllerTest {
+public class AbstractPreferenceControllerTest {
 
     @Mock
     private Context mContext;
     @Mock
     private PreferenceScreen mScreen;
-    @Mock
-    private Preference mPreference;
 
+    private Preference mPreference;
     private TestPrefController mTestPrefController;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mPreference = new Preference(RuntimeEnvironment.application);
+        mPreference.setKey(TestPrefController.KEY_PREF);
         mTestPrefController = new TestPrefController(mContext);
     }
 
@@ -61,7 +65,6 @@
     public void removeExistingPref_shouldBeRemoved() {
         when(mScreen.getPreferenceCount()).thenReturn(1);
         when(mScreen.getPreference(0)).thenReturn(mPreference);
-        when(mPreference.getKey()).thenReturn(TestPrefController.KEY_PREF);
 
         mTestPrefController.removePreference(mScreen, TestPrefController.KEY_PREF);
 
@@ -85,10 +88,25 @@
     }
 
     @Test
+    public void setVisible_prefIsVisible_shouldSetToVisible() {
+        when(mScreen.findPreference(TestPrefController.KEY_PREF)).thenReturn(mPreference);
+
+        mTestPrefController.setVisible(mScreen, TestPrefController.KEY_PREF, true /* visible */);
+        assertThat(mPreference.isVisible()).isTrue();
+    }
+
+    @Test
+    public void setVisible_prefNotVisible_shouldSetToInvisible() {
+        when(mScreen.findPreference(TestPrefController.KEY_PREF)).thenReturn(mPreference);
+
+        mTestPrefController.setVisible(mScreen, TestPrefController.KEY_PREF, false /* visible */);
+        assertThat(mPreference.isVisible()).isFalse();
+    }
+
+    @Test
     public void doNotDisplayPref_ifNotAvailable() {
         when(mScreen.getPreferenceCount()).thenReturn(1);
         when(mScreen.getPreference(0)).thenReturn(mPreference);
-        when(mPreference.getKey()).thenReturn(TestPrefController.KEY_PREF);
         mTestPrefController.isAvailable = false;
 
         mTestPrefController.displayPreference(mScreen);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
index 3522b8a..e022232 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
@@ -35,11 +36,13 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
 
 @RunWith(SettingsLibRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
         shadows = SettingsLibShadowResources.class)
 public class BatteryMeterDrawableBaseTest {
+    private static final int CRITICAL_LEVEL = 5;
     private static final int PADDING = 5;
     private static final int HEIGHT = 80;
     private static final int WIDTH = 40;
@@ -53,7 +56,8 @@
         MockitoAnnotations.initMocks(this);
 
         mContext = RuntimeEnvironment.application;
-        mBatteryMeterDrawableBase = new BatteryMeterDrawableBase(mContext, 0 /* frameColor */);
+        mBatteryMeterDrawableBase = spy(new BatteryMeterDrawableBase(mContext, 0 /* frameColor */));
+        ReflectionHelpers.setField(mBatteryMeterDrawableBase, "mCriticalLevel", CRITICAL_LEVEL);
     }
 
     @Test
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7b8d0db..29ecac0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -120,6 +120,7 @@
     <uses-permission android:name="android.permission.TRUST_LISTENER" />
     <uses-permission android:name="android.permission.USE_FINGERPRINT" />
     <uses-permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT" />
+    <uses-permission android:name="android.permission.BIND_SLICE" />
 
     <!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked -->
     <uses-permission android:name="android.permission.SET_WALLPAPER"/>
@@ -222,7 +223,7 @@
              -->
         <service android:name="SystemUIService"
             android:exported="true"
-            />
+        />
 
         <!-- Recents depends on every user having their own SystemUI process, so on user switch,
              ensure that the process is created by starting this service.
@@ -568,6 +569,11 @@
                 android:resource="@xml/fileprovider" />
         </provider>
 
+        <provider android:name=".keyguard.KeyguardSliceProvider"
+                  android:authorities="com.android.systemui.keyguard"
+                  android:exported="true">
+        </provider>
+
         <receiver
             android:name=".statusbar.KeyboardShortcutsReceiver">
             <intent-filter>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index 56fb73f..020cfee 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -18,34 +18,37 @@
 -->
 
 <!-- This is a view that shows general status information in Keyguard. -->
-<LinearLayout
+<com.android.keyguard.KeyguardSliceView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
+    android:layout_marginStart="16dp"
+    android:layout_marginEnd="16dp"
+    android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:gravity="center">
-    <com.android.systemui.statusbar.policy.DateView
-        android:id="@+id/date_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textColor="?attr/wallpaperTextColor"
-        style="@style/widget_label"
-        android:letterSpacing="0.05"
-        android:gravity="center"
-        />
-    <TextView android:id="@+id/alarm_status"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:drawablePadding="6dp"
-        android:drawableStart="@drawable/ic_access_alarms_big"
-        android:drawableTint="?attr/wallpaperTextColorSecondary"
-        android:drawableTintMode="src_in"
-        android:textColor="?attr/wallpaperTextColorSecondary"
-        android:letterSpacing="0.05"
-        style="@style/widget_label"
-        android:layout_marginStart="6dp"
-        android:gravity="center"
-        android:visibility="gone"
-        />
-</LinearLayout>
+    android:layout_marginTop="@dimen/date_owner_info_margin"
+    android:layout_gravity="center_horizontal"
+    android:paddingTop="4dp"
+    android:clipToPadding="false"
+    android:orientation="vertical"
+    android:layout_centerHorizontal="true">
+    <TextView android:id="@+id/title"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:singleLine="true"
+              android:ellipsize="end"
+              android:fadingEdge="horizontal"
+              android:gravity="center"
+              android:textSize="22sp"
+              android:textColor="?attr/wallpaperTextColor"
+    />
+    <TextView android:id="@+id/text"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:singleLine="true"
+              android:gravity="center"
+              android:visibility="gone"
+              android:textSize="16sp"
+              android:textColor="?attr/wallpaperTextColor"
+              android:layout_marginTop="4dp"
+              android:ellipsize="end"
+    />
+</com.android.keyguard.KeyguardSliceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml
index 8ab95f9..cfda6cc 100644
--- a/packages/SystemUI/res-keyguard/values-mr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml
@@ -25,12 +25,11 @@
     <string name="keyguard_password_enter_puk_code" msgid="670683628782925409">"सिम PUK आणि नवीन पिन कोड टाइप करा"</string>
     <string name="keyguard_password_enter_puk_prompt" msgid="3747778500166059332">"सिम PUK कोड"</string>
     <string name="keyguard_password_enter_pin_prompt" msgid="8188243197504453830">"नवीन सिम पिन कोड"</string>
-    <string name="keyguard_password_entry_touch_hint" msgid="5790410752696806482"><font size="17">"संकेतशब्द टाइप करण्यासाठी स्पर्श करा"</font></string>
-    <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"अनलॉक करण्यासाठी संकेतशब्द टाइप करा"</string>
+    <string name="keyguard_password_entry_touch_hint" msgid="5790410752696806482"><font size="17">"पासवर्ड टाइप करण्यासाठी स्पर्श करा"</font></string>
+    <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"अनलॉक करण्यासाठी पासवर्ड टाइप करा"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"अनलॉक करण्यासाठी पिन टाइप करा"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"चुकीचा पिन कोड."</string>
-    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
-    <skip />
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"अवैध कार्ड."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"चार्ज झाली"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"चार्ज होत आहे"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"द्रुतपणे चार्ज होत आहे"</string>
@@ -54,10 +53,10 @@
     <string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"पुढील अलार्म <xliff:g id="ALARM">%1$s</xliff:g> साठी सेट केला"</string>
     <string name="keyboardview_keycode_delete" msgid="6883116827512721630">"हटवा"</string>
     <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM बंद करा"</string>
-    <string name="keyboardview_keycode_enter" msgid="4505833604411016668">"प्रविष्ट करा"</string>
+    <string name="keyboardview_keycode_enter" msgid="4505833604411016668">"एंटर करा"</string>
     <string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"पॅटर्न विसरलात"</string>
     <string name="kg_wrong_pattern" msgid="7620081431514773802">"चुकीचा पॅटर्न"</string>
-    <string name="kg_wrong_password" msgid="4580683060277329277">"चुकीचा संकेतशब्द"</string>
+    <string name="kg_wrong_password" msgid="4580683060277329277">"चुकीचा पासवर्ड"</string>
     <string name="kg_wrong_pin" msgid="4785660766909463466">"चुकीचा पिन"</string>
     <plurals name="kg_too_many_failed_attempts_countdown" formatted="false" msgid="4368805541257003755">
       <item quantity="one"><xliff:g id="NUMBER">%d</xliff:g> सेकंदात पुन्हा प्रयत्न करा.</item>
@@ -67,20 +66,20 @@
     <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"सिम पिन एंटर करा"</string>
     <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" साठी सिम पिन एंटर करा"</string>
     <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"मोबाइल सेवांशिवाय डिव्हाइस वापरण्यासाठी eSIM बंद करा."</string>
-    <string name="kg_pin_instructions" msgid="4069609316644030034">"पिन प्रविष्ट करा"</string>
-    <string name="kg_password_instructions" msgid="136952397352976538">"संकेतशब्द प्रविष्ट करा"</string>
-    <string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"सिम आता अक्षम केले आहे. सुरू ठेवण्यासाठी PUK कोड प्रविष्ट करा. तपशीलांसाठी वाहकाशी संपर्क साधा."</string>
-    <string name="kg_puk_enter_puk_hint_multi" msgid="1373131883510840794">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" सिम आता अक्षम केले आहे. सुरू ठेवण्यासाठी PUK कोड प्रविष्ट करा. तपशीलांसाठी वाहकाशी संपर्क साधा."</string>
-    <string name="kg_puk_enter_pin_hint" msgid="3137789674920391087">"इच्छित पिन कोड प्रविष्ट करा"</string>
+    <string name="kg_pin_instructions" msgid="4069609316644030034">"पिन एंटर करा"</string>
+    <string name="kg_password_instructions" msgid="136952397352976538">"पासवर्ड एंटर करा"</string>
+    <string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"सिम आता अक्षम केले आहे. सुरू ठेवण्यासाठी PUK कोड एंटर करा. तपशीलांसाठी वाहकाशी संपर्क साधा."</string>
+    <string name="kg_puk_enter_puk_hint_multi" msgid="1373131883510840794">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" सिम आता अक्षम केले आहे. सुरू ठेवण्यासाठी PUK कोड एंटर करा. तपशीलांसाठी वाहकाशी संपर्क साधा."</string>
+    <string name="kg_puk_enter_pin_hint" msgid="3137789674920391087">"इच्छित पिन कोड एंटर करा"</string>
     <string name="kg_enter_confirm_pin_hint" msgid="3089485999116759671">"इच्छित पिन कोड ची पुष्टी करा"</string>
     <string name="kg_sim_unlock_progress_dialog_message" msgid="4471738151810900114">"सिम कार्ड अनलॉक करत आहे…"</string>
     <string name="kg_invalid_sim_pin_hint" msgid="3057533256729513335">"4 ते 8 अंकांचा पिन टाईप करा."</string>
     <string name="kg_invalid_sim_puk_hint" msgid="6003602401368264144">"PUK कोड 8 अंकी किंवा त्यापेक्षा अधिकचा असावा."</string>
-    <string name="kg_invalid_puk" msgid="5399287873762592502">"योग्य PUK कोड पुन्हा प्रविष्ट करा. पुनःपुन्हा प्रयत्न करणे सिम कायमचे अक्षम करेल."</string>
+    <string name="kg_invalid_puk" msgid="5399287873762592502">"योग्य PUK कोड पुन्हा एंटर करा. पुनःपुन्हा प्रयत्न करणे सिम कायमचे अक्षम करेल."</string>
     <string name="kg_invalid_confirm_pin_hint" product="default" msgid="5672736555427444330">"पिन कोड जुळत नाहीत"</string>
     <string name="kg_login_too_many_attempts" msgid="6604574268387867255">"खूप जास्त पॅटर्न प्रयत्न"</string>
     <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8637788033282252027">"आपण आपला PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा चुकीच्या पद्धतीने टाइप केला आहे. \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
-    <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7724148763268377734">"आपण आपला संकेतशब्द <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा चुकीच्या पद्धतीने टाइप केला आहे. \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
+    <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7724148763268377734">"आपण आपला पासवर्ड <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा चुकीच्या पद्धतीने टाइप केला आहे. \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
     <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4820967667848302092">"तुम्ही आपला अनलॉक पॅटर्न <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यरितीने काढला. \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="1629351522209932316">"आपण टॅबलेट अनलॉक करण्याचा <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा चुकीच्या पद्धतीने प्रयत्न केला आहे. आणखी <xliff:g id="NUMBER_1">%2$d</xliff:g> अयशस्वी प्रयत्नांनंतर, हे टॅबलेट रीसेट केला जाईल, जे त्याचा सर्व डेटा हटवेल."</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="3921998703529189931">"आपण फोन अनलॉक करण्याचा <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा चुकीच्या पद्धतीने प्रयत्न केला आहे. आणखी <xliff:g id="NUMBER_1">%2$d</xliff:g> अयशस्वी प्रयत्नांनंतर, हा फोन रीसेट केला जाईल, जे त्याचा सर्व डेटा हटवेल."</string>
@@ -117,10 +116,10 @@
     <string name="kg_prompt_reason_restart_password" msgid="6984641181515902406">"डिव्हाइस रीस्टार्ट झाल्यावर पासवर्ड आवश्यक आहे"</string>
     <string name="kg_prompt_reason_timeout_pattern" msgid="5304487696073914063">"अतिरिक्त सुरक्षिततेसाठी पॅटर्न आवश्‍यक आहे"</string>
     <string name="kg_prompt_reason_timeout_pin" msgid="8851462864335757813">"अतिरिक्त सुरक्षिततेसाठी पिन आवश्‍यक आहे"</string>
-    <string name="kg_prompt_reason_timeout_password" msgid="6563904839641583441">"अतिरिक्त सुरक्षिततेसाठी संकेतशब्द आवश्‍यक आहे"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="6563904839641583441">"अतिरिक्त सुरक्षिततेसाठी पासवर्ड आवश्‍यक आहे"</string>
     <string name="kg_prompt_reason_switch_profiles_pattern" msgid="3398054847288438444">"तुम्ही प्रोफाईल स्विच करता तेव्‍हा पॅटर्न आवश्‍यक आहे"</string>
     <string name="kg_prompt_reason_switch_profiles_pin" msgid="7426368139226961699">"आपण प्रोफाईल स्विच करता तेव्‍हा पिन आवश्‍यक आहे"</string>
-    <string name="kg_prompt_reason_switch_profiles_password" msgid="8383831046318421845">"आपण प्रोफाईल स्विच करता तेव्‍हा संकेतशब्द आवश्‍यक आहे"</string>
+    <string name="kg_prompt_reason_switch_profiles_password" msgid="8383831046318421845">"आपण प्रोफाईल स्विच करता तेव्‍हा पासवर्ड आवश्‍यक आहे"</string>
     <string name="kg_prompt_reason_device_admin" msgid="3452168247888906179">"प्रशासकाद्वारे लॉक केलेले डिव्हाइस"</string>
     <string name="kg_prompt_reason_user_request" msgid="8236951765212462286">"डिव्हाइस मॅन्युअली लॉक केले होते"</string>
     <plurals name="kg_prompt_reason_time_pattern" formatted="false" msgid="71299470072448533">
diff --git a/packages/SystemUI/res/values-in/config.xml b/packages/SystemUI/res/values-in/config.xml
index 5309563..9857f13 100644
--- a/packages/SystemUI/res/values-in/config.xml
+++ b/packages/SystemUI/res/values-in/config.xml
@@ -22,5 +22,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for config_overviewServiceComponent (2288311504315574053) -->
+    <skip />
     <string name="doze_pickup_subtype_performs_proximity_check" msgid="533127617385956583"></string>
 </resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 27d5f1b..255ba2c 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -358,12 +358,12 @@
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Notifikasi kurang darurat di bawah"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"Tap lagi untuk membuka"</string>
-    <string name="keyguard_unlock" msgid="8043466894212841998">"Gesek ke atas untuk membuka kunci"</string>
+    <string name="keyguard_unlock" msgid="8043466894212841998">"Geser ke atas untuk membuka kunci"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Perangkat ini dikelola oleh organisasi"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Perangkat ini dikelola oleh <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
-    <string name="phone_hint" msgid="4872890986869209950">"Gesek dari ikon untuk telepon"</string>
-    <string name="voice_hint" msgid="8939888732119726665">"Gesek dari ikon untuk mengaktifkan bantuan suara"</string>
-    <string name="camera_hint" msgid="7939688436797157483">"Gesek dari ikon untuk kamera"</string>
+    <string name="phone_hint" msgid="4872890986869209950">"Geser dari ikon untuk telepon"</string>
+    <string name="voice_hint" msgid="8939888732119726665">"Geser dari ikon untuk bantuan suara"</string>
+    <string name="camera_hint" msgid="7939688436797157483">"Geser dari ikon untuk kamera"</string>
     <string name="interruption_level_none_with_warning" msgid="5114872171614161084">"Senyap total. Tindakan ini juga akan mensenyapkan pembaca layar."</string>
     <string name="interruption_level_none" msgid="6000083681244492992">"Senyap total"</string>
     <string name="interruption_level_priority" msgid="6426766465363855505">"Hanya untuk prioritas"</string>
@@ -499,7 +499,7 @@
     <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. Ketuk untuk membisukan. Layanan aksesibilitas mungkin dibisukan."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="6427727603978431301">"%1$s. Tap untuk menyetel agar bergetar."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="8995013018414535494">"%1$s. Tap untuk menonaktifkan."</string>
-    <string name="volume_dialog_accessibility_shown_message" msgid="1834631467074259998">"Kontrol volume %s ditampilkan. Gesek ke atas untuk menutup."</string>
+    <string name="volume_dialog_accessibility_shown_message" msgid="1834631467074259998">"Kontrol volume %s ditampilkan. Geser ke atas untuk menutup."</string>
     <string name="volume_dialog_accessibility_dismissed_message" msgid="51543526013711399">"Kontrol volume disembunyikan"</string>
     <string name="system_ui_tuner" msgid="708224127392452018">"Penyetel Antarmuka Pengguna Sistem"</string>
     <string name="show_battery_percentage" msgid="5444136600512968798">"Tampilkan persentase baterai yang tersemat"</string>
diff --git a/packages/SystemUI/res/values-mr/config.xml b/packages/SystemUI/res/values-mr/config.xml
index 5309563..9857f13 100644
--- a/packages/SystemUI/res/values-mr/config.xml
+++ b/packages/SystemUI/res/values-mr/config.xml
@@ -22,5 +22,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for config_overviewServiceComponent (2288311504315574053) -->
+    <skip />
     <string name="doze_pickup_subtype_performs_proximity_check" msgid="533127617385956583"></string>
 </resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index b5975b1..2a9ec0c 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -137,7 +137,7 @@
     <string name="accessibility_desc_on" msgid="2385254693624345265">"चालू."</string>
     <string name="accessibility_desc_off" msgid="6475508157786853157">"बंद."</string>
     <string name="accessibility_desc_connected" msgid="8366256693719499665">"कनेक्‍ट केले."</string>
-    <string name="accessibility_desc_connecting" msgid="3812924520316280149">"कनेक्ट करीत आहे."</string>
+    <string name="accessibility_desc_connecting" msgid="3812924520316280149">"कनेक्ट करत आहे."</string>
     <string name="accessibility_data_connection_gprs" msgid="1606477224486747751">"GPRS"</string>
     <string name="accessibility_data_connection_1x" msgid="994133468120244018">"1 X"</string>
     <string name="accessibility_data_connection_hspa" msgid="2032328855462645198">"HSPA"</string>
@@ -168,7 +168,7 @@
     <string name="accessibility_overflow_action" msgid="5681882033274783311">"सर्व सूचना पहा"</string>
     <string name="accessibility_remove_notification" msgid="3603099514902182350">"सूचना साफ करा."</string>
     <string name="accessibility_gps_enabled" msgid="3511469499240123019">"GPS सक्षम केले."</string>
-    <string name="accessibility_gps_acquiring" msgid="8959333351058967158">"GPS प्राप्त करीत आहे."</string>
+    <string name="accessibility_gps_acquiring" msgid="8959333351058967158">"GPS प्राप्त करत आहे."</string>
     <string name="accessibility_tty_enabled" msgid="4613200365379426561">"TeleTypewriter सक्षम केले."</string>
     <string name="accessibility_ringer_vibrate" msgid="666585363364155055">"रिंगर कंपन."</string>
     <string name="accessibility_ringer_silent" msgid="9061243307939135383">"रिंगर मूक."</string>
@@ -179,7 +179,7 @@
     <string name="accessibility_recents_item_dismissed" msgid="6803574935084867070">"<xliff:g id="APP">%s</xliff:g> डिसमिस केला."</string>
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"अलीकडील सर्व अॅप्लिकेशन डिसमिस झाले."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> अॅप्लिकेशन माहिती उघडा."</string>
-    <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> प्रारंभ करीत आहे."</string>
+    <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> प्रारंभ करत आहे."</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"सूचना डिसमिस केल्या."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"सूचना शेड."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"द्रुत सेटिंग्ज."</string>
@@ -310,7 +310,7 @@
     <string name="quick_settings_done" msgid="3402999958839153376">"पूर्ण झाले"</string>
     <string name="quick_settings_connected" msgid="1722253542984847487">"कनेक्ट केलेले"</string>
     <string name="quick_settings_connected_battery_level" msgid="4136051440381328892">"कनेक्ट केलेले आहे, बॅटरी <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
-    <string name="quick_settings_connecting" msgid="47623027419264404">"कनेक्ट करीत आहे..."</string>
+    <string name="quick_settings_connecting" msgid="47623027419264404">"कनेक्ट करत आहे..."</string>
     <string name="quick_settings_tethering_label" msgid="7153452060448575549">"टेदरिंग"</string>
     <string name="quick_settings_hotspot_label" msgid="6046917934974004879">"हॉटस्पॉट"</string>
     <string name="quick_settings_notifications_label" msgid="4818156442169154523">"सूचना"</string>
diff --git a/packages/SystemUI/res/values-ta/config.xml b/packages/SystemUI/res/values-ta/config.xml
index 5309563..9857f13 100644
--- a/packages/SystemUI/res/values-ta/config.xml
+++ b/packages/SystemUI/res/values-ta/config.xml
@@ -22,5 +22,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for config_overviewServiceComponent (2288311504315574053) -->
+    <skip />
     <string name="doze_pickup_subtype_performs_proximity_check" msgid="533127617385956583"></string>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9901f6f..c678111f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -95,6 +95,10 @@
     <!-- Height of a heads up notification in the status bar -->
     <dimen name="notification_max_heads_up_height_increased">188dp</dimen>
 
+    <!-- Height of a messaging notifications with actions at least. Not that this is an upper bound
+         and the notification won't use this much, but is measured with wrap_content -->
+    <dimen name="notification_messaging_actions_min_height">196dp</dimen>
+
     <!-- a threshold in dp per second that is considered fast scrolling -->
     <dimen name="scroll_fast_threshold">1500dp</dimen>
 
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index e5f8029..cfd95b4 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -66,9 +66,6 @@
 
     <!-- For notification icons for which targetSdk < L, this caches whether the icon is grayscale -->
     <item type="id" name="icon_is_grayscale" />
-    <item type="id" name="clip_children_tag" />
-    <item type="id" name="clip_children_set_tag" />
-    <item type="id" name="clip_to_padding_tag" />
     <item type="id" name="image_icon_tag" />
     <item type="id" name="contains_transformed_view" />
     <item type="id" name="is_clicked_heads_up_tag" />
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
new file mode 100644
index 0000000..c18f9b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -0,0 +1,160 @@
+/*
+ * 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.keyguard;
+
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.R;
+import com.android.systemui.keyguard.KeyguardSliceProvider;
+
+/**
+ * View visible under the clock on the lock screen and AoD.
+ */
+public class KeyguardSliceView extends LinearLayout {
+
+    private final Uri mKeyguardSliceUri;
+    private TextView mTitle;
+    private TextView mText;
+    private Slice mSlice;
+    private PendingIntent mSliceAction;
+    private int mTextColor;
+    private float mDarkAmount = 0;
+
+    private final ContentObserver mObserver;
+
+    public KeyguardSliceView(Context context) {
+        this(context, null, 0);
+    }
+
+    public KeyguardSliceView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mObserver = new KeyguardSliceObserver(new Handler());
+        mKeyguardSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mTitle = findViewById(R.id.title);
+        mText = findViewById(R.id.text);
+        mTextColor = mTitle.getCurrentTextColor();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        // Set initial content
+        showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+
+        // Make sure we always have the most current slice
+        getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
+                false /* notifyDescendants */, mObserver);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        getContext().getContentResolver().unregisterContentObserver(mObserver);
+    }
+
+    private void showSlice(Slice slice) {
+        // Items will be wrapped into an action when they have tap targets.
+        SliceItem actionSlice = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+        if (actionSlice != null) {
+            mSlice = actionSlice.getSlice();
+            mSliceAction = actionSlice.getAction();
+        } else {
+            mSlice = slice;
+            mSliceAction = null;
+        }
+
+        if (mSlice == null) {
+            setVisibility(GONE);
+            return;
+        }
+
+        SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null);
+        if (title == null) {
+            mTitle.setVisibility(GONE);
+        } else {
+            mTitle.setVisibility(VISIBLE);
+            mTitle.setText(title.getText());
+        }
+
+        SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE);
+        if (text == null) {
+            mText.setVisibility(GONE);
+        } else {
+            mText.setVisibility(VISIBLE);
+            mText.setText(text.getText());
+        }
+
+        final int visibility = title == null && text == null ? GONE : VISIBLE;
+        if (visibility != getVisibility()) {
+            setVisibility(visibility);
+        }
+    }
+
+    public void setDark(float darkAmount) {
+        mDarkAmount = darkAmount;
+        updateTextColors();
+    }
+
+    public void setTextColor(int textColor) {
+        mTextColor = textColor;
+    }
+
+    private void updateTextColors() {
+        final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+        mTitle.setTextColor(blendedColor);
+        mText.setTextColor(blendedColor);
+    }
+
+    private class KeyguardSliceObserver extends ContentObserver {
+        KeyguardSliceObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            this.onChange(selfChange, null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index bc2a59d..78cf2b9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -19,11 +19,9 @@
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
-import android.graphics.PorterDuff;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -42,8 +40,8 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.settingslib.Utils;
 import com.android.systemui.ChargingView;
-import com.android.systemui.statusbar.policy.DateView;
 
 import java.util.Locale;
 
@@ -55,13 +53,11 @@
     private final LockPatternUtils mLockPatternUtils;
     private final AlarmManager mAlarmManager;
 
-    private TextView mAlarmStatusView;
-    private DateView mDateView;
     private TextClock mClockView;
     private TextView mOwnerInfo;
     private ViewGroup mClockContainer;
     private ChargingView mBatteryDoze;
-    private View mKeyguardStatusArea;
+    private KeyguardSliceView mKeyguardSlice;
     private Runnable mPendingMarqueeStart;
     private Handler mHandler;
 
@@ -69,8 +65,6 @@
     private boolean mPulsing;
     private float mDarkAmount = 0;
     private int mTextColor;
-    private int mDateTextColor;
-    private int mAlarmTextColor;
 
     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
 
@@ -141,7 +135,6 @@
 
     private void setEnableMarqueeImpl(boolean enabled) {
         if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
-        if (mAlarmStatusView != null) mAlarmStatusView.setSelected(enabled);
         if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled);
     }
 
@@ -149,8 +142,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mClockContainer = findViewById(R.id.keyguard_clock_container);
-        mAlarmStatusView = findViewById(R.id.alarm_status);
-        mDateView = findViewById(R.id.date_view);
         mClockView = findViewById(R.id.clock_view);
         mClockView.setShowCurrentUserTime(true);
         if (KeyguardClockAccessibilityDelegate.isNeeded(mContext)) {
@@ -158,11 +149,9 @@
         }
         mOwnerInfo = findViewById(R.id.owner_info);
         mBatteryDoze = findViewById(R.id.battery_doze);
-        mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
-        mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardStatusArea};
+        mKeyguardSlice = findViewById(R.id.keyguard_status_area);
+        mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardSlice};
         mTextColor = mClockView.getCurrentTextColor();
-        mDateTextColor = mDateView.getCurrentTextColor();
-        mAlarmTextColor = mAlarmStatusView.getCurrentTextColor();
 
         boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
         setEnableMarquee(shouldMarquee);
@@ -184,8 +173,6 @@
         layoutParams.bottomMargin = getResources().getDimensionPixelSize(
                 R.dimen.bottom_text_spacing_digital);
         mClockView.setLayoutParams(layoutParams);
-        mDateView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
-                getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
         if (mOwnerInfo != null) {
             mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                     getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
@@ -193,8 +180,6 @@
     }
 
     public void refreshTime() {
-        mDateView.setDatePattern(Patterns.dateViewSkel);
-
         mClockView.setFormat12Hour(Patterns.clockView12);
         mClockView.setFormat24Hour(Patterns.clockView24);
     }
@@ -205,23 +190,11 @@
         Patterns.update(mContext, nextAlarm != null);
 
         refreshTime();
-        refreshAlarmStatus(nextAlarm);
-    }
-
-    void refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm) {
-        if (nextAlarm != null) {
-            String alarm = formatNextAlarm(mContext, nextAlarm);
-            mAlarmStatusView.setText(alarm);
-            mAlarmStatusView.setContentDescription(
-                    getResources().getString(R.string.keyguard_accessibility_next_alarm, alarm));
-            mAlarmStatusView.setVisibility(View.VISIBLE);
-        } else {
-            mAlarmStatusView.setVisibility(View.GONE);
-        }
     }
 
     public int getClockBottom() {
-        return mKeyguardStatusArea.getBottom();
+        return mKeyguardSlice.getVisibility() == VISIBLE ? mKeyguardSlice.getBottom()
+                : mClockView.getBottom();
     }
 
     public float getClockTextSize() {
@@ -341,11 +314,8 @@
 
         updateDozeVisibleViews();
         mBatteryDoze.setDark(dark);
+        mKeyguardSlice.setDark(darkAmount);
         mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
-        mDateView.setTextColor(ColorUtils.blendARGB(mDateTextColor, Color.WHITE, darkAmount));
-        int blendedAlarmColor = ColorUtils.blendARGB(mAlarmTextColor, Color.WHITE, darkAmount);
-        mAlarmStatusView.setTextColor(blendedAlarmColor);
-        mAlarmStatusView.setCompoundDrawableTintList(ColorStateList.valueOf(blendedAlarmColor));
     }
 
     public void setPulsing(boolean pulsing) {
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index 3878cd1..528e242 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -70,7 +70,7 @@
     private final DeviceProvisionedListener mDeviceProvisionedCallback =
                 new DeviceProvisionedListener() {
             @Override
-            public void onDeviceProvisionedChanged() {
+            public void onUserSetupChanged() {
                 if (mDeviceProvisionedController.isCurrentUserSetup()) {
                     startConnectionToCurrentUser();
                 }
@@ -78,7 +78,6 @@
 
             @Override
             public void onUserSwitched() {
-                disconnectFromLauncherService();
                 mConnectionBackoffAttempts = 0;
                 startConnectionToCurrentUser();
             }
@@ -100,8 +99,10 @@
     }
 
     public void startConnectionToCurrentUser() {
+        disconnectFromLauncherService();
+
         // If user has not setup yet or already connected, do not try to connect
-        if (!mDeviceProvisionedController.isCurrentUserSetup() || mOverviewProxy != null) {
+        if (!mDeviceProvisionedController.isCurrentUserSetup()) {
             return;
         }
         mHandler.removeCallbacks(mConnectionRunnable);
@@ -124,7 +125,9 @@
     }
 
     private void disconnectFromLauncherService() {
-        mContext.unbindService(mOverviewServiceConnection);
-        mOverviewProxy = null;
+        if (mOverviewProxy != null) {
+            mContext.unbindService(mOverviewServiceConnection);
+            mOverviewProxy = null;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 98b1106..6650cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -92,6 +92,7 @@
 
     @Override
     protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dumpOnHandler(fd, pw, args);
         if (mDozeMachine != null) {
             mDozeMachine.dump(pw);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
new file mode 100644
index 0000000..03018f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -0,0 +1,159 @@
+/*
+ * 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.keyguard;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.icu.text.DateFormat;
+import android.icu.text.DisplayContext;
+import android.net.Uri;
+import android.os.Handler;
+import android.app.slice.Slice;
+import android.app.slice.SliceProvider;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Simple Slice provider that shows the current date.
+ */
+public class KeyguardSliceProvider extends SliceProvider {
+
+    public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
+
+    private final Date mCurrentTime = new Date();
+    protected final Uri mSliceUri;
+    private final Handler mHandler;
+    private String mDatePattern;
+    private DateFormat mDateFormat;
+    private String mLastText;
+    private boolean mRegistered;
+    private boolean mRegisteredEveryMinute;
+
+    /**
+     * Receiver responsible for time ticking and updating the date format.
+     */
+    @VisibleForTesting
+    final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_TIME_TICK.equals(action)
+                    || Intent.ACTION_DATE_CHANGED.equals(action)
+                    || Intent.ACTION_TIME_CHANGED.equals(action)
+                    || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
+                    || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+                if (Intent.ACTION_LOCALE_CHANGED.equals(action)
+                        || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
+                    // need to get a fresh date format
+                    mHandler.post(KeyguardSliceProvider.this::cleanDateFormat);
+                }
+                mHandler.post(KeyguardSliceProvider.this::updateClock);
+            }
+        }
+    };
+
+    public KeyguardSliceProvider() {
+        this(new Handler());
+    }
+
+    @VisibleForTesting
+    KeyguardSliceProvider(Handler handler) {
+        mHandler = handler;
+        mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
+    }
+
+    @Override
+    public Slice onBindSlice(Uri sliceUri) {
+        return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build();
+    }
+
+    @Override
+    public boolean onCreate() {
+
+        mDatePattern = getContext().getString(R.string.system_ui_date_pattern);
+
+        registerClockUpdate(false /* everyMinute */);
+        updateClock();
+        return true;
+    }
+
+    protected void registerClockUpdate(boolean everyMinute) {
+        if (mRegistered) {
+            if (mRegisteredEveryMinute == everyMinute) {
+                return;
+            } else {
+                unregisterClockUpdate();
+            }
+        }
+
+        IntentFilter filter = new IntentFilter();
+        if (everyMinute) {
+            filter.addAction(Intent.ACTION_TIME_TICK);
+        }
+        filter.addAction(Intent.ACTION_DATE_CHANGED);
+        filter.addAction(Intent.ACTION_TIME_CHANGED);
+        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+        getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
+                null /* scheduler */);
+        mRegistered = true;
+        mRegisteredEveryMinute = everyMinute;
+    }
+
+    protected void unregisterClockUpdate() {
+        if (!mRegistered) {
+            return;
+        }
+        getContext().unregisterReceiver(mIntentReceiver);
+        mRegistered = false;
+    }
+
+    @VisibleForTesting
+    boolean isRegistered() {
+        return mRegistered;
+    }
+
+    protected void updateClock() {
+        final String text = getFormattedDate();
+        if (!text.equals(mLastText)) {
+            mLastText = text;
+            getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+        }
+    }
+
+    protected String getFormattedDate() {
+        if (mDateFormat == null) {
+            final Locale l = Locale.getDefault();
+            DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l);
+            format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
+            mDateFormat = format;
+        }
+        mCurrentTime.setTime(System.currentTimeMillis());
+        return mDateFormat.format(mCurrentTime);
+    }
+
+    @VisibleForTesting
+    void cleanDateFormat() {
+        mDateFormat = null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 6c5f4b2..8ff950e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -69,6 +69,7 @@
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationInflater;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -453,6 +454,11 @@
         } else {
             headsUpheight = mMaxHeadsUpHeight;
         }
+        NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper(
+                NotificationContentView.VISIBLE_TYPE_HEADSUP);
+        if (headsUpWrapper != null) {
+            headsUpheight = Math.max(headsUpheight, headsUpWrapper.getMinLayoutHeight());
+        }
         layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
                 mNotificationAmbientHeight);
     }
@@ -1256,16 +1262,21 @@
     }
 
     private void initDimens() {
-        mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
-        mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
-        mNotificationMinHeightLarge = getFontScaledHeight(
+        mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_min_height_legacy);
+        mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_min_height);
+        mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_min_height_increased);
-        mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
-        mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height);
-        mMaxHeadsUpHeightLegacy = getFontScaledHeight(
+        mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_max_height);
+        mNotificationAmbientHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_ambient_height);
+        mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height_legacy);
-        mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
-        mMaxHeadsUpHeightIncreased = getFontScaledHeight(
+        mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_max_heads_up_height);
+        mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height_increased);
 
         Resources res = getResources();
@@ -1280,17 +1291,6 @@
     }
 
     /**
-     * @param dimenId the dimen to look up
-     * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
-     */
-    private int getFontScaledHeight(int dimenId) {
-        int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId);
-        float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity /
-                getResources().getDisplayMetrics().density);
-        return (int) (dimensionPixelSize * factor);
-    }
-
-    /**
      * Resets this view so it can be re-used for an updated notification.
      */
     public void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 5353005..09b11c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -35,7 +35,8 @@
 /**
  * A view that can be transformed to and from.
  */
-public class ViewTransformationHelper implements TransformableView {
+public class ViewTransformationHelper implements TransformableView,
+        TransformState.TransformInfo {
 
     private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
 
@@ -59,7 +60,7 @@
     public TransformState getCurrentState(int fadingView) {
         View view = mTransformedViews.get(fadingView);
         if (view != null && view.getVisibility() != View.GONE) {
-            return TransformState.createFrom(view);
+            return TransformState.createFrom(view, this);
         }
         return null;
     }
@@ -88,6 +89,7 @@
                         endRunnable.run();
                     }
                     setVisible(false);
+                    mViewTransformationAnimation = null;
                 } else {
                     abortTransformations();
                 }
@@ -245,7 +247,7 @@
     }
 
     public void resetTransformedView(View view) {
-        TransformState state = TransformState.createFrom(view);
+        TransformState state = TransformState.createFrom(view, this);
         state.setVisible(true /* visible */, true /* force */);
         state.recycle();
     }
@@ -257,6 +259,11 @@
         return new ArraySet<>(mTransformedViews.values());
     }
 
+    @Override
+    public boolean isAnimating() {
+        return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning();
+    }
+
     public static abstract class CustomTransformation {
         /**
          * Transform a state to the given view
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index 92f26d6..8227b77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -39,8 +39,8 @@
     private Icon mIcon;
 
     @Override
-    public void initFrom(View view) {
-        super.initFrom(view);
+    public void initFrom(View view, TransformInfo transformInfo) {
+        super.initFrom(view, transformInfo);
         if (view instanceof ImageView) {
             mIcon = (Icon) view.getTag(ICON_TAG);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
new file mode 100644
index 0000000..4c2a2f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.content.res.Resources;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingLayout;
+import com.android.internal.widget.MessagingLinearLayout;
+import com.android.internal.widget.MessagingMessage;
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * A transform state of the action list
+*/
+public class MessagingLayoutTransformState extends TransformState {
+
+    private static Pools.SimplePool<MessagingLayoutTransformState> sInstancePool
+            = new Pools.SimplePool<>(40);
+    private MessagingLinearLayout mMessageContainer;
+    private MessagingLayout mMessagingLayout;
+    private HashMap<MessagingGroup, MessagingGroup> mGroupMap = new HashMap<>();
+    private float mRelativeTranslationOffset;
+
+    public static MessagingLayoutTransformState obtain() {
+        MessagingLayoutTransformState instance = sInstancePool.acquire();
+        if (instance != null) {
+            return instance;
+        }
+        return new MessagingLayoutTransformState();
+    }
+
+    @Override
+    public void initFrom(View view, TransformInfo transformInfo) {
+        super.initFrom(view, transformInfo);
+        if (mTransformedView instanceof MessagingLinearLayout) {
+            mMessageContainer = (MessagingLinearLayout) mTransformedView;
+            mMessagingLayout = mMessageContainer.getMessagingLayout();
+            Resources resources = view.getContext().getResources();
+            mRelativeTranslationOffset = resources.getDisplayMetrics().density * 8;
+        }
+    }
+
+    @Override
+    public boolean transformViewTo(TransformState otherState, float transformationAmount) {
+        if (otherState instanceof MessagingLayoutTransformState) {
+            // It's a party! Let's transform between these two layouts!
+            transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+                    true /* to */);
+            return true;
+        } else {
+            return super.transformViewTo(otherState, transformationAmount);
+        }
+    }
+
+    @Override
+    public void transformViewFrom(TransformState otherState, float transformationAmount) {
+        if (otherState instanceof MessagingLayoutTransformState) {
+            // It's a party! Let's transform between these two layouts!
+            transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+                    false /* to */);
+        } else {
+            super.transformViewFrom(otherState, transformationAmount);
+        }
+    }
+
+    private void transformViewInternal(MessagingLayoutTransformState mlt,
+            float transformationAmount, boolean to) {
+        ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
+                mMessagingLayout.getMessagingGroups());
+        ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
+                mlt.mMessagingLayout.getMessagingGroups());
+        HashMap<MessagingGroup, MessagingGroup> pairs = findPairs(ownGroups, otherGroups);
+        MessagingGroup lastPairedGroup = null;
+        float currentTranslation = 0;
+        float transformationDistanceRemaining = 0;
+        for (int i = ownGroups.size() - 1; i >= 0; i--) {
+            MessagingGroup ownGroup = ownGroups.get(i);
+            MessagingGroup matchingGroup = pairs.get(ownGroup);
+            if (!isGone(ownGroup)) {
+                if (matchingGroup != null) {
+                    transformGroups(ownGroup, matchingGroup, transformationAmount, to);
+                    if (lastPairedGroup == null) {
+                        lastPairedGroup = ownGroup;
+                        if (to){
+                            float totalTranslation = ownGroup.getTop() - matchingGroup.getTop();
+                            transformationDistanceRemaining
+                                    = matchingGroup.getAvatar().getTranslationY();
+                            currentTranslation = transformationDistanceRemaining - totalTranslation;
+                        } else {
+                            float totalTranslation = matchingGroup.getTop() - ownGroup.getTop();
+                            currentTranslation = ownGroup.getAvatar().getTranslationY();
+                            transformationDistanceRemaining = currentTranslation - totalTranslation;
+                        }
+                    }
+                } else {
+                    float groupTransformationAmount = transformationAmount;
+                    if (lastPairedGroup != null) {
+                        adaptGroupAppear(ownGroup, transformationAmount, currentTranslation,
+                                to);
+                        int distance = lastPairedGroup.getTop() - ownGroup.getTop();
+                        float transformationDistance = mTransformInfo.isAnimating()
+                                ? distance
+                                : ownGroup.getHeight() * 0.75f;
+                        float translationProgress = transformationDistanceRemaining
+                                - (distance - transformationDistance);
+                        groupTransformationAmount =
+                                translationProgress / transformationDistance;
+                        groupTransformationAmount = Math.max(0.0f, Math.min(1.0f,
+                                groupTransformationAmount));
+                        if (to) {
+                            groupTransformationAmount = 1.0f - groupTransformationAmount;
+                        }
+                    }
+                    if (to) {
+                        disappear(ownGroup, groupTransformationAmount);
+                    } else {
+                        appear(ownGroup, groupTransformationAmount);
+                    }
+                }
+            }
+        }
+    }
+
+    private void appear(MessagingGroup ownGroup, float transformationAmount) {
+        MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+        for (int j = 0; j < ownMessages.getChildCount(); j++) {
+            View child = ownMessages.getChildAt(j);
+            if (isGone(child)) {
+                continue;
+            }
+            appear(child, transformationAmount);
+            setClippingDeactivated(child, true);
+        }
+        appear(ownGroup.getAvatar(), transformationAmount);
+        appear(ownGroup.getSender(), transformationAmount);
+        setClippingDeactivated(ownGroup.getSender(), true);
+        setClippingDeactivated(ownGroup.getAvatar(), true);
+    }
+
+    private void adaptGroupAppear(MessagingGroup ownGroup, float transformationAmount,
+            float overallTranslation, boolean to) {
+        float relativeOffset;
+        if (to) {
+            relativeOffset = transformationAmount * mRelativeTranslationOffset;
+        } else {
+            relativeOffset = (1.0f - transformationAmount) * mRelativeTranslationOffset;
+        }
+        if (ownGroup.getSender().getVisibility() != View.GONE) {
+            relativeOffset *= 0.5f;
+        }
+        ownGroup.getMessageContainer().setTranslationY(relativeOffset);
+        ownGroup.setTranslationY(overallTranslation * 0.85f);
+    }
+
+    private void disappear(MessagingGroup ownGroup, float transformationAmount) {
+        MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+        for (int j = 0; j < ownMessages.getChildCount(); j++) {
+            View child = ownMessages.getChildAt(j);
+            if (isGone(child)) {
+                continue;
+            }
+            disappear(child, transformationAmount);
+            setClippingDeactivated(child, true);
+        }
+        disappear(ownGroup.getAvatar(), transformationAmount);
+        disappear(ownGroup.getSender(), transformationAmount);
+        setClippingDeactivated(ownGroup.getSender(), true);
+        setClippingDeactivated(ownGroup.getAvatar(), true);
+    }
+
+    private void appear(View child, float transformationAmount) {
+        if (child.getVisibility() == View.GONE) {
+            return;
+        }
+        TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+        ownState.appear(transformationAmount, null);
+        ownState.recycle();
+    }
+
+    private void disappear(View child, float transformationAmount) {
+        if (child.getVisibility() == View.GONE) {
+            return;
+        }
+        TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+        ownState.disappear(transformationAmount, null);
+        ownState.recycle();
+    }
+
+    private ArrayList<MessagingGroup> filterHiddenGroups(
+            ArrayList<MessagingGroup> groups) {
+        ArrayList<MessagingGroup> result = new ArrayList<>(groups);
+        for (int i = 0; i < result.size(); i++) {
+            MessagingGroup messagingGroup = result.get(i);
+            if (isGone(messagingGroup)) {
+                result.remove(i);
+                i--;
+            }
+        }
+        return result;
+    }
+
+    private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup,
+            float transformationAmount, boolean to) {
+        transformView(transformationAmount, to, ownGroup.getSender(), otherGroup.getSender(),
+                true /* sameAsAny */);
+        transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(),
+                true /* sameAsAny */);
+        MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+        MessagingLinearLayout otherMessages = otherGroup.getMessageContainer();
+        float previousTranslation = 0;
+        for (int i = 0; i < ownMessages.getChildCount(); i++) {
+            View child = ownMessages.getChildAt(ownMessages.getChildCount() - 1 - i);
+            if (isGone(child)) {
+                continue;
+            }
+            int otherIndex = otherMessages.getChildCount() - 1 - i;
+            View otherChild = null;
+            if (otherIndex >= 0) {
+                otherChild = otherMessages.getChildAt(otherIndex);
+                if (isGone(otherChild)) {
+                    otherChild = null;
+                }
+            }
+            if (otherChild == null) {
+                float distanceToTop = child.getTop() + child.getHeight() + previousTranslation;
+                transformationAmount = distanceToTop / child.getHeight();
+                transformationAmount = Math.max(0.0f, Math.min(1.0f, transformationAmount));
+                if (to) {
+                    transformationAmount = 1.0f - transformationAmount;
+                }
+            }
+            transformView(transformationAmount, to, child, otherChild, false /* sameAsAny */);
+            if (otherChild == null) {
+                child.setTranslationY(previousTranslation);
+                setClippingDeactivated(child, true);
+            } else if (to) {
+                float totalTranslation = child.getTop() + ownGroup.getTop()
+                        - otherChild.getTop() - otherChild.getTop();
+                previousTranslation = otherChild.getTranslationY() - totalTranslation;
+            } else {
+                previousTranslation = child.getTranslationY();
+            }
+        }
+    }
+
+    private void transformView(float transformationAmount, boolean to, View ownView,
+            View otherView, boolean sameAsAny) {
+        TransformState ownState = TransformState.createFrom(ownView, mTransformInfo);
+        if (!mTransformInfo.isAnimating()) {
+            ownState.setDefaultInterpolator(Interpolators.LINEAR);
+        }
+        ownState.setIsSameAsAnyView(sameAsAny);
+        if (to) {
+            if (otherView != null) {
+                TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+                ownState.transformViewTo(otherState, transformationAmount);
+                otherState.recycle();
+            } else {
+                ownState.disappear(transformationAmount, null);
+            }
+        } else {
+            if (otherView != null) {
+                TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+                ownState.transformViewFrom(otherState, transformationAmount);
+                otherState.recycle();
+            } else {
+                ownState.appear(transformationAmount, null);
+            }
+        }
+        ownState.recycle();
+    }
+
+    private HashMap<MessagingGroup, MessagingGroup> findPairs(ArrayList<MessagingGroup> ownGroups,
+            ArrayList<MessagingGroup> otherGroups) {
+        mGroupMap.clear();
+        int lastMatch = Integer.MAX_VALUE;
+        for (int i = ownGroups.size() - 1; i >= 0; i--) {
+            MessagingGroup ownGroup = ownGroups.get(i);
+            MessagingGroup bestMatch = null;
+            int bestCompatibility = 0;
+            for (int j = Math.min(otherGroups.size(), lastMatch) - 1; j >= 0; j--) {
+                MessagingGroup otherGroup = otherGroups.get(j);
+                int compatibility = ownGroup.calculateGroupCompatibility(otherGroup);
+                if (compatibility > bestCompatibility) {
+                    bestCompatibility = compatibility;
+                    bestMatch = otherGroup;
+                    lastMatch = j;
+                }
+            }
+            if (bestMatch != null) {
+                mGroupMap.put(ownGroup, bestMatch);
+            }
+        }
+        return mGroupMap;
+    }
+
+    private boolean isGone(View view) {
+        if (view.getVisibility() == View.GONE) {
+            return true;
+        }
+        final ViewGroup.LayoutParams lp = view.getLayoutParams();
+        if (lp instanceof MessagingLinearLayout.LayoutParams
+                && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void setVisible(boolean visible, boolean force) {
+        resetTransformedView();
+        ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+        for (int i = 0; i < ownGroups.size(); i++) {
+            MessagingGroup ownGroup = ownGroups.get(i);
+            if (!isGone(ownGroup)) {
+                MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+                for (int j = 0; j < ownMessages.getChildCount(); j++) {
+                    MessagingMessage child = (MessagingMessage) ownMessages.getChildAt(j);
+                    setVisible(child, visible, force);
+                }
+                setVisible(ownGroup.getAvatar(), visible, force);
+                setVisible(ownGroup.getSender(), visible, force);
+            }
+        }
+    }
+
+    private void setVisible(View child, boolean visible, boolean force) {
+        if (isGone(child) || MessagingPropertyAnimator.isAnimatingAlpha(child)) {
+            return;
+        }
+        TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+        ownState.setVisible(visible, force);
+        ownState.recycle();
+    }
+
+    @Override
+    protected void resetTransformedView() {
+        super.resetTransformedView();
+        ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+        for (int i = 0; i < ownGroups.size(); i++) {
+            MessagingGroup ownGroup = ownGroups.get(i);
+            if (!isGone(ownGroup)) {
+                MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+                for (int j = 0; j < ownMessages.getChildCount(); j++) {
+                    View child = ownMessages.getChildAt(j);
+                    if (isGone(child)) {
+                        continue;
+                    }
+                    resetTransformedView(child);
+                    setClippingDeactivated(child, false);
+                }
+                resetTransformedView(ownGroup.getAvatar());
+                resetTransformedView(ownGroup.getSender());
+                setClippingDeactivated(ownGroup.getAvatar(), false);
+                setClippingDeactivated(ownGroup.getSender(), false);
+                ownGroup.setTranslationY(0);
+                ownGroup.getMessageContainer().setTranslationY(0);
+            }
+        }
+    }
+
+    private void resetTransformedView(View child) {
+        TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+        ownState.resetTransformedView();
+        ownState.recycle();
+    }
+
+    @Override
+    protected void reset() {
+        super.reset();
+        mMessageContainer = null;
+        mMessagingLayout = null;
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        mGroupMap.clear();;
+        sInstancePool.release(this);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index f6ee1ca..fb5644f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.notification;
 
+import com.android.internal.widget.MessagingLayout;
 import com.android.internal.widget.MessagingLinearLayout;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.TransformableView;
 
@@ -32,41 +34,20 @@
  */
 public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper {
 
-    private View mContractedMessage;
-    private ArrayList<View> mHistoricMessages = new ArrayList<View>();
+    private final int mMinHeightWithActions;
+    private MessagingLayout mMessagingLayout;
+    private MessagingLinearLayout mMessagingLinearLayout;
 
     protected NotificationMessagingTemplateViewWrapper(Context ctx, View view,
             ExpandableNotificationRow row) {
         super(ctx, view, row);
+        mMessagingLayout = (MessagingLayout) view;
+        mMinHeightWithActions = NotificationUtils.getFontScaledHeight(ctx,
+                R.dimen.notification_messaging_actions_min_height);
     }
 
     private void resolveViews() {
-        mContractedMessage = null;
-
-        View container = mView.findViewById(com.android.internal.R.id.notification_messaging);
-        if (container instanceof MessagingLinearLayout
-                && ((MessagingLinearLayout) container).getChildCount() > 0) {
-            MessagingLinearLayout messagingContainer = (MessagingLinearLayout) container;
-
-            int childCount = messagingContainer.getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                View child = messagingContainer.getChildAt(i);
-
-                if (child.getVisibility() == View.GONE
-                        && child instanceof TextView
-                        && !TextUtils.isEmpty(((TextView) child).getText())) {
-                    mHistoricMessages.add(child);
-                }
-
-                // Only consider the first visible child - transforming to a position other than the
-                // first looks bad because we have to move across other messages that are fading in.
-                if (child.getId() == messagingContainer.getContractedChildId()) {
-                    mContractedMessage = child;
-                } else if (child.getVisibility() == View.VISIBLE) {
-                    break;
-                }
-            }
-        }
+        mMessagingLinearLayout = mMessagingLayout.getMessagingLinearLayout();
     }
 
     @Override
@@ -81,16 +62,22 @@
     protected void updateTransformedTypes() {
         // This also clears the existing types
         super.updateTransformedTypes();
-        if (mContractedMessage != null) {
-            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
-                    mContractedMessage);
+        if (mMessagingLinearLayout != null) {
+            mTransformationHelper.addTransformedView(mMessagingLinearLayout.getId(),
+                    mMessagingLinearLayout);
         }
     }
 
     @Override
     public void setRemoteInputVisible(boolean visible) {
-        for (int i = 0; i < mHistoricMessages.size(); i++) {
-            mHistoricMessages.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
+        mMessagingLayout.showHistoricMessages(visible);
+    }
+
+    @Override
+    public int getMinLayoutHeight() {
+        if (mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE) {
+            return mMinHeightWithActions;
         }
+        return super.getMinLayoutHeight();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index bb979eb..fd085d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -41,7 +41,7 @@
     private ProgressBar mProgressBar;
     private TextView mTitle;
     private TextView mText;
-    private View mActionsContainer;
+    protected View mActionsContainer;
     private View mReplyAction;
     private Rect mTmpRect = new Rect();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index 3115361..af393c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -66,4 +66,14 @@
                 Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
     }
 
+    /**
+     * @param dimenId the dimen to look up
+     * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
+     */
+    public static int getFontScaledHeight(Context context, int dimenId) {
+        int dimensionPixelSize = context.getResources().getDimensionPixelSize(dimenId);
+        float factor = Math.max(1.0f, context.getResources().getDisplayMetrics().scaledDensity /
+                context.getResources().getDisplayMetrics().density);
+        return (int) (dimensionPixelSize * factor);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 5200d69..1cd5f15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -190,4 +190,8 @@
     public boolean disallowSingleClick(float x, float y) {
         return false;
     }
+
+    public int getMinLayoutHeight() {
+        return 0;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
index c4aabe4..178c813 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
@@ -33,8 +33,8 @@
     private TextView mText;
 
     @Override
-    public void initFrom(View view) {
-        super.initFrom(view);
+    public void initFrom(View view, TransformInfo transformInfo) {
+        super.initFrom(view, transformInfo);
         if (view instanceof TextView) {
             mText = (TextView) view;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index bafedff..ad07af0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -26,6 +26,8 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
@@ -43,23 +45,46 @@
     public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y;
 
     private static final float UNDEFINED = -1f;
-    private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
-    private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
-    private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
     private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag;
     private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag;
     private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag;
     private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag;
     private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
+    private static ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS
+            = new ViewClippingUtil.ClippingParameters() {
+        @Override
+        public boolean shouldFinish(View view) {
+            if (view instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+                return !row.isChildInGroup();
+            }
+            return false;
+        }
+
+        @Override
+        public void onClippingStateChanged(View view, boolean isClipping) {
+            if (view instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+                if (isClipping) {
+                    row.setClipToActualHeight(true);
+                } else if (row.isChildInGroup()) {
+                    row.setClipToActualHeight(false);
+                }
+            }
+        }
+    };
 
     protected View mTransformedView;
+    protected TransformInfo mTransformInfo;
     private int[] mOwnPosition = new int[2];
     private boolean mSameAsAny;
     private float mTransformationEndY = UNDEFINED;
     private float mTransformationEndX = UNDEFINED;
+    private Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
 
-    public void initFrom(View view) {
+    public void initFrom(View view, TransformInfo transformInfo) {
         mTransformedView = view;
+        mTransformInfo = transformInfo;
     }
 
     /**
@@ -108,13 +133,16 @@
         final View transformedView = mTransformedView;
         boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
         boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
-        boolean transformScale = transformScale(otherState);
+        boolean differentHeight = otherState.getViewHeight() != getViewHeight();
+        boolean differentWidth = otherState.getViewWidth() != getViewWidth();
+        boolean transformScale = transformScale(otherState) && (differentHeight || differentWidth);
         // lets animate the positions correctly
         if (transformationAmount == 0.0f
                 || transformX && getTransformationStartX() == UNDEFINED
                 || transformY && getTransformationStartY() == UNDEFINED
-                || transformScale && getTransformationStartScaleX() == UNDEFINED
-                || transformScale && getTransformationStartScaleY() == UNDEFINED) {
+                || transformScale && getTransformationStartScaleX() == UNDEFINED && differentWidth
+                || transformScale && getTransformationStartScaleY() == UNDEFINED
+                        && differentHeight) {
             int[] otherPosition;
             if (transformationAmount != 0.0f) {
                 otherPosition = otherState.getLaidOutLocationOnScreen();
@@ -132,14 +160,14 @@
                 }
                 // we also want to animate the scale if we're the same
                 View otherView = otherState.getTransformedView();
-                if (transformScale && otherState.getViewWidth() != getViewWidth()) {
+                if (transformScale && differentWidth) {
                     setTransformationStartScaleX(otherState.getViewWidth() * otherView.getScaleX()
                             / (float) getViewWidth());
                     transformedView.setPivotX(0);
                 } else {
                     setTransformationStartScaleX(UNDEFINED);
                 }
-                if (transformScale && otherState.getViewHeight() != getViewHeight()) {
+                if (transformScale && differentHeight) {
                     setTransformationStartScaleY(otherState.getViewHeight() * otherView.getScaleY()
                             / (float) getViewHeight());
                     transformedView.setPivotY(0);
@@ -159,7 +187,7 @@
             }
             setClippingDeactivated(transformedView, true);
         }
-        float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+        float interpolatedValue = mDefaultInterpolator.getInterpolation(
                 transformationAmount);
         if (transformX) {
             float interpolation = interpolatedValue;
@@ -297,7 +325,7 @@
             }
             setClippingDeactivated(transformedView, true);
         }
-        float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+        float interpolatedValue = mDefaultInterpolator.getInterpolation(
                 transformationAmount);
         int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
         int[] ownPosition = getLaidOutLocationOnScreen();
@@ -354,59 +382,8 @@
         }
     }
 
-    public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
-        if (!(transformedView.getParent() instanceof ViewGroup)) {
-            return;
-        }
-        ViewGroup view = (ViewGroup) transformedView.getParent();
-        while (true) {
-            ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
-            if (clipSet == null) {
-                clipSet = new ArraySet<>();
-                view.setTag(CLIP_CLIPPING_SET, clipSet);
-            }
-            Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
-            if (clipChildren == null) {
-                clipChildren = view.getClipChildren();
-                view.setTag(CLIP_CHILDREN_TAG, clipChildren);
-            }
-            Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
-            if (clipToPadding == null) {
-                clipToPadding = view.getClipToPadding();
-                view.setTag(CLIP_TO_PADDING, clipToPadding);
-            }
-            ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
-                    ? (ExpandableNotificationRow) view
-                    : null;
-            if (!deactivated) {
-                clipSet.remove(transformedView);
-                if (clipSet.isEmpty()) {
-                    view.setClipChildren(clipChildren);
-                    view.setClipToPadding(clipToPadding);
-                    view.setTag(CLIP_CLIPPING_SET, null);
-                    if (row != null) {
-                        row.setClipToActualHeight(true);
-                    }
-                }
-            } else {
-                clipSet.add(transformedView);
-                view.setClipChildren(false);
-                view.setClipToPadding(false);
-                if (row != null && row.isChildInGroup()) {
-                    // We still want to clip to the parent's height
-                    row.setClipToActualHeight(false);
-                }
-            }
-            if (row != null && !row.isChildInGroup()) {
-                return;
-            }
-            final ViewParent parent = view.getParent();
-            if (parent instanceof ViewGroup) {
-                view = (ViewGroup) parent;
-            } else {
-                return;
-            }
-        }
+    protected void setClippingDeactivated(final View transformedView, boolean deactivated) {
+        ViewClippingUtil.setClippingDeactivated(transformedView, deactivated, CLIPPING_PARAMETERS);
     }
 
     public int[] getLaidOutLocationOnScreen() {
@@ -423,6 +400,9 @@
         // remove scale
         mOwnPosition[0] -= (1.0f - mTransformedView.getScaleX()) * mTransformedView.getPivotX();
         mOwnPosition[1] -= (1.0f - mTransformedView.getScaleY()) * mTransformedView.getPivotY();
+
+        // Remove local translations
+        mOwnPosition[1] -= MessagingPropertyAnimator.getLocalTranslationY(mTransformedView);
         return mOwnPosition;
     }
 
@@ -444,20 +424,26 @@
         CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
     }
 
-    public static TransformState createFrom(View view) {
+    public static TransformState createFrom(View view,
+            TransformInfo transformInfo) {
         if (view instanceof TextView) {
             TextViewTransformState result = TextViewTransformState.obtain();
-            result.initFrom(view);
+            result.initFrom(view, transformInfo);
             return result;
         }
         if (view.getId() == com.android.internal.R.id.actions_container) {
             ActionListTransformState result = ActionListTransformState.obtain();
-            result.initFrom(view);
+            result.initFrom(view, transformInfo);
+            return result;
+        }
+        if (view.getId() == com.android.internal.R.id.notification_messaging) {
+            MessagingLayoutTransformState result = MessagingLayoutTransformState.obtain();
+            result.initFrom(view, transformInfo);
             return result;
         }
         if (view instanceof ImageView) {
             ImageTransformState result = ImageTransformState.obtain();
-            result.initFrom(view);
+            result.initFrom(view, transformInfo);
             if (view.getId() == com.android.internal.R.id.reply_icon_action) {
                 ((TransformState) result).setIsSameAsAnyView(true);
             }
@@ -465,15 +451,15 @@
         }
         if (view instanceof ProgressBar) {
             ProgressTransformState result = ProgressTransformState.obtain();
-            result.initFrom(view);
+            result.initFrom(view, transformInfo);
             return result;
         }
         TransformState result = obtain();
-        result.initFrom(view);
+        result.initFrom(view, transformInfo);
         return result;
     }
 
-    private void setIsSameAsAnyView(boolean sameAsAny) {
+    public void setIsSameAsAnyView(boolean sameAsAny) {
         mSameAsAny = sameAsAny;
     }
 
@@ -533,6 +519,7 @@
         mSameAsAny = false;
         mTransformationEndX = UNDEFINED;
         mTransformationEndY = UNDEFINED;
+        mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
     }
 
     public void setVisible(boolean visible, boolean force) {
@@ -578,4 +565,12 @@
     public View getTransformedView() {
         return mTransformedView;
     }
+
+    public void setDefaultInterpolator(Interpolator interpolator) {
+        mDefaultInterpolator = interpolator;
+    }
+
+    public interface TransformInfo {
+        boolean isAnimating();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index d226fed..1c361ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -175,9 +175,8 @@
     private boolean isLight(int vis, int barMode, int flag) {
         boolean isTransparentBar = (barMode == MODE_TRANSPARENT
                 || barMode == MODE_LIGHTS_OUT_TRANSPARENT);
-        boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave();
         boolean light = (vis & flag) != 0;
-        return allowLight && light;
+        return isTransparentBar && light;
     }
 
     private boolean animateChange() {
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 4a58c6d..46e3aa1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -146,6 +146,8 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingMessage;
 import com.android.keyguard.KeyguardHostView.OnDismissAction;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -273,7 +275,7 @@
     public static final boolean ENABLE_CHILD_NOTIFICATIONS
             = SystemProperties.getBoolean("debug.child_notifs", true);
     public static final boolean FORCE_REMOTE_INPUT_HISTORY =
-            SystemProperties.getBoolean("debug.force_remoteinput_history", false);
+            SystemProperties.getBoolean("debug.force_remoteinput_history", true);
     private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
 
     protected static final int MSG_HIDE_RECENT_APPS = 1020;
@@ -1232,6 +1234,8 @@
     }
 
     public void onDensityOrFontScaleChanged() {
+        MessagingMessage.dropCache();
+        MessagingGroup.dropCache();
         // start old BaseStatusBar.onDensityOrFontScaleChanged().
         if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
             updateNotificationsOnDensityOrFontScaleChanged();
@@ -1685,8 +1689,9 @@
             clearCurrentMediaNotification();
             updateMediaMetaData(true, true);
         }
-        if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)) {
-            Entry entry = mNotificationData.get(key);
+        Entry entry = mNotificationData.get(key);
+        if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)
+                && entry.row != null && !entry.row.isDismissed()) {
             StatusBarNotification sbn = entry.notification;
 
             Notification.Builder b = Notification.Builder
@@ -1722,6 +1727,7 @@
                 deferRemoval = false;
             }
             if (updated) {
+                Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
                 mKeysKeptForRemoteInput.add(entry.key);
                 return;
             }
@@ -1731,7 +1737,6 @@
             mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
             return;
         }
-        Entry entry = mNotificationData.get(key);
 
         if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
                 && (entry.row != null && !entry.row.isDismissed())) {
@@ -3278,12 +3283,8 @@
     }
 
     void checkBarMode(int mode, int windowState, BarTransitions transitions) {
-        final boolean powerSave = mBatteryController.isPowerSave();
         final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive
-                && windowState != WINDOW_STATE_HIDDEN && !powerSave;
-        if (powerSave && getBarState() == StatusBarState.SHADE) {
-            mode = MODE_WARNING;
-        }
+                && windowState != WINDOW_STATE_HIDDEN;
         transitions.transitionTo(mode, anim);
     }
 
@@ -7117,7 +7118,7 @@
         mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
     }
 
-    private void updateLockscreenNotificationSetting() {
+    protected void updateLockscreenNotificationSetting() {
         final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
                 1,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
new file mode 100644
index 0000000..4437aac
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.keyguard;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+public class KeyguardSliceProviderTest extends SysuiTestCase {
+
+    private TestableKeyguardSliceProvider mProvider;
+
+    @Before
+    public void setup() {
+        mProvider = new TestableKeyguardSliceProvider();
+        mProvider.attachInfo(getContext(), null);
+    }
+
+    @Test
+    public void registersClockUpdate() {
+        Assert.assertTrue("registerClockUpdate should have been called during initialization.",
+                mProvider.isRegistered());
+    }
+
+    @Test
+    public void unregisterClockUpdate() {
+        mProvider.unregisterClockUpdate();
+        Assert.assertFalse("Clock updates should have been unregistered.",
+                mProvider.isRegistered());
+    }
+
+    @Test
+    public void returnsValidSlice() {
+        Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI));
+        SliceItem text = SliceQuery.find(slice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+                null /* nonHints */);
+        Assert.assertNotNull("Slice must provide a title.", text);
+    }
+
+    @Test
+    public void cleansDateFormat() {
+        mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIMEZONE_CHANGED));
+        TestableLooper.get(this).processAllMessages();
+        Assert.assertEquals("Date format should have been cleaned.", 1 /* expected */,
+                mProvider.mCleanDateFormatInvokations);
+    }
+
+    @Test
+    public void updatesClock() {
+        mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIME_TICK));
+        TestableLooper.get(this).processAllMessages();
+        Assert.assertEquals("Clock should have been updated.", 2 /* expected */,
+                mProvider.mUpdateClockInvokations);
+    }
+
+    private class TestableKeyguardSliceProvider extends KeyguardSliceProvider {
+        int mCleanDateFormatInvokations;
+        int mUpdateClockInvokations;
+
+        TestableKeyguardSliceProvider() {
+            super(new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper()));
+        }
+
+        @Override
+        void cleanDateFormat() {
+            super.cleanDateFormat();
+            mCleanDateFormatInvokations++;
+        }
+
+        @Override
+        protected void updateClock() {
+            super.updateClock();
+            mUpdateClockInvokations++;
+        }
+    }
+
+}
diff --git a/packages/VpnDialogs/res/values-ta/strings.xml b/packages/VpnDialogs/res/values-ta/strings.xml
index 81a1984..7daa11b 100644
--- a/packages/VpnDialogs/res/values-ta/strings.xml
+++ b/packages/VpnDialogs/res/values-ta/strings.xml
@@ -17,7 +17,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="prompt" msgid="3183836924226407828">"இணைப்புக் கோரிக்கை"</string>
-    <string name="warning" msgid="809658604548412033">"VPN இணைப்பை அமைக்க <xliff:g id="APP">%s</xliff:g> விழைகிறது அதன்மூலம் இது நெட்வொர்க் ட்ராஃபிக்கைக் கண்காணிக்கும் அனுமதியைப் பெறும். நம்பகமான மூலத்தை மட்டுமே ஏற்கவும். VPN இயக்கத்தில் உள்ளபோது திரையில் மேல் பகுதியில் &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; தோன்றும்."</string>
+    <string name="warning" msgid="809658604548412033">"VPN இணைப்பை அமைக்க <xliff:g id="APP">%s</xliff:g> விழைகிறது அதன்மூலம் இது நெட்வொர்க் ட்ராஃபிக்கைக் கண்காணிக்கும் அனுமதியைப் பெறும். நம்பகமான மூலத்தை மட்டுமே ஏற்கவும். &lt;br /&gt; &lt;br /&gt; VPN இயக்கத்தில் உள்ளபோது திரையில் மேல் பகுதியில் &lt;img src=vpn_icon /&gt; தோன்றும்."</string>
     <string name="legacy_title" msgid="192936250066580964">"VPN இணைக்கப்பட்டது"</string>
     <string name="session" msgid="6470628549473641030">"அமர்வு:"</string>
     <string name="duration" msgid="3584782459928719435">"காலஅளவு:"</string>
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index bfbce40..75e8000 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -56,6 +56,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.IProgressListener;
 import android.os.IStoraged;
 import android.os.IVold;
 import android.os.IVoldListener;
@@ -570,7 +571,7 @@
                     }
 
                     // TODO: Reintroduce shouldBenchmark() test
-                    fstrim(0);
+                    fstrim(0, null);
 
                     // invoke the completion callback, if any
                     // TODO: fstrim is non-blocking, so remove this useless callback
@@ -1576,21 +1577,19 @@
     }
 
     @Override
-    public long benchmark(String volId) {
+    public void benchmark(String volId, IVoldTaskListener listener) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
 
-        // TODO: refactor for callers to provide a listener
         try {
-            final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
             mVold.benchmark(volId, new IVoldTaskListener.Stub() {
                 @Override
                 public void onStatus(int status, PersistableBundle extras) {
-                    // Not currently used
+                    dispatchOnStatus(listener, status, extras);
                 }
 
                 @Override
                 public void onFinished(int status, PersistableBundle extras) {
-                    result.complete(extras);
+                    dispatchOnFinished(listener, status, extras);
 
                     final String path = extras.getString("path");
                     final String ident = extras.getString("ident");
@@ -1611,10 +1610,8 @@
                     }
                 }
             });
-            return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE);
-        } catch (Exception e) {
-            Slog.wtf(TAG, e);
-            return Long.MAX_VALUE;
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -1742,13 +1739,15 @@
     }
 
     @Override
-    public void fstrim(int flags) {
+    public void fstrim(int flags, IVoldTaskListener listener) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
 
         try {
             mVold.fstrim(flags, new IVoldTaskListener.Stub() {
                 @Override
                 public void onStatus(int status, PersistableBundle extras) {
+                    dispatchOnStatus(listener, status, extras);
+
                     // Ignore trim failures
                     if (status != 0) return;
 
@@ -1770,12 +1769,13 @@
 
                 @Override
                 public void onFinished(int status, PersistableBundle extras) {
-                    // Not currently used
+                    dispatchOnFinished(listener, status, extras);
+
                     // TODO: benchmark when desired
                 }
             });
-        } catch (Exception e) {
-            Slog.wtf(TAG, e);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -3239,6 +3239,26 @@
         }
     }
 
+    private void dispatchOnStatus(IVoldTaskListener listener, int status,
+            PersistableBundle extras) {
+        if (listener != null) {
+            try {
+                listener.onStatus(status, extras);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    private void dispatchOnFinished(IVoldTaskListener listener, int status,
+            PersistableBundle extras) {
+        if (listener != null) {
+            try {
+                listener.onFinished(status, extras);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
     private static class Callbacks extends Handler {
         private static final int MSG_STORAGE_STATE_CHANGED = 1;
         private static final int MSG_VOLUME_STATE_CHANGED = 2;
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index 5a7e7ce..7896e2d 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -95,8 +95,8 @@
     };
 
     private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> {
-        final ActivityRecord r = (ActivityRecord) m.obj;
-        l.onActivityPinned(r.packageName, r.userId, r.getTask().taskId, r.getStackId());
+        l.onActivityPinned((String) m.obj /* packageName */, m.sendingUid /* userId */,
+                m.arg1 /* taskId */, m.arg2 /* stackId */);
     };
 
     private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> {
@@ -281,7 +281,9 @@
     /** Notifies all listeners when an Activity is pinned. */
     void notifyActivityPinned(ActivityRecord r) {
         mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
-        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG, r);
+        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
+                r.getTask().taskId, r.getStackId(), r.packageName);
+        msg.sendingUid = r.userId;
         forAllLocalListeners(mNotifyActivityPinned, msg);
         msg.sendToTarget();
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5eb2a8d..3f23737 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -122,6 +122,7 @@
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.server.EventLogTags;
@@ -398,8 +399,9 @@
      * {@link AudioManager#RINGER_MODE_SILENT}, or
      * {@link AudioManager#RINGER_MODE_VIBRATE}.
      */
-    // protected by mSettingsLock
+    @GuardedBy("mSettingsLock")
     private int mRingerMode;  // internal ringer mode, affects muting of underlying streams
+    @GuardedBy("mSettingsLock")
     private int mRingerModeExternal = -1;  // reported ringer mode to outside clients (AudioManager)
 
     /** @see System#MODE_RINGER_STREAMS_AFFECTED */
@@ -929,8 +931,11 @@
         mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm,
                 "onAudioServerDied"));
         AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm);
-        final int forSys = mCameraSoundForced ?
-                AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
+        final int forSys;
+        synchronized (mSettingsLock) {
+            forSys = mCameraSoundForced ?
+                    AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
+        }
         mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys,
                 "onAudioServerDied"));
         AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys);
@@ -3797,6 +3802,7 @@
         return (mRingerModeMutedStreams & (1 << streamType)) != 0;
     }
 
+    @GuardedBy("mSettingsLock")
     private boolean updateRingerModeAffectedStreams() {
         int ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver,
                 Settings.System.MODE_RINGER_STREAMS_AFFECTED,
@@ -3810,12 +3816,10 @@
             ringerModeAffectedStreams = mRingerModeDelegate
                     .getRingerModeAffectedStreams(ringerModeAffectedStreams);
         }
-        synchronized (mCameraSoundForced) {
-            if (mCameraSoundForced) {
-                ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
-            } else {
-                ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
-            }
+        if (mCameraSoundForced) {
+            ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+        } else {
+            ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
         }
         if (mStreamVolumeAlias[AudioSystem.STREAM_DTMF] == AudioSystem.STREAM_RING) {
             ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
@@ -4185,7 +4189,6 @@
     //  2   mSetModeDeathHandlers
     //  3     mSettingsLock
     //  4       VolumeStreamState.class
-    //  5         mCameraSoundForced
     public class VolumeStreamState {
         private final int mStreamType;
         private final int mIndexMin;
@@ -4252,27 +4255,28 @@
         }
 
         public void readSettings() {
-            synchronized (VolumeStreamState.class) {
-                // force maximum volume on all streams if fixed volume property is set
-                if (mUseFixedVolume) {
-                    mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
-                    return;
-                }
-                // do not read system stream volume from settings: this stream is always aliased
-                // to another stream type and its volume is never persisted. Values in settings can
-                // only be stale values
-                if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
-                        (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
-                    int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
-                    synchronized (mCameraSoundForced) {
+            synchronized (mSettingsLock) {
+                synchronized (VolumeStreamState.class) {
+                    // force maximum volume on all streams if fixed volume property is set
+                    if (mUseFixedVolume) {
+                        mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+                        return;
+                    }
+                    // do not read system stream volume from settings: this stream is always aliased
+                    // to another stream type and its volume is never persisted. Values in settings can
+                    // only be stale values
+                    if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
+                            (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
+                        int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
                         if (mCameraSoundForced) {
                             index = mIndexMax;
                         }
+                        mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
+                        return;
                     }
-                    mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
-                    return;
                 }
-
+            }
+            synchronized (VolumeStreamState.class) {
                 int remainingDevices = AudioSystem.DEVICE_OUT_ALL;
 
                 for (int i = 0; remainingDevices != 0; i++) {
@@ -4385,34 +4389,34 @@
         public boolean setIndex(int index, int device, String caller) {
             boolean changed = false;
             int oldIndex;
-            synchronized (VolumeStreamState.class) {
-                oldIndex = getIndex(device);
-                index = getValidIndex(index);
-                synchronized (mCameraSoundForced) {
+            synchronized (mSettingsLock) {
+                synchronized (VolumeStreamState.class) {
+                    oldIndex = getIndex(device);
+                    index = getValidIndex(index);
                     if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
                         index = mIndexMax;
                     }
-                }
-                mIndexMap.put(device, index);
+                    mIndexMap.put(device, index);
 
-                changed = oldIndex != index;
-                // Apply change to all streams using this one as alias if:
-                // - the index actually changed OR
-                // - there is no volume index stored for this device on alias stream.
-                // If changing volume of current device, also change volume of current
-                // device on aliased stream
-                final boolean currentDevice = (device == getDeviceForStream(mStreamType));
-                final int numStreamTypes = AudioSystem.getNumStreamTypes();
-                for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
-                    final VolumeStreamState aliasStreamState = mStreamStates[streamType];
-                    if (streamType != mStreamType &&
-                            mStreamVolumeAlias[streamType] == mStreamType &&
-                            (changed || !aliasStreamState.hasIndexForDevice(device))) {
-                        final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
-                        aliasStreamState.setIndex(scaledIndex, device, caller);
-                        if (currentDevice) {
-                            aliasStreamState.setIndex(scaledIndex,
-                                    getDeviceForStream(streamType), caller);
+                    changed = oldIndex != index;
+                    // Apply change to all streams using this one as alias if:
+                    // - the index actually changed OR
+                    // - there is no volume index stored for this device on alias stream.
+                    // If changing volume of current device, also change volume of current
+                    // device on aliased stream
+                    final boolean currentDevice = (device == getDeviceForStream(mStreamType));
+                    final int numStreamTypes = AudioSystem.getNumStreamTypes();
+                    for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+                        final VolumeStreamState aliasStreamState = mStreamStates[streamType];
+                        if (streamType != mStreamType &&
+                                mStreamVolumeAlias[streamType] == mStreamType &&
+                                (changed || !aliasStreamState.hasIndexForDevice(device))) {
+                            final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
+                            aliasStreamState.setIndex(scaledIndex, device, caller);
+                            if (currentDevice) {
+                                aliasStreamState.setIndex(scaledIndex,
+                                        getDeviceForStream(streamType), caller);
+                            }
                         }
                     }
                 }
@@ -6022,13 +6026,8 @@
 
             boolean cameraSoundForced = readCameraSoundForced();
             synchronized (mSettingsLock) {
-                boolean cameraSoundForcedChanged = false;
-                synchronized (mCameraSoundForced) {
-                    if (cameraSoundForced != mCameraSoundForced) {
-                        mCameraSoundForced = cameraSoundForced;
-                        cameraSoundForcedChanged = true;
-                    }
-                }
+                final boolean cameraSoundForcedChanged = (cameraSoundForced != mCameraSoundForced);
+                mCameraSoundForced = cameraSoundForced;
                 if (cameraSoundForcedChanged) {
                     if (!mIsSingleVolume) {
                         VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
@@ -6408,11 +6407,12 @@
     //==========================================================================================
 
     // cached value of com.android.internal.R.bool.config_camera_sound_forced
-    private Boolean mCameraSoundForced;
+    @GuardedBy("mSettingsLock")
+    private boolean mCameraSoundForced;
 
     // called by android.hardware.Camera to populate CameraInfo.canDisableShutterSound
     public boolean isCameraSoundForced() {
-        synchronized (mCameraSoundForced) {
+        synchronized (mSettingsLock) {
             return mCameraSoundForced;
         }
     }
@@ -6734,7 +6734,9 @@
         public void setRingerModeDelegate(RingerModeDelegate delegate) {
             mRingerModeDelegate = delegate;
             if (mRingerModeDelegate != null) {
-                updateRingerModeAffectedStreams();
+                synchronized (mSettingsLock) {
+                    updateRingerModeAffectedStreams();
+                }
                 setRingerModeInternal(getRingerModeInternal(), TAG + ".setRingerModeDelegate");
             }
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 45f645b..936ffdf 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1665,14 +1665,10 @@
 
     private long getAccessibilityShortcutTimeout() {
         ViewConfiguration config = ViewConfiguration.get(mContext);
-        try {
-            return Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, mCurrentUserId) == 0
-                    ? config.getAccessibilityShortcutKeyTimeout()
-                    : config.getAccessibilityShortcutKeyTimeoutAfterConfirmation();
-        } catch (Settings.SettingNotFoundException e) {
-            throw new RuntimeException(e);
-        }
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, mCurrentUserId) == 0
+                ? config.getAccessibilityShortcutKeyTimeout()
+                : config.getAccessibilityShortcutKeyTimeoutAfterConfirmation();
     }
 
     private long getScreenshotChordLongPressDelay() {
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index cc4e0f8..76e25ba 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -52,6 +52,8 @@
 
     /**
      * Drag state per operation.
+     * Needs a lock of {@code WindowManagerService#mWindowMap} to read this. Needs both locks of
+     * {@code mWriteLock} and {@code WindowManagerService#mWindowMap} to update this.
      * The variable is cleared by {@code #onDragStateClosedLocked} which is invoked by DragState
      * itself, thus the variable can be null after calling DragState's methods.
      */
@@ -59,6 +61,38 @@
 
     private WindowManagerService mService;
     private final Handler mHandler;
+    /**
+     * Lock to preserve the order of state updates.
+     * The lock is used to process drag and drop state updates in order without having the window
+     * manager lock.
+     *
+     * Suppose DragDropController invokes a callback method A, then processes the following update
+     * A'. Same for a callback method B and the following update B'. The callback wants
+     * DragDropController to processes the updates in the order of  A' then B'.
+     *
+     * Without mWriteLock: the following race can happen.
+     *
+     * 1. Thread a calls A.
+     * 2. Thread b calls B.
+     * 3. Thread b acquires the window manager lock
+     * 4. thread b processes the update B'
+     * 5. Thread a acquires the window manager lock
+     * 6. thread a processes the update A'
+     *
+     * With mWriteLock we can ensure the order of A' and B'
+     *
+     * 1. Thread a acquire mWriteLock
+     * 2. Thread a calls A
+     * 3. Thread a acquire the window manager lock
+     * 4. Thread a processes A'
+     * 5. Thread b acquire mWriteLock
+     * 6. Thread b calls B
+     * 7. Thread b acquire the window manager lock
+     * 8. Thread b processes B'
+     *
+     * Don't acquire the lock while holding the window manager lock, otherwise it causes a deadlock.
+     */
+    private final Object mWriteLock = new Object();
 
     boolean dragDropActiveLocked() {
         return mDragState != null;
@@ -85,46 +119,46 @@
                     + " asbinder=" + window.asBinder());
         }
 
-        IBinder token = null;
+        synchronized (mWriteLock) {
+            synchronized (mService.mWindowMap) {
+                if (dragDropActiveLocked()) {
+                    Slog.w(TAG_WM, "Drag already in progress");
+                    return null;
+                }
 
-        synchronized (mService.mWindowMap) {
-            if (dragDropActiveLocked()) {
-                Slog.w(TAG_WM, "Drag already in progress");
-                return null;
+                // TODO(multi-display): support other displays
+                final DisplayContent displayContent =
+                        mService.getDefaultDisplayContentLocked();
+                final Display display = displayContent.getDisplay();
+
+                final SurfaceControl surface = new SurfaceControl.Builder(session)
+                        .setName("drag surface")
+                        .setSize(width, height)
+                        .setFormat(PixelFormat.TRANSLUCENT)
+                        .build();
+                surface.setLayerStack(display.getLayerStack());
+                float alpha = 1;
+                if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
+                    alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
+                }
+                surface.setAlpha(alpha);
+
+                if (SHOW_TRANSACTIONS)
+                    Slog.i(TAG_WM, "  DRAG " + surface + ": CREATE");
+                outSurface.copyFrom(surface);
+                final IBinder winBinder = window.asBinder();
+                IBinder token = new Binder();
+                mDragState = new DragState(mService, token, surface, flags, winBinder);
+                mDragState.mPid = callerPid;
+                mDragState.mUid = callerUid;
+                mDragState.mOriginalAlpha = alpha;
+                token = mDragState.mToken = new Binder();
+
+                // 5 second timeout for this window to actually begin the drag
+                sendTimeoutMessage(MSG_DRAG_START_TIMEOUT, winBinder);
+                return token;
             }
-
-            // TODO(multi-display): support other displays
-            final DisplayContent displayContent =
-                    mService.getDefaultDisplayContentLocked();
-            final Display display = displayContent.getDisplay();
-
-            final SurfaceControl surface = new SurfaceControl.Builder(session)
-                    .setName("drag surface")
-                    .setSize(width, height)
-                    .setFormat(PixelFormat.TRANSLUCENT)
-                    .build();
-            surface.setLayerStack(display.getLayerStack());
-            float alpha = 1;
-            if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
-                alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
-            }
-            surface.setAlpha(alpha);
-
-            if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  DRAG " + surface + ": CREATE");
-            outSurface.copyFrom(surface);
-            final IBinder winBinder = window.asBinder();
-            token = new Binder();
-            mDragState = new DragState(mService, token, surface, flags, winBinder);
-            mDragState.mPid = callerPid;
-            mDragState.mUid = callerUid;
-            mDragState.mOriginalAlpha = alpha;
-            token = mDragState.mToken = new Binder();
-
-            // 5 second timeout for this window to actually begin the drag
-            sendTimeoutMessage(MSG_DRAG_START_TIMEOUT, winBinder);
         }
-
-        return token;
     }
 
     boolean performDrag(IWindow window, IBinder dragToken,
@@ -134,75 +168,77 @@
             Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data);
         }
 
-        synchronized (mService.mWindowMap) {
-            if (mDragState == null) {
-                Slog.w(TAG_WM, "No drag prepared");
-                throw new IllegalStateException("performDrag() without prepareDrag()");
+        synchronized (mWriteLock) {
+            synchronized (mService.mWindowMap) {
+                if (mDragState == null) {
+                    Slog.w(TAG_WM, "No drag prepared");
+                    throw new IllegalStateException("performDrag() without prepareDrag()");
+                }
+
+                if (dragToken != mDragState.mToken) {
+                    Slog.w(TAG_WM, "Performing mismatched drag");
+                    throw new IllegalStateException("performDrag() does not match prepareDrag()");
+                }
+
+                final WindowState callingWin = mService.windowForClientLocked(null, window, false);
+                if (callingWin == null) {
+                    Slog.w(TAG_WM, "Bad requesting window " + window);
+                    return false;  // !!! TODO: throw here?
+                }
+
+                // !!! TODO: if input is not still focused on the initiating window, fail
+                // the drag initiation (e.g. an alarm window popped up just as the application
+                // called performDrag()
+
+                mHandler.removeMessages(MSG_DRAG_START_TIMEOUT, window.asBinder());
+
+                // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
+                // will let us eliminate the (touchX,touchY) parameters from the API.
+
+                // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as
+                // the actual drag event dispatch stuff in the dragstate
+
+                final DisplayContent displayContent = callingWin.getDisplayContent();
+                if (displayContent == null) {
+                    return false;
+                }
+                Display display = displayContent.getDisplay();
+                mDragState.register(display);
+                if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
+                        mDragState.getInputChannel())) {
+                    Slog.e(TAG_WM, "Unable to transfer touch focus");
+                    mDragState.closeLocked();
+                    return false;
+                }
+
+                mDragState.mDisplayContent = displayContent;
+                mDragState.mData = data;
+                mDragState.broadcastDragStartedLocked(touchX, touchY);
+                mDragState.overridePointerIconLocked(touchSource);
+
+                // remember the thumb offsets for later
+                mDragState.mThumbOffsetX = thumbCenterX;
+                mDragState.mThumbOffsetY = thumbCenterY;
+
+                // Make the surface visible at the proper location
+                final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
+                mService.openSurfaceTransaction();
+                try {
+                    surfaceControl.setPosition(touchX - thumbCenterX,
+                            touchY - thumbCenterY);
+                    surfaceControl.setLayer(mDragState.getDragLayerLocked());
+                    surfaceControl.setLayerStack(display.getLayerStack());
+                    surfaceControl.show();
+                } finally {
+                    mService.closeSurfaceTransaction("performDrag");
+                    if (SHOW_LIGHT_TRANSACTIONS) {
+                        Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
+                    }
+                }
+
+                mDragState.notifyLocationLocked(touchX, touchY);
             }
-
-            if (dragToken != mDragState.mToken) {
-                Slog.w(TAG_WM, "Performing mismatched drag");
-                throw new IllegalStateException("performDrag() does not match prepareDrag()");
-            }
-
-            final WindowState callingWin = mService.windowForClientLocked(null, window, false);
-            if (callingWin == null) {
-                Slog.w(TAG_WM, "Bad requesting window " + window);
-                return false;  // !!! TODO: throw here?
-            }
-
-            // !!! TODO: if input is not still focused on the initiating window, fail
-            // the drag initiation (e.g. an alarm window popped up just as the application
-            // called performDrag()
-
-            mHandler.removeMessages(MSG_DRAG_START_TIMEOUT, window.asBinder());
-
-            // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
-            // will let us eliminate the (touchX,touchY) parameters from the API.
-
-            // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as
-            // the actual drag event dispatch stuff in the dragstate
-
-            final DisplayContent displayContent = callingWin.getDisplayContent();
-            if (displayContent == null) {
-                return false;
-            }
-            Display display = displayContent.getDisplay();
-            mDragState.register(display);
-            if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
-                    mDragState.getInputChannel())) {
-                Slog.e(TAG_WM, "Unable to transfer touch focus");
-                mDragState.closeLocked();
-                return false;
-            }
-
-            mDragState.mDisplayContent = displayContent;
-            mDragState.mData = data;
-            mDragState.broadcastDragStartedLocked(touchX, touchY);
-            mDragState.overridePointerIconLocked(touchSource);
-
-            // remember the thumb offsets for later
-            mDragState.mThumbOffsetX = thumbCenterX;
-            mDragState.mThumbOffsetY = thumbCenterY;
-
-            // Make the surface visible at the proper location
-            final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                    TAG_WM, ">>> OPEN TRANSACTION performDrag");
-            mService.openSurfaceTransaction();
-            try {
-                surfaceControl.setPosition(touchX - thumbCenterX,
-                        touchY - thumbCenterY);
-                surfaceControl.setLayer(mDragState.getDragLayerLocked());
-                surfaceControl.setLayerStack(display.getLayerStack());
-                surfaceControl.show();
-            } finally {
-                mService.closeSurfaceTransaction("performDrag");
-                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                        TAG_WM, "<<< CLOSE TRANSACTION performDrag");
-            }
-
-            mDragState.notifyLocationLocked(touchX, touchY);
         }
 
         return true;    // success!
@@ -214,32 +250,35 @@
             Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
         }
 
-        synchronized (mService.mWindowMap) {
-            if (mDragState == null) {
-                // Most likely the drop recipient ANRed and we ended the drag
-                // out from under it.  Log the issue and move on.
-                Slog.w(TAG_WM, "Drop result given but no drag in progress");
-                return;
-            }
+        synchronized (mWriteLock) {
+            synchronized (mService.mWindowMap) {
+                if (mDragState == null) {
+                    // Most likely the drop recipient ANRed and we ended the drag
+                    // out from under it.  Log the issue and move on.
+                    Slog.w(TAG_WM, "Drop result given but no drag in progress");
+                    return;
+                }
 
-            if (mDragState.mToken != token) {
-                // We're in a drag, but the wrong window has responded.
-                Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
-                throw new IllegalStateException("reportDropResult() by non-recipient");
-            }
+                if (mDragState.mToken != token) {
+                    // We're in a drag, but the wrong window has responded.
+                    Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
+                    throw new IllegalStateException("reportDropResult() by non-recipient");
+                }
 
-            // The right window has responded, even if it's no longer around,
-            // so be sure to halt the timeout even if the later WindowState
-            // lookup fails.
-            mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
-            WindowState callingWin = mService.windowForClientLocked(null, window, false);
-            if (callingWin == null) {
-                Slog.w(TAG_WM, "Bad result-reporting window " + window);
-                return;  // !!! TODO: throw here?
-            }
+                // The right window has responded, even if it's no longer around,
+                // so be sure to halt the timeout even if the later WindowState
+                // lookup fails.
+                mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
+                WindowState callingWin = mService.windowForClientLocked(null, window, false);
+                if (callingWin == null) {
+                    Slog.w(TAG_WM, "Bad result-reporting window " + window);
+                    return;  // !!! TODO: throw here?
+                }
 
-            mDragState.mDragResult = consumed;
-            mDragState.endDragLocked();
+
+                mDragState.mDragResult = consumed;
+                mDragState.endDragLocked();
+            }
         }
     }
 
@@ -248,21 +287,23 @@
             Slog.d(TAG_WM, "cancelDragAndDrop");
         }
 
-        synchronized (mService.mWindowMap) {
-            if (mDragState == null) {
-                Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
-                throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
-            }
+        synchronized (mWriteLock) {
+            synchronized (mService.mWindowMap) {
+                if (mDragState == null) {
+                    Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
+                    throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
+                }
 
-            if (mDragState.mToken != dragToken) {
-                Slog.w(TAG_WM,
-                        "cancelDragAndDrop() does not match prepareDrag()");
-                throw new IllegalStateException(
-                        "cancelDragAndDrop() does not match prepareDrag()");
-            }
+                if (mDragState.mToken != dragToken) {
+                    Slog.w(TAG_WM,
+                            "cancelDragAndDrop() does not match prepareDrag()");
+                    throw new IllegalStateException(
+                            "cancelDragAndDrop() does not match prepareDrag()");
+                }
 
-            mDragState.mDragResult = false;
-            mDragState.cancelDragLocked();
+                mDragState.mDragResult = false;
+                mDragState.cancelDragLocked();
+            }
         }
     }
 
@@ -274,18 +315,20 @@
      * @param newY Y coordinate value in dp in the screen coordinate
      */
     void handleMotionEvent(boolean keepHandling, float newX, float newY) {
-        synchronized (mService.mWindowMap) {
-            if (!dragDropActiveLocked()) {
-                // The drag has ended but the clean-up message has not been processed by
-                // window manager. Drop events that occur after this until window manager
-                // has a chance to clean-up the input handle.
-                return;
-            }
+        synchronized (mWriteLock) {
+            synchronized (mService.mWindowMap) {
+                if (!dragDropActiveLocked()) {
+                    // The drag has ended but the clean-up message has not been processed by
+                    // window manager. Drop events that occur after this until window manager
+                    // has a chance to clean-up the input handle.
+                    return;
+                }
 
-            if (keepHandling) {
-                mDragState.notifyMoveLocked(newX, newY);
-            } else {
-                mDragState.notifyDropLocked(newX, newY);
+                if (keepHandling) {
+                    mDragState.notifyMoveLocked(newX, newY);
+                } else {
+                    mDragState.notifyDropLocked(newX, newY);
+                }
             }
         }
     }
@@ -348,25 +391,29 @@
                     if (DEBUG_DRAG) {
                         Slog.w(TAG_WM, "Timeout starting drag by win " + win);
                     }
-                    synchronized (mService.mWindowMap) {
-                        // !!! TODO: ANR the app that has failed to start the drag in time
-                        if (mDragState != null) {
-                            mDragState.closeLocked();
+                    synchronized (mWriteLock) {
+                        synchronized (mService.mWindowMap) {
+                            // !!! TODO: ANR the app that has failed to start the drag in time
+                            if (mDragState != null) {
+                                mDragState.closeLocked();
+                            }
                         }
                     }
                     break;
                 }
 
                 case MSG_DRAG_END_TIMEOUT: {
-                    IBinder win = (IBinder) msg.obj;
+                    final IBinder win = (IBinder) msg.obj;
                     if (DEBUG_DRAG) {
                         Slog.w(TAG_WM, "Timeout ending drag to win " + win);
                     }
-                    synchronized (mService.mWindowMap) {
-                        // !!! TODO: ANR the drag-receiving app
-                        if (mDragState != null) {
-                            mDragState.mDragResult = false;
-                            mDragState.endDragLocked();
+                    synchronized (mWriteLock) {
+                        synchronized (mService.mWindowMap) {
+                            // !!! TODO: ANR the drag-receiving app
+                            if (mDragState != null) {
+                                mDragState.mDragResult = false;
+                                mDragState.endDragLocked();
+                            }
                         }
                     }
                     break;
@@ -375,23 +422,25 @@
                 case MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT: {
                     if (DEBUG_DRAG)
                         Slog.d(TAG_WM, "Drag ending; tearing down input channel");
-                    DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj;
-                    if (interceptor != null) {
-                        synchronized (mService.mWindowMap) {
-                            interceptor.tearDown();
-                        }
+                    final DragState.InputInterceptor interceptor =
+                            (DragState.InputInterceptor) msg.obj;
+                    if (interceptor == null) return;
+                    synchronized (mService.mWindowMap) {
+                        interceptor.tearDown();
                     }
                     break;
                 }
 
                 case MSG_ANIMATION_END: {
-                    synchronized (mService.mWindowMap) {
-                        if (mDragState == null) {
-                            Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " +
-                                    "plyaing animation");
-                            return;
+                    synchronized (mWriteLock) {
+                        synchronized (mService.mWindowMap) {
+                            if (mDragState == null) {
+                                Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " +
+                                        "plyaing animation");
+                                return;
+                            }
+                            mDragState.closeLocked();
                         }
-                        mDragState.closeLocked();
                     }
                     break;
                 }
diff --git a/services/core/jni/com_android_server_UsbHostManager.cpp b/services/core/jni/com_android_server_UsbHostManager.cpp
index 11f508b..24f2014 100644
--- a/services/core/jni/com_android_server_UsbHostManager.cpp
+++ b/services/core/jni/com_android_server_UsbHostManager.cpp
@@ -65,28 +65,15 @@
     int subClassID = deviceDesc->bDeviceSubClass;
 
     // get the raw descriptors
-    int fd = usb_device_get_fd(device);
-    if (fd < 0) {
-        ALOGE("usb_device_get_fd failed\n");
-        usb_device_close(device);
-        // TODO return an error code here?
-        return 0;
-    }
-
-    // from android_hardware_UsbDeviceConnection_get_desc()
-    jbyte rawdescriptors[MAX_DESCRIPTORS_LENGTH];
-    lseek(fd, 0, SEEK_SET);
-    int numBytes = read(fd, rawdescriptors, sizeof(rawdescriptors));
-
-    usb_device_close(device);
-
+    int numBytes = usb_device_get_descriptors_length(device);
     if (numBytes > 0) {
         JNIEnv* env = AndroidRuntime::getJNIEnv();
         jobject thiz = (jobject)clientData;
         jstring deviceAddress = env->NewStringUTF(devAddress);
 
         jbyteArray descriptorsArray = env->NewByteArray(numBytes);
-        env->SetByteArrayRegion(descriptorsArray, 0, numBytes, rawdescriptors);
+        const jbyte* rawDescriptors = (const jbyte*)usb_device_get_raw_descriptors(device);
+        env->SetByteArrayRegion(descriptorsArray, 0, numBytes, rawDescriptors);
 
         env->CallBooleanMethod(thiz, method_usbDeviceAdded,
                 deviceAddress, classID, subClassID, descriptorsArray);
@@ -100,6 +87,8 @@
         ALOGE("error reading descriptors\n");
     }
 
+    usb_device_close(device);
+
     return 0;
 }
 
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 5c2b66f..31a1abb 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -86,6 +86,14 @@
  */
 public class ApfFilter {
 
+    // Helper class for specifying functional filter parameters.
+    public static class ApfConfiguration {
+        public ApfCapabilities apfCapabilities;
+        public boolean multicastFilter;
+        public boolean ieee802_3Filter;
+        public int[] ethTypeBlackList;
+    }
+
     // Enums describing the outcome of receiving an RA packet.
     private static enum ProcessRaResult {
         MATCH,          // Received RA matched a known RA
@@ -261,17 +269,16 @@
     private int mIPv4PrefixLength;
 
     @VisibleForTesting
-    ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
-            IpClient.Callback ipClientCallback, boolean multicastFilter,
-            boolean ieee802_3Filter, int[] ethTypeBlackList, IpConnectivityLog log) {
-        mApfCapabilities = apfCapabilities;
+    ApfFilter(ApfConfiguration config, NetworkInterface networkInterface,
+            IpClient.Callback ipClientCallback, IpConnectivityLog log) {
+        mApfCapabilities = config.apfCapabilities;
         mIpClientCallback = ipClientCallback;
         mNetworkInterface = networkInterface;
-        mMulticastFilter = multicastFilter;
-        mDrop802_3Frames = ieee802_3Filter;
+        mMulticastFilter = config.multicastFilter;
+        mDrop802_3Frames = config.ieee802_3Filter;
 
         // Now fill the black list from the passed array
-        mEthTypeBlackList = filterEthTypeBlackList(ethTypeBlackList);
+        mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList);
 
         mMetricsLog = log;
 
@@ -1160,9 +1167,10 @@
      * Create an {@link ApfFilter} if {@code apfCapabilities} indicates support for packet
      * filtering using APF programs.
      */
-    public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
-            NetworkInterface networkInterface, IpClient.Callback ipClientCallback,
-            boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList) {
+    public static ApfFilter maybeCreate(ApfConfiguration config,
+            NetworkInterface networkInterface, IpClient.Callback ipClientCallback) {
+        if (config == null) return null;
+        ApfCapabilities apfCapabilities =  config.apfCapabilities;
         if (apfCapabilities == null || networkInterface == null) return null;
         if (apfCapabilities.apfVersionSupported == 0) return null;
         if (apfCapabilities.maximumApfProgramSize < 512) {
@@ -1178,8 +1186,7 @@
             Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
             return null;
         }
-        return new ApfFilter(apfCapabilities, networkInterface, ipClientCallback,
-                multicastFilter, ieee802_3Filter, ethTypeBlackList, new IpConnectivityLog());
+        return new ApfFilter(config, networkInterface, ipClientCallback, new IpConnectivityLog());
     }
 
     public synchronized void shutdown() {
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 24f1cf3..70983c8 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -1429,15 +1429,15 @@
 
         @Override
         public void enter() {
+            ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
+            apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
+            apfConfig.multicastFilter = mMulticastFiltering;
             // Get the Configuration for ApfFilter from Context
-            final boolean filter802_3Frames =
+            apfConfig.ieee802_3Filter =
                     mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
-
-            final int[] ethTypeBlackList = mContext.getResources().getIntArray(
-                    R.array.config_apfEthTypeBlackList);
-
-            mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
-                    mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
+            apfConfig.ethTypeBlackList =
+                    mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
+            mApfFilter = ApfFilter.maybeCreate(apfConfig, mNetworkInterface, mCallback);
             // TODO: investigate the effects of any multicast filtering racing/interfering with the
             // rest of this IP configuration startup.
             if (mApfFilter == null) {
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 924f0de..5d03926 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -338,16 +338,18 @@
     /**
      * Send a text based SMS without writing it into the SMS Provider.
      *
+     * <p>
+     * The message will be sent directly over the network and will not be visible in SMS
+     * applications. Intended for internal carrier use only.
+     * </p>
+     *
      * <p>Requires Permission:
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
      * privileges.
      * </p>
      *
      * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
-     * @hide
      */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void sendTextMessageWithoutPersisting(
             String destinationAddress, String scAddress, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index f2de7db..c6b4a54 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -92,6 +92,15 @@
             </intent-filter>
         </activity>
         <activity
+            android:name=".TrivialAnimationActivityWideGamut"
+            android:label="General/Trivial Animation (Wide Gamut)"
+            android:colorMode="wideColorGamut">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.uibench.TEST" />
+            </intent-filter>
+        </activity>
+        <activity
             android:name=".TrivialListActivity"
             android:label="General/Trivial ListView" >
             <intent-filter>
diff --git a/tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java b/tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java
new file mode 100644
index 0000000..c492753
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java
@@ -0,0 +1,18 @@
+/*
+ * 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.test.uibench;
+
+public class TrivialAnimationActivityWideGamut extends TrivialAnimationActivity { }
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
new file mode 100644
index 0000000..3fa9c3a
--- /dev/null
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import android.net.MacAddress.MacAddressType;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MacAddressTest {
+
+    static class AddrTypeTestCase {
+        byte[] addr;
+        MacAddressType expected;
+
+        static AddrTypeTestCase of(MacAddressType expected, int... addr) {
+            AddrTypeTestCase t = new AddrTypeTestCase();
+            t.expected = expected;
+            t.addr = toByteArray(addr);
+            return t;
+        }
+    }
+
+    @Test
+    public void testMacAddrTypes() {
+        AddrTypeTestCase[] testcases = {
+            AddrTypeTestCase.of(null),
+            AddrTypeTestCase.of(null, 0),
+            AddrTypeTestCase.of(null, 1, 2, 3, 4, 5),
+            AddrTypeTestCase.of(null, 1, 2, 3, 4, 5, 6, 7),
+            AddrTypeTestCase.of(MacAddressType.UNICAST, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0),
+            AddrTypeTestCase.of(MacAddressType.BROADCAST, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff),
+            AddrTypeTestCase.of(MacAddressType.MULTICAST, 1, 2, 3, 4, 5, 6),
+            AddrTypeTestCase.of(MacAddressType.MULTICAST, 11, 22, 33, 44, 55, 66),
+            AddrTypeTestCase.of(MacAddressType.MULTICAST, 33, 33, 0xaa, 0xbb, 0xcc, 0xdd)
+        };
+
+        for (AddrTypeTestCase t : testcases) {
+            MacAddressType got = MacAddress.macAddressType(t.addr);
+            String msg = String.format("expected type of %s to be %s, but got %s",
+                    Arrays.toString(t.addr), t.expected, got);
+            assertEquals(msg, t.expected, got);
+        }
+    }
+
+    static byte[] toByteArray(int[] in) {
+        byte[] out = new byte[in.length];
+        for (int i = 0; i < in.length; i++) {
+            out[i] = (byte) in[i];
+        }
+        return out;
+    }
+}
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 99a2ad9..725ddb9 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -29,9 +29,7 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkUtils;
-import android.net.apf.ApfCapabilities;
-import android.net.apf.ApfFilter;
-import android.net.apf.ApfGenerator;
+import android.net.apf.ApfFilter.ApfConfiguration;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
 import android.net.ip.IpManager;
@@ -99,12 +97,24 @@
     // least the minimum packet size.
     private final static int MIN_PKT_SIZE = 15;
 
+    private static final ApfCapabilities MOCK_APF_CAPABILITIES =
+      new ApfCapabilities(2, 1700, ARPHRD_ETHER);
+
     private final static boolean DROP_MULTICAST = true;
     private final static boolean ALLOW_MULTICAST = false;
 
     private final static boolean DROP_802_3_FRAMES = true;
     private final static boolean ALLOW_802_3_FRAMES = false;
 
+    private static ApfConfiguration getDefaultConfig() {
+        ApfFilter.ApfConfiguration config = new ApfConfiguration();
+        config.apfCapabilities = MOCK_APF_CAPABILITIES;
+        config.multicastFilter = ALLOW_MULTICAST;
+        config.ieee802_3Filter = ALLOW_802_3_FRAMES;
+        config.ethTypeBlackList = new int[0];
+        return config;
+    }
+
     private static String label(int code) {
         switch (code) {
             case PASS: return "PASS";
@@ -619,15 +629,13 @@
 
     private static class TestApfFilter extends ApfFilter {
         public final static byte[] MOCK_MAC_ADDR = {1,2,3,4,5,6};
-        private FileDescriptor mWriteSocket;
 
+        private FileDescriptor mWriteSocket;
         private final long mFixedTimeMs = SystemClock.elapsedRealtime();
 
-        public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter,
-                boolean ieee802_3Filter, int[] ethTypeBlackList,
+        public TestApfFilter(ApfConfiguration config, IpManager.Callback ipManagerCallback,
                 IpConnectivityLog log) throws Exception {
-            super(new ApfCapabilities(2, 1700, ARPHRD_ETHER), NetworkInterface.getByName("lo"),
-                    ipManagerCallback, multicastFilter, ieee802_3Filter, ethTypeBlackList, log);
+            super(config, NetworkInterface.getByName("lo"), ipManagerCallback, log);
         }
 
         // Pretend an RA packet has been received and show it to ApfFilter.
@@ -755,10 +763,10 @@
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
         LinkProperties lp = new LinkProperties();
         lp.addLinkAddress(link);
-        final int[] ethTypeBlackList = {};
 
-        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST,
-                ALLOW_802_3_FRAMES, ethTypeBlackList, mLog);
+        ApfConfiguration config = getDefaultConfig();
+        config.multicastFilter = DROP_MULTICAST;
+        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
         apfFilter.setLinkProperties(lp);
 
         byte[] program = ipManagerCallback.getApfProgram();
@@ -808,10 +816,9 @@
 
     @Test
     public void testApfFilterIPv6() throws Exception {
-        final int[] ethTypeBlackList = {};
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
-        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
-                ALLOW_802_3_FRAMES, ethTypeBlackList, mLog);
+        ApfConfiguration config = getDefaultConfig();
+        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
         byte[] program = ipManagerCallback.getApfProgram();
 
         // Verify empty IPv6 packet is passed
@@ -846,15 +853,15 @@
         final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
         final byte[] multicastIpv4Addr = {(byte)224,0,0,1};
         final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
-        final int[] ethTypeBlackList = {};
 
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
         LinkProperties lp = new LinkProperties();
         lp.addLinkAddress(link);
 
-        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
-                DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+        ApfConfiguration config = getDefaultConfig();
+        config.ieee802_3Filter = DROP_802_3_FRAMES;
+        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
         apfFilter.setLinkProperties(lp);
 
         byte[] program = ipManagerCallback.getApfProgram();
@@ -916,8 +923,9 @@
         // Verify it can be initialized to on
         ipManagerCallback.resetApfProgramWait();
         apfFilter.shutdown();
-        apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST,
-                DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+        config.multicastFilter = DROP_MULTICAST;
+        config.ieee802_3Filter = DROP_802_3_FRAMES;
+        apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
         apfFilter.setLinkProperties(lp);
         program = ipManagerCallback.getApfProgram();
         assertDrop(program, mcastv4packet.array());
@@ -938,10 +946,9 @@
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
         LinkProperties lp = new LinkProperties();
         lp.addLinkAddress(link);
-        final int[] ethTypeBlackList = {};
 
-        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
-                ALLOW_802_3_FRAMES, ethTypeBlackList, mLog);
+        ApfConfiguration config = getDefaultConfig();
+        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
         apfFilter.setLinkProperties(lp);
 
         byte[] program = ipManagerCallback.getApfProgram();
@@ -962,8 +969,8 @@
         // Now turn on the filter
         ipManagerCallback.resetApfProgramWait();
         apfFilter.shutdown();
-        apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
-                DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+        config.ieee802_3Filter = DROP_802_3_FRAMES;
+        apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
         apfFilter.setLinkProperties(lp);
         program = ipManagerCallback.getApfProgram();
 
@@ -993,8 +1000,8 @@
         final int[] ipv4BlackList = {ETH_P_IP};
         final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6};
 
-        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
-                ALLOW_802_3_FRAMES, emptyBlackList, mLog);
+        ApfConfiguration config = getDefaultConfig();
+        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
         apfFilter.setLinkProperties(lp);
 
         byte[] program = ipManagerCallback.getApfProgram();
@@ -1015,8 +1022,8 @@
         // Now add IPv4 to the black list
         ipManagerCallback.resetApfProgramWait();
         apfFilter.shutdown();
-        apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
-                ALLOW_802_3_FRAMES, ipv4BlackList, mLog);
+        config.ethTypeBlackList = ipv4BlackList;
+        apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
         apfFilter.setLinkProperties(lp);
         program = ipManagerCallback.getApfProgram();
 
@@ -1031,8 +1038,8 @@
         // Now let us have both IPv4 and IPv6 in the black list
         ipManagerCallback.resetApfProgramWait();
         apfFilter.shutdown();
-        apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
-                ALLOW_802_3_FRAMES, ipv4Ipv6BlackList, mLog);
+        config.ethTypeBlackList = ipv4Ipv6BlackList;
+        apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
         apfFilter.setLinkProperties(lp);
         program = ipManagerCallback.getApfProgram();
 
@@ -1070,10 +1077,11 @@
 
     @Test
     public void testApfFilterArp() throws Exception {
-        final int[] ethTypeBlackList = {};
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
-        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
-                DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+        ApfConfiguration config = getDefaultConfig();
+        config.multicastFilter = DROP_MULTICAST;
+        config.ieee802_3Filter = DROP_802_3_FRAMES;
+        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
 
         // Verify initially ARP request filter is off, and GARP filter is on.
         verifyArpFilter(ipManagerCallback.getApfProgram(), PASS);
@@ -1194,9 +1202,10 @@
     @Test
     public void testApfFilterRa() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
-        final int[] ethTypeBlackList = {};
-        TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST,
-                DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+        ApfConfiguration config = getDefaultConfig();
+        config.multicastFilter = DROP_MULTICAST;
+        config.ieee802_3Filter = DROP_802_3_FRAMES;
+        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
         byte[] program = ipManagerCallback.getApfProgram();
 
         final int ROUTER_LIFETIME = 1000;
@@ -1338,10 +1347,11 @@
     public void testRaParsing() throws Exception {
         final int maxRandomPacketSize = 512;
         final Random r = new Random();
-        final int[] ethTypeBlackList = {};
         MockIpManagerCallback cb = new MockIpManagerCallback();
-        TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST,
-                DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+        ApfConfiguration config = getDefaultConfig();
+        config.multicastFilter = DROP_MULTICAST;
+        config.ieee802_3Filter = DROP_802_3_FRAMES;
+        TestApfFilter apfFilter = new TestApfFilter(config, cb, mLog);
         for (int i = 0; i < 1000; i++) {
             byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
             r.nextBytes(packet);
@@ -1358,10 +1368,11 @@
     public void testRaProcessing() throws Exception {
         final int maxRandomPacketSize = 512;
         final Random r = new Random();
-        final int[] ethTypeBlackList = {};
         MockIpManagerCallback cb = new MockIpManagerCallback();
-        TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST,
-                DROP_802_3_FRAMES, ethTypeBlackList, mLog);
+        ApfConfiguration config = getDefaultConfig();
+        config.multicastFilter = DROP_MULTICAST;
+        config.ieee802_3Filter = DROP_802_3_FRAMES;
+        TestApfFilter apfFilter = new TestApfFilter(config, cb, mLog);
         for (int i = 0; i < 1000; i++) {
             byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
             r.nextBytes(packet);
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 058504d..eb3a99a 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -16,6 +16,7 @@
 
 toolSources = [
     "cmd/Compile.cpp",
+    "cmd/Convert.cpp",
     "cmd/Diff.cpp",
     "cmd/Dump.cpp",
     "cmd/Link.cpp",
@@ -112,6 +113,7 @@
         "optimize/VersionCollapser.cpp",
         "process/SymbolTable.cpp",
         "split/TableSplitter.cpp",
+        "text/Printer.cpp",
         "text/Unicode.cpp",
         "text/Utf8Iterator.cpp",
         "util/BigBuffer.cpp",
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 59a6e12..f621660 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -882,6 +882,11 @@
   return std::string(locale);
 }
 
+std::string ConfigDescription::to_string() const {
+  const android::String8 str = toString();
+  return std::string(str.string(), str.size());
+}
+
 bool ConfigDescription::Dominates(const ConfigDescription& o) const {
   if (*this == o) {
     return true;
diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h
index c1d0e10..f719552 100644
--- a/tools/aapt2/ConfigDescription.h
+++ b/tools/aapt2/ConfigDescription.h
@@ -64,6 +64,8 @@
   // Returns the BCP-47 language tag of this configuration's locale.
   std::string GetBcp47LanguageTag(bool canonicalize = false) const;
 
+  std::string to_string() const;
+
   /**
    * A configuration X dominates another configuration Y, if X has at least the
    * precedence of Y and X is strictly more general than Y: for any type defined
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 61c304b..08efc27 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -17,7 +17,6 @@
 #include "Debug.h"
 
 #include <algorithm>
-#include <iostream>
 #include <map>
 #include <memory>
 #include <queue>
@@ -25,120 +24,244 @@
 #include <vector>
 
 #include "android-base/logging.h"
+#include "android-base/stringprintf.h"
 
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
+#include "text/Printer.h"
 #include "util/Util.h"
 
+using ::aapt::text::Printer;
+using ::android::StringPiece;
+using ::android::base::StringPrintf;
+
 namespace aapt {
 
 namespace {
 
-class PrintVisitor : public ConstValueVisitor {
+class ValueHeadlinePrinter : public ConstValueVisitor {
  public:
   using ConstValueVisitor::Visit;
 
+  explicit ValueHeadlinePrinter(const std::string& package, Printer* printer)
+      : package_(package), printer_(printer) {
+  }
+
   void Visit(const Attribute* attr) override {
-    std::cout << "(attr) type=";
-    attr->PrintMask(&std::cout);
-    static constexpr uint32_t kMask =
-        android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS;
+    printer_->Print("(attr) type=");
+    printer_->Print(attr->MaskString());
+    if (!attr->symbols.empty()) {
+      printer_->Print(StringPrintf(" size=%zd", attr->symbols.size()));
+    }
+  }
+
+  void Visit(const Style* style) override {
+    printer_->Print(StringPrintf("(style) size=%zd", style->entries.size()));
+    if (style->parent) {
+      printer_->Print(" parent=");
+
+      const Reference& parent_ref = style->parent.value();
+      if (parent_ref.name) {
+        if (parent_ref.private_reference) {
+          printer_->Print("*");
+        }
+
+        const ResourceName& parent_name = parent_ref.name.value();
+        if (package_ != parent_name.package) {
+          printer_->Print(parent_name.package);
+          printer_->Print(":");
+        }
+        printer_->Print(to_string(parent_name.type));
+        printer_->Print("/");
+        printer_->Print(parent_name.entry);
+        if (parent_ref.id) {
+          printer_->Print(" (");
+          printer_->Print(parent_ref.id.value().to_string());
+          printer_->Print(")");
+        }
+      } else if (parent_ref.id) {
+        printer_->Print(parent_ref.id.value().to_string());
+      } else {
+        printer_->Print("???");
+      }
+    }
+  }
+
+  void Visit(const Array* array) override {
+    printer_->Print(StringPrintf("(array) size=%zd", array->elements.size()));
+  }
+
+  void Visit(const Plural* plural) override {
+    size_t count = std::count_if(plural->values.begin(), plural->values.end(),
+                                 [](const std::unique_ptr<Item>& v) { return v != nullptr; });
+    printer_->Print(StringPrintf("(plurals) size=%zd", count));
+  }
+
+  void Visit(const Styleable* styleable) override {
+    printer_->Println(StringPrintf("(styleable) size=%zd", styleable->entries.size()));
+  }
+
+  void VisitItem(const Item* item) override {
+    // Pretty much guaranteed to be one line.
+    if (const Reference* ref = ValueCast<Reference>(item)) {
+      // Special case Reference so that we can print local resources without a package name.
+      ref->PrettyPrint(package_, printer_);
+    } else {
+      item->PrettyPrint(printer_);
+    }
+  }
+
+ private:
+  std::string package_;
+  Printer* printer_;
+};
+
+class ValueBodyPrinter : public ConstValueVisitor {
+ public:
+  using ConstValueVisitor::Visit;
+
+  explicit ValueBodyPrinter(const std::string& package, Printer* printer)
+      : package_(package), printer_(printer) {
+  }
+
+  void Visit(const Attribute* attr) override {
+    constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS;
     if (attr->type_mask & kMask) {
       for (const auto& symbol : attr->symbols) {
-        std::cout << "\n        " << symbol.symbol.name.value().entry;
+        printer_->Print(symbol.symbol.name.value().entry);
         if (symbol.symbol.id) {
-          std::cout << " (" << symbol.symbol.id.value() << ")";
+          printer_->Print("(");
+          printer_->Print(symbol.symbol.id.value().to_string());
+          printer_->Print(")");
         }
-        std::cout << " = " << symbol.value;
+        printer_->Println(StringPrintf("=0x%08x", symbol.value));
       }
     }
   }
 
   void Visit(const Style* style) override {
-    std::cout << "(style)";
-    if (style->parent) {
-      const Reference& parent_ref = style->parent.value();
-      std::cout << " parent=";
-      if (parent_ref.name) {
-        if (parent_ref.private_reference) {
-          std::cout << "*";
-        }
-        std::cout << parent_ref.name.value() << " ";
-      }
-
-      if (parent_ref.id) {
-        std::cout << parent_ref.id.value();
-      }
-    }
-
     for (const auto& entry : style->entries) {
-      std::cout << "\n        ";
       if (entry.key.name) {
         const ResourceName& name = entry.key.name.value();
-        if (!name.package.empty()) {
-          std::cout << name.package << ":";
+        if (!name.package.empty() && name.package != package_) {
+          printer_->Print(name.package);
+          printer_->Print(":");
         }
-        std::cout << name.entry;
+        printer_->Print(name.entry);
+
+        if (entry.key.id) {
+          printer_->Print("(");
+          printer_->Print(entry.key.id.value().to_string());
+          printer_->Print(")");
+        }
+      } else if (entry.key.id) {
+        printer_->Print(entry.key.id.value().to_string());
+      } else {
+        printer_->Print("???");
       }
 
-      if (entry.key.id) {
-        std::cout << "(" << entry.key.id.value() << ")";
-      }
-
-      std::cout << "=" << *entry.value;
+      printer_->Print("=");
+      PrintItem(*entry.value);
+      printer_->Println();
     }
   }
 
   void Visit(const Array* array) override {
-    array->Print(&std::cout);
+    const size_t count = array->elements.size();
+    printer_->Print("[");
+    if (count > 0) {
+      for (size_t i = 0u; i < count; i++) {
+        if (i != 0u && i % 4u == 0u) {
+          printer_->Println();
+          printer_->Print(" ");
+        }
+        PrintItem(*array->elements[i]);
+        if (i != count - 1) {
+          printer_->Print(", ");
+        }
+      }
+      printer_->Println("]");
+    }
   }
 
   void Visit(const Plural* plural) override {
-    plural->Print(&std::cout);
+    constexpr std::array<const char*, Plural::Count> kPluralNames = {
+        {"zero", "one", "two", "few", "many", "other"}};
+
+    for (size_t i = 0; i < Plural::Count; i++) {
+      if (plural->values[i] != nullptr) {
+        printer_->Print(StringPrintf("%s=", kPluralNames[i]));
+        PrintItem(*plural->values[i]);
+        printer_->Println();
+      }
+    }
   }
 
   void Visit(const Styleable* styleable) override {
-    std::cout << "(styleable)";
     for (const auto& attr : styleable->entries) {
-      std::cout << "\n        ";
       if (attr.name) {
         const ResourceName& name = attr.name.value();
-        if (!name.package.empty()) {
-          std::cout << name.package << ":";
+        if (!name.package.empty() && name.package != package_) {
+          printer_->Print(name.package);
+          printer_->Print(":");
         }
-        std::cout << name.entry;
+        printer_->Print(name.entry);
+
+        if (attr.id) {
+          printer_->Print("(");
+          printer_->Print(attr.id.value().to_string());
+          printer_->Print(")");
+        }
       }
 
       if (attr.id) {
-        std::cout << "(" << attr.id.value() << ")";
+        printer_->Print(attr.id.value().to_string());
       }
     }
   }
 
   void VisitItem(const Item* item) override {
-    item->Print(&std::cout);
+    // Intentionally left empty, we already printed the Items.
   }
+
+ private:
+  void PrintItem(const Item& item) {
+    if (const Reference* ref = ValueCast<Reference>(&item)) {
+      // Special case Reference so that we can print local resources without a package name.
+      ref->PrettyPrint(package_, printer_);
+    } else {
+      item.PrettyPrint(printer_);
+    }
+  }
+
+  std::string package_;
+  Printer* printer_;
 };
 
 }  // namespace
 
-void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options) {
-  PrintVisitor visitor;
-
+void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options,
+                       Printer* printer) {
   for (const auto& package : table.packages) {
-    std::cout << "Package name=" << package->name;
-    if (package->id) {
-      std::cout << " id=" << std::hex << (int)package->id.value() << std::dec;
-    }
-    std::cout << std::endl;
+    ValueHeadlinePrinter headline_printer(package->name, printer);
+    ValueBodyPrinter body_printer(package->name, printer);
 
+    printer->Print("Package name=");
+    printer->Print(package->name);
+    if (package->id) {
+      printer->Print(StringPrintf(" id=%02x", package->id.value()));
+    }
+    printer->Println();
+
+    printer->Indent();
     for (const auto& type : package->types) {
-      std::cout << "\n  type " << type->type;
+      printer->Print("type ");
+      printer->Print(to_string(type->type));
       if (type->id) {
-        std::cout << " id=" << std::hex << (int)type->id.value() << std::dec;
+        printer->Print(StringPrintf(" id=%02x", type->id.value()));
       }
-      std::cout << " entryCount=" << type->entries.size() << std::endl;
+      printer->Println(StringPrintf(" entryCount=%zd", type->entries.size()));
 
       std::vector<const ResourceEntry*> sorted_entries;
       for (const auto& entry : type->entries) {
@@ -156,35 +279,54 @@
         sorted_entries.insert(iter, entry.get());
       }
 
+      printer->Indent();
       for (const ResourceEntry* entry : sorted_entries) {
         const ResourceId id(package->id.value_or_default(0), type->id.value_or_default(0),
                             entry->id.value_or_default(0));
-        const ResourceName name(package->name, type->type, entry->name);
 
-        std::cout << "    spec resource " << id << " " << name;
+        printer->Print("resource ");
+        printer->Print(id.to_string());
+        printer->Print(" ");
+
+        // Write the name without the package (this is obvious and too verbose).
+        printer->Print(to_string(type->type));
+        printer->Print("/");
+        printer->Print(entry->name);
+
         switch (entry->symbol_status.state) {
           case SymbolState::kPublic:
-            std::cout << " PUBLIC";
+            printer->Print(" PUBLIC");
             break;
           case SymbolState::kPrivate:
-            std::cout << " _PRIVATE_";
+            printer->Print(" _PRIVATE_");
             break;
-          default:
+          case SymbolState::kUndefined:
+            // Print nothing.
             break;
         }
 
-        std::cout << std::endl;
+        printer->Println();
 
+        printer->Indent();
         for (const auto& value : entry->values) {
-          std::cout << "      (" << value->config << ") ";
-          value->value->Accept(&visitor);
+          printer->Print("(");
+          printer->Print(value->config.to_string());
+          printer->Print(") ");
+          value->value->Accept(&headline_printer);
           if (options.show_sources && !value->value->GetSource().path.empty()) {
-            std::cout << " src=" << value->value->GetSource();
+            printer->Print(" src=");
+            printer->Print(value->value->GetSource().to_string());
           }
-          std::cout << std::endl;
+          printer->Println();
+          printer->Indent();
+          value->value->Accept(&body_printer);
+          printer->Undent();
         }
+        printer->Undent();
       }
+      printer->Undent();
     }
+    printer->Undent();
   }
 }
 
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index 296d04b..3c1ee4c 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -22,6 +22,7 @@
 
 #include "Resource.h"
 #include "ResourceTable.h"
+#include "text/Printer.h"
 #include "xml/XmlDom.h"
 
 namespace aapt {
@@ -31,9 +32,9 @@
 };
 
 struct Debug {
-  static void PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options = {});
-  static void PrintStyleGraph(ResourceTable* table,
-                              const ResourceName& target_style);
+  static void PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options,
+                         text::Printer* printer);
+  static void PrintStyleGraph(ResourceTable* table, const ResourceName& target_style);
   static void DumpHex(const void* data, size_t len);
   static void DumpXml(const xml::XmlResource& doc);
 };
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index ae32ee9..921d853 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -21,43 +21,139 @@
 #include "format/Archive.h"
 #include "format/binary/TableFlattener.h"
 #include "format/binary/XmlFlattener.h"
+#include "format/proto/ProtoDeserialize.h"
 #include "io/BigBufferStream.h"
 #include "io/Util.h"
 #include "xml/XmlDom.h"
 
+using ::aapt::io::IFile;
+using ::aapt::io::IFileCollection;
+using ::aapt::xml::XmlResource;
+using ::android::StringPiece;
+using ::std::unique_ptr;
+
 namespace aapt {
 
-using xml::XmlResource;
-
-std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,
-                                                      const android::StringPiece& path) {
+std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, IDiagnostics* diag) {
   Source source(path);
   std::string error;
   std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
-  if (!apk) {
-    context->GetDiagnostics()->Error(DiagMessage(source) << error);
+  if (apk == nullptr) {
+    diag->Error(DiagMessage(path) << "failed opening zip: " << error);
     return {};
   }
 
-  io::IFile* file = apk->FindFile("resources.arsc");
-  if (!file) {
-    context->GetDiagnostics()->Error(DiagMessage(source) << "no resources.arsc found");
+  if (apk->FindFile("resources.arsc") != nullptr) {
+    return LoadBinaryApkFromFileCollection(source, std::move(apk), diag);
+  } else if (apk->FindFile("resources.pb") != nullptr) {
+    return LoadProtoApkFromFileCollection(source, std::move(apk), diag);
+  }
+  diag->Error(DiagMessage(path) << "no resource table found");
+  return {};
+}
+
+std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection(
+    const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
+  io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
+  if (table_file == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to find " << kProtoResourceTablePath);
     return {};
   }
 
-  std::unique_ptr<io::IData> data = file->OpenAsData();
-  if (!data) {
-    context->GetDiagnostics()->Error(DiagMessage(source) << "could not open resources.arsc");
+  std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
+  if (in == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
+    return {};
+  }
+
+  pb::ResourceTable pb_table;
+  io::ZeroCopyInputAdaptor adaptor(in.get());
+  if (!pb_table.ParseFromZeroCopyStream(&adaptor)) {
+    diag->Error(DiagMessage(source) << "failed to read " << kProtoResourceTablePath);
+    return {};
+  }
+
+  std::string error;
+  std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+  if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) {
+    diag->Error(DiagMessage(source)
+                << "failed to deserialize " << kProtoResourceTablePath << ": " << error);
+    return {};
+  }
+
+  io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
+  if (manifest_file == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
+    return {};
+  }
+
+  std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
+  if (manifest_in == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
+    return {};
+  }
+
+  pb::XmlNode pb_node;
+  io::ZeroCopyInputAdaptor manifest_adaptor(manifest_in.get());
+  if (!pb_node.ParseFromZeroCopyStream(&manifest_adaptor)) {
+    diag->Error(DiagMessage(source) << "failed to read proto " << kAndroidManifestPath);
+    return {};
+  }
+
+  std::unique_ptr<xml::XmlResource> manifest = DeserializeXmlResourceFromPb(pb_node, &error);
+  if (manifest == nullptr) {
+    diag->Error(DiagMessage(source)
+                << "failed to deserialize proto " << kAndroidManifestPath << ": " << error);
+    return {};
+  }
+  return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
+                                      std::move(manifest));
+}
+
+std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
+    const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
+  io::IFile* table_file = collection->FindFile(kApkResourceTablePath);
+  if (table_file == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to find " << kApkResourceTablePath);
+
+    return {};
+  }
+
+  std::unique_ptr<io::IData> data = table_file->OpenAsData();
+  if (data == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath);
     return {};
   }
 
   std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
-  BinaryResourceParser parser(context, table.get(), source, data->data(), data->size(), apk.get());
+  BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(),
+                              collection.get());
   if (!parser.Parse()) {
     return {};
   }
 
-  return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
+  io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
+  if (manifest_file == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
+    return {};
+  }
+
+  std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
+  if (manifest_data == nullptr) {
+    diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
+    return {};
+  }
+
+  std::string error;
+  std::unique_ptr<xml::XmlResource> manifest =
+      xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
+  if (manifest == nullptr) {
+    diag->Error(DiagMessage(source)
+                << "failed to parse binary " << kAndroidManifestPath << ": " << error);
+    return {};
+  }
+  return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
+                                      std::move(manifest));
 }
 
 bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
@@ -148,26 +244,4 @@
   return true;
 }
 
-std::unique_ptr<xml::XmlResource> LoadedApk::InflateManifest(IAaptContext* context) {
-  IDiagnostics* diag = context->GetDiagnostics();
-
-  io::IFile* manifest_file = GetFileCollection()->FindFile("AndroidManifest.xml");
-  if (manifest_file == nullptr) {
-    diag->Error(DiagMessage(source_) << "no AndroidManifest.xml found");
-    return {};
-  }
-
-  std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
-  if (manifest_data == nullptr) {
-    diag->Error(DiagMessage(manifest_file->GetSource()) << "could not open AndroidManifest.xml");
-    return {};
-  }
-
-  std::unique_ptr<xml::XmlResource> manifest =
-      xml::Inflate(manifest_data->data(), manifest_data->size(), diag, manifest_file->GetSource());
-  if (manifest == nullptr) {
-    diag->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
-  }
-  return manifest;
-}
 }  // namespace aapt
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index d2dd5cf..ef97de3 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -29,20 +29,41 @@
 
 namespace aapt {
 
+constexpr static const char kApkResourceTablePath[] = "resources.arsc";
+constexpr static const char kProtoResourceTablePath[] = "resources.pb";
+constexpr static const char kAndroidManifestPath[] = "AndroidManifest.xml";
+
 // Info about an APK loaded in memory.
 class LoadedApk {
  public:
-  LoadedApk(
-      const Source& source,
-      std::unique_ptr<io::IFileCollection> apk,
-      std::unique_ptr<ResourceTable> table)
-      : source_(source), apk_(std::move(apk)), table_(std::move(table)) {}
-  virtual ~LoadedApk() = default;
+  // Loads both binary and proto APKs from disk.
+  static std::unique_ptr<LoadedApk> LoadApkFromPath(const ::android::StringPiece& path,
+                                                    IDiagnostics* diag);
+
+  // Loads a proto APK from the given file collection.
+  static std::unique_ptr<LoadedApk> LoadProtoApkFromFileCollection(
+      const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag);
+
+  // Loads a binary APK from the given file collection.
+  static std::unique_ptr<LoadedApk> LoadBinaryApkFromFileCollection(
+      const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag);
+
+  LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
+            std::unique_ptr<ResourceTable> table, std::unique_ptr<xml::XmlResource> manifest)
+      : source_(source),
+        apk_(std::move(apk)),
+        table_(std::move(table)),
+        manifest_(std::move(manifest)) {
+  }
 
   io::IFileCollection* GetFileCollection() {
     return apk_.get();
   }
 
+  const ResourceTable* GetResourceTable() const {
+    return table_.get();
+  }
+
   ResourceTable* GetResourceTable() {
     return table_.get();
   }
@@ -51,6 +72,10 @@
     return source_;
   }
 
+  const xml::XmlResource* GetManifest() const {
+    return manifest_.get();
+  }
+
   /**
    * Writes the APK on disk at the given path, while also removing the resource
    * files that are not referenced in the resource table.
@@ -71,11 +96,6 @@
                               const TableFlattenerOptions& options, FilterChain* filters,
                               IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
 
-  /** Inflates the AndroidManifest.xml file from the APK. */
-  std::unique_ptr<xml::XmlResource> InflateManifest(IAaptContext* context);
-
-  static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
-                                                    const android::StringPiece& path);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedApk);
@@ -83,6 +103,7 @@
   Source source_;
   std::unique_ptr<io::IFileCollection> apk_;
   std::unique_ptr<ResourceTable> table_;
+  std::unique_ptr<xml::XmlResource> manifest_;
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 36ab30c..808b29c 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -50,7 +50,7 @@
 }
 
 static void PrintUsage() {
-  std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|version] ..." << std::endl;
+  std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|convert|version] ..." << std::endl;
 }
 
 extern int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics);
@@ -58,6 +58,7 @@
 extern int Dump(const std::vector<StringPiece>& args);
 extern int Diff(const std::vector<StringPiece>& args);
 extern int Optimize(const std::vector<StringPiece>& args);
+extern int Convert(const std::vector<StringPiece>& args);
 
 static int ExecuteCommand(const StringPiece& command, const std::vector<StringPiece>& args,
                           IDiagnostics* diagnostics) {
@@ -71,6 +72,8 @@
     return Diff(args);
   } else if (command == "optimize") {
     return Optimize(args);
+  } else if (command == "convert") {
+    return Convert(args);
   } else if (command == "version") {
     PrintVersion();
     return 0;
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index a9f5f29..b78f48c 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -17,13 +17,34 @@
 #include "Resource.h"
 
 #include <map>
+#include <sstream>
 #include <string>
 
-using android::StringPiece;
+#include "android-base/stringprintf.h"
+
+using ::android::StringPiece;
+using ::android::base::StringPrintf;
 
 namespace aapt {
 
-StringPiece ToString(ResourceType type) {
+std::string ResourceId::to_string() const {
+  return StringPrintf("0x%08x", id);
+}
+
+std::string ResourceName::to_string() const {
+  return ResourceNameRef(*this).to_string();
+}
+
+std::string ResourceNameRef::to_string() const {
+  std::ostringstream str_stream;
+  if (!package.empty()) {
+    str_stream << package << ":";
+  }
+  str_stream << type << "/" << entry;
+  return str_stream.str();
+}
+
+StringPiece to_string(ResourceType type) {
   switch (type) {
     case ResourceType::kAnim:
       return "anim";
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 87b9867..96a0203 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -69,11 +69,10 @@
   kXml,
 };
 
-android::StringPiece ToString(ResourceType type);
+android::StringPiece to_string(ResourceType type);
 
 /**
- * Returns a pointer to a valid ResourceType, or nullptr if
- * the string was invalid.
+ * Returns a pointer to a valid ResourceType, or nullptr if the string was invalid.
  */
 const ResourceType* ParseResourceType(const android::StringPiece& str);
 
@@ -92,7 +91,7 @@
   int compare(const ResourceName& other) const;
 
   bool is_valid() const;
-  std::string ToString() const;
+  std::string to_string() const;
 };
 
 /**
@@ -115,8 +114,10 @@
   ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
   ResourceNameRef& operator=(const ResourceName& rhs);
 
-  ResourceName ToResourceName() const;
   bool is_valid() const;
+
+  ResourceName ToResourceName() const;
+  std::string to_string() const;
 };
 
 constexpr const uint8_t kAppPackageId = 0x7fu;
@@ -149,6 +150,8 @@
   uint8_t package_id() const;
   uint8_t type_id() const;
   uint16_t entry_id() const;
+
+  std::string to_string() const;
 };
 
 struct SourcedResourceName {
@@ -229,7 +232,9 @@
   return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
 }
 
-inline bool ResourceId::is_valid_dynamic() const { return (id & 0x00ff0000u) != 0; }
+inline bool ResourceId::is_valid_dynamic() const {
+  return (id & 0x00ff0000u) != 0;
+}
 
 inline uint8_t ResourceId::package_id() const {
   return static_cast<uint8_t>(id >> 24);
@@ -259,24 +264,16 @@
   return lhs.id != rhs.id;
 }
 
-inline ::std::ostream& operator<<(::std::ostream& out,
-                                  const ResourceId& res_id) {
-  std::ios_base::fmtflags old_flags = out.flags();
-  char old_fill = out.fill();
-  out << "0x" << std::internal << std::setfill('0') << std::setw(8) << std::hex
-      << res_id.id;
-  out.flags(old_flags);
-  out.fill(old_fill);
-  return out;
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceId& res_id) {
+  return out << res_id.to_string();
 }
 
 //
 // ResourceType implementation.
 //
 
-inline ::std::ostream& operator<<(::std::ostream& out,
-                                  const ResourceType& val) {
-  return out << ToString(val);
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) {
+  return out << to_string(val);
 }
 
 //
@@ -315,18 +312,8 @@
          std::tie(rhs.package, rhs.type, rhs.entry);
 }
 
-inline ::std::ostream& operator<<(::std::ostream& out,
-                                  const ResourceName& name) {
-  if (!name.package.empty()) {
-    out << name.package << ":";
-  }
-  return out << name.type << "/" << name.entry;
-}
-
-inline std::string ResourceName::ToString() const {
-  std::stringstream stream;
-  stream << *this;
-  return stream.str();
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) {
+  return out << name.to_string();
 }
 
 //
@@ -370,12 +357,8 @@
          std::tie(rhs.package, rhs.type, rhs.entry);
 }
 
-inline ::std::ostream& operator<<(::std::ostream& out,
-                                  const ResourceNameRef& name) {
-  if (!name.package.empty()) {
-    out << name.package << ":";
-  }
-  return out << name.type << "/" << name.entry;
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& name) {
+  return out << name.to_string();
 }
 
 inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) {
@@ -386,8 +369,7 @@
   return ResourceNameRef(lhs) != rhs;
 }
 
-inline bool operator==(const SourcedResourceName& lhs,
-                       const SourcedResourceName& rhs) {
+inline bool operator==(const SourcedResourceName& lhs, const SourcedResourceName& rhs) {
   return lhs.name == rhs.name && lhs.line == rhs.line;
 }
 
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 082fd86..b38d259 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -17,9 +17,12 @@
 #include "ResourceValues.h"
 
 #include <algorithm>
+#include <cinttypes>
 #include <limits>
 #include <set>
+#include <sstream>
 
+#include "android-base/stringprintf.h"
 #include "androidfw/ResourceTypes.h"
 
 #include "Resource.h"
@@ -27,8 +30,18 @@
 #include "ValueVisitor.h"
 #include "util/Util.h"
 
+using ::aapt::text::Printer;
+using ::android::StringPiece;
+using ::android::base::StringPrintf;
+
 namespace aapt {
 
+void Value::PrettyPrint(Printer* printer) const {
+  std::ostringstream str_stream;
+  Print(&str_stream);
+  printer->Print(str_stream.str());
+}
+
 std::ostream& operator<<(std::ostream& out, const Value& value) {
   value.Print(&out);
   return out;
@@ -155,6 +168,49 @@
   }
 }
 
+static void PrettyPrintReferenceImpl(const Reference& ref, bool print_package, Printer* printer) {
+  switch (ref.reference_type) {
+    case Reference::Type::kResource:
+      printer->Print("@");
+      break;
+
+    case Reference::Type::kAttribute:
+      printer->Print("?");
+      break;
+  }
+
+  if (!ref.name && !ref.id) {
+    printer->Print("null");
+    return;
+  }
+
+  if (ref.private_reference) {
+    printer->Print("*");
+  }
+
+  if (ref.name) {
+    const ResourceName& name = ref.name.value();
+    if (print_package) {
+      printer->Print(name.to_string());
+    } else {
+      printer->Print(to_string(name.type));
+      printer->Print("/");
+      printer->Print(name.entry);
+    }
+  } else if (ref.id && ref.id.value().is_valid_dynamic()) {
+    printer->Print(ref.id.value().to_string());
+  }
+}
+
+void Reference::PrettyPrint(Printer* printer) const {
+  PrettyPrintReferenceImpl(*this, true /*print_package*/, printer);
+}
+
+void Reference::PrettyPrint(const StringPiece& package, Printer* printer) const {
+  const bool print_package = name ? package != name.value().package : true;
+  PrettyPrintReferenceImpl(*this, print_package, printer);
+}
+
 bool Id::Equals(const Value* value) const {
   return ValueCast<Id>(value) != nullptr;
 }
@@ -165,11 +221,16 @@
   return true;
 }
 
-Id* Id::Clone(StringPool* /*new_pool*/) const { return new Id(*this); }
+Id* Id::Clone(StringPool* /*new_pool*/) const {
+  return new Id(*this);
+}
 
-void Id::Print(std::ostream* out) const { *out << "(id)"; }
+void Id::Print(std::ostream* out) const {
+  *out << "(id)";
+}
 
-String::String(const StringPool::Ref& ref) : value(ref) {}
+String::String(const StringPool::Ref& ref) : value(ref) {
+}
 
 bool String::Equals(const Value* value) const {
   const String* other = ValueCast<String>(value);
@@ -218,7 +279,14 @@
   *out << "(string) \"" << *value << "\"";
 }
 
-StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {}
+void String::PrettyPrint(Printer* printer) const {
+  printer->Print("\"");
+  printer->Print(*value);
+  printer->Print("\"");
+}
+
+StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
+}
 
 bool StyledString::Equals(const Value* value) const {
   const StyledString* other = ValueCast<StyledString>(value);
@@ -269,7 +337,8 @@
   }
 }
 
-FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {}
+FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
+}
 
 bool FileReference::Equals(const Value* value) const {
   const FileReference* other = ValueCast<FileReference>(value);
@@ -302,7 +371,8 @@
   *out << "(file) " << *path;
 }
 
-BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {}
+BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {
+}
 
 BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) {
   value.dataType = dataType;
@@ -318,7 +388,7 @@
          this->value.data == other->value.data;
 }
 
-bool BinaryPrimitive::Flatten(android::Res_value* out_value) const {
+bool BinaryPrimitive::Flatten(::android::Res_value* out_value) const {
   out_value->dataType = value.dataType;
   out_value->data = util::HostToDevice32(value.data);
   return true;
@@ -329,32 +399,110 @@
 }
 
 void BinaryPrimitive::Print(std::ostream* out) const {
+  *out << StringPrintf("(primitive) type=0x%02x data=0x%08x", value.dataType, value.data);
+}
+
+static std::string ComplexToString(uint32_t complex_value, bool fraction) {
+  using ::android::Res_value;
+
+  constexpr std::array<int, 4> kRadixShifts = {{23, 16, 8, 0}};
+
+  // Determine the radix that was used.
+  const uint32_t radix =
+      (complex_value >> Res_value::COMPLEX_RADIX_SHIFT) & Res_value::COMPLEX_RADIX_MASK;
+  const uint64_t mantissa = uint64_t{(complex_value >> Res_value::COMPLEX_MANTISSA_SHIFT) &
+                                     Res_value::COMPLEX_MANTISSA_MASK}
+                            << kRadixShifts[radix];
+  const float value = mantissa * (1.0f / (1 << 23));
+
+  std::string str = StringPrintf("%f", value);
+
+  const int unit_type =
+      (complex_value >> Res_value::COMPLEX_UNIT_SHIFT) & Res_value::COMPLEX_UNIT_MASK;
+  if (fraction) {
+    switch (unit_type) {
+      case Res_value::COMPLEX_UNIT_FRACTION:
+        str += "%";
+        break;
+      case Res_value::COMPLEX_UNIT_FRACTION_PARENT:
+        str += "%p";
+        break;
+      default:
+        str += "???";
+        break;
+    }
+  } else {
+    switch (unit_type) {
+      case Res_value::COMPLEX_UNIT_PX:
+        str += "px";
+        break;
+      case Res_value::COMPLEX_UNIT_DIP:
+        str += "dp";
+        break;
+      case Res_value::COMPLEX_UNIT_SP:
+        str += "sp";
+        break;
+      case Res_value::COMPLEX_UNIT_PT:
+        str += "pt";
+        break;
+      case Res_value::COMPLEX_UNIT_IN:
+        str += "in";
+        break;
+      case Res_value::COMPLEX_UNIT_MM:
+        str += "mm";
+        break;
+      default:
+        str += "???";
+        break;
+    }
+  }
+  return str;
+}
+
+void BinaryPrimitive::PrettyPrint(Printer* printer) const {
+  using ::android::Res_value;
   switch (value.dataType) {
-    case android::Res_value::TYPE_NULL:
-      if (value.data == android::Res_value::DATA_NULL_EMPTY) {
-        *out << "(empty)";
+    case Res_value::TYPE_NULL:
+      if (value.data == Res_value::DATA_NULL_EMPTY) {
+        printer->Print("@empty");
       } else {
-        *out << "(null)";
+        printer->Print("@null");
       }
       break;
-    case android::Res_value::TYPE_INT_DEC:
-      *out << "(integer) " << static_cast<int32_t>(value.data);
+
+    case Res_value::TYPE_INT_DEC:
+      printer->Print(StringPrintf("%" PRIi32, static_cast<int32_t>(value.data)));
       break;
-    case android::Res_value::TYPE_INT_HEX:
-      *out << "(integer) 0x" << std::hex << value.data << std::dec;
+
+    case Res_value::TYPE_INT_HEX:
+      printer->Print(StringPrintf("0x%08x", value.data));
       break;
-    case android::Res_value::TYPE_INT_BOOLEAN:
-      *out << "(boolean) " << (value.data != 0 ? "true" : "false");
+
+    case Res_value::TYPE_INT_BOOLEAN:
+      printer->Print(value.data != 0 ? "true" : "false");
       break;
-    case android::Res_value::TYPE_INT_COLOR_ARGB8:
-    case android::Res_value::TYPE_INT_COLOR_RGB8:
-    case android::Res_value::TYPE_INT_COLOR_ARGB4:
-    case android::Res_value::TYPE_INT_COLOR_RGB4:
-      *out << "(color) #" << std::hex << value.data << std::dec;
+
+    case Res_value::TYPE_INT_COLOR_ARGB8:
+    case Res_value::TYPE_INT_COLOR_RGB8:
+    case Res_value::TYPE_INT_COLOR_ARGB4:
+    case Res_value::TYPE_INT_COLOR_RGB4:
+      printer->Print(StringPrintf("#%08x", value.data));
       break;
+
+    case Res_value::TYPE_FLOAT:
+      printer->Print(StringPrintf("%g", *reinterpret_cast<const float*>(&value.data)));
+      break;
+
+    case Res_value::TYPE_DIMENSION:
+      printer->Print(ComplexToString(value.data, false /*fraction*/));
+      break;
+
+    case Res_value::TYPE_FRACTION:
+      printer->Print(ComplexToString(value.data, true /*fraction*/));
+      break;
+
     default:
-      *out << "(unknown 0x" << std::hex << (int)value.dataType << ") 0x"
-           << std::hex << value.data << std::dec;
+      printer->Print(StringPrintf("(unknown 0x%02x) 0x%08x", value.dataType, value.data));
       break;
   }
 }
@@ -424,107 +572,107 @@
   return new Attribute(*this);
 }
 
-void Attribute::PrintMask(std::ostream* out) const {
+std::string Attribute::MaskString() const {
   if (type_mask == android::ResTable_map::TYPE_ANY) {
-    *out << "any";
-    return;
+    return "any";
   }
 
+  std::ostringstream out;
   bool set = false;
   if ((type_mask & android::ResTable_map::TYPE_REFERENCE) != 0) {
     if (!set) {
       set = true;
     } else {
-      *out << "|";
+      out << "|";
     }
-    *out << "reference";
+    out << "reference";
   }
 
   if ((type_mask & android::ResTable_map::TYPE_STRING) != 0) {
     if (!set) {
       set = true;
     } else {
-      *out << "|";
+      out << "|";
     }
-    *out << "string";
+    out << "string";
   }
 
   if ((type_mask & android::ResTable_map::TYPE_INTEGER) != 0) {
     if (!set) {
       set = true;
     } else {
-      *out << "|";
+      out << "|";
     }
-    *out << "integer";
+    out << "integer";
   }
 
   if ((type_mask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
     if (!set) {
       set = true;
     } else {
-      *out << "|";
+      out << "|";
     }
-    *out << "boolean";
+    out << "boolean";
   }
 
   if ((type_mask & android::ResTable_map::TYPE_COLOR) != 0) {
     if (!set) {
       set = true;
     } else {
-      *out << "|";
+      out << "|";
     }
-    *out << "color";
+    out << "color";
   }
 
   if ((type_mask & android::ResTable_map::TYPE_FLOAT) != 0) {
     if (!set) {
       set = true;
     } else {
-      *out << "|";
+      out << "|";
     }
-    *out << "float";
+    out << "float";
   }
 
   if ((type_mask & android::ResTable_map::TYPE_DIMENSION) != 0) {
     if (!set) {
       set = true;
     } else {
-      *out << "|";
+      out << "|";
     }
-    *out << "dimension";
+    out << "dimension";
   }
 
   if ((type_mask & android::ResTable_map::TYPE_FRACTION) != 0) {
     if (!set) {
       set = true;
     } else {
-      *out << "|";
+      out << "|";
     }
-    *out << "fraction";
+    out << "fraction";
   }
 
   if ((type_mask & android::ResTable_map::TYPE_ENUM) != 0) {
     if (!set) {
       set = true;
     } else {
-      *out << "|";
+      out << "|";
     }
-    *out << "enum";
+    out << "enum";
   }
 
   if ((type_mask & android::ResTable_map::TYPE_FLAGS) != 0) {
     if (!set) {
       set = true;
     } else {
-      *out << "|";
+      out << "|";
     }
-    *out << "flags";
+    out << "flags";
   }
+  return out.str();
 }
 
 void Attribute::Print(std::ostream* out) const {
-  *out << "(attr) ";
-  PrintMask(out);
+  *out << "(attr) " << MaskString();
 
   if (!symbols.empty()) {
     *out << " [" << util::Joiner(symbols, ", ") << "]";
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index fd242a1..b2ec8bdd 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -29,6 +29,7 @@
 #include "Resource.h"
 #include "StringPool.h"
 #include "io/File.h"
+#include "text/Printer.h"
 #include "util/Maybe.h"
 
 namespace aapt {
@@ -106,6 +107,10 @@
   // Human readable printout of this value.
   virtual void Print(std::ostream* out) const = 0;
 
+  // Human readable printout of this value that may omit some information for the sake
+  // of brevity and readability. Default implementation just calls Print().
+  virtual void PrettyPrint(text::Printer* printer) const;
+
   friend std::ostream& operator<<(std::ostream& out, const Value& value);
 
  protected:
@@ -162,6 +167,10 @@
   bool Flatten(android::Res_value* out_value) const override;
   Reference* Clone(StringPool* new_pool) const override;
   void Print(std::ostream* out) const override;
+  void PrettyPrint(text::Printer* printer) const override;
+
+  // Prints the reference without a package name if the package name matches the one given.
+  void PrettyPrint(const android::StringPiece& package, text::Printer* printer) const;
 };
 
 bool operator<(const Reference&, const Reference&);
@@ -224,6 +233,7 @@
   bool Flatten(android::Res_value* out_value) const override;
   String* Clone(StringPool* new_pool) const override;
   void Print(std::ostream* out) const override;
+  void PrettyPrint(text::Printer* printer) const override;
 };
 
 struct StyledString : public BaseItem<StyledString> {
@@ -274,6 +284,7 @@
   bool Flatten(android::Res_value* out_value) const override;
   BinaryPrimitive* Clone(StringPool* new_pool) const override;
   void Print(std::ostream* out) const override;
+  void PrettyPrint(text::Printer* printer) const override;
 };
 
 struct Attribute : public BaseValue<Attribute> {
@@ -294,7 +305,7 @@
 
   bool Equals(const Value* value) const override;
   Attribute* Clone(StringPool* new_pool) const override;
-  void PrintMask(std::ostream* out) const;
+  std::string MaskString() const;
   void Print(std::ostream* out) const override;
   bool Matches(const Item& item, DiagMessage* out_msg = nullptr) const;
 };
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index d7f2a66..0f312d6 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -20,16 +20,14 @@
 #include <ostream>
 #include <string>
 
+#include "android-base/stringprintf.h"
 #include "androidfw/StringPiece.h"
 
 #include "util/Maybe.h"
 
 namespace aapt {
 
-/**
- * Represents a file on disk. Used for logging and
- * showing errors.
- */
+// Represents a file on disk. Used for logging and showing errors.
 struct Source {
   std::string path;
   Maybe<size_t> line;
@@ -42,7 +40,16 @@
   inline Source(const android::StringPiece& path, size_t line)
       : path(path.to_string()), line(line) {}
 
-  inline Source WithLine(size_t line) const { return Source(path, line); }
+  inline Source WithLine(size_t line) const {
+    return Source(path, line);
+  }
+
+  std::string to_string() const {
+    if (line) {
+      return ::android::base::StringPrintf("%s:%zd", path.c_str(), line.value());
+    }
+    return path;
+  }
 };
 
 //
@@ -50,11 +57,7 @@
 //
 
 inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
-  out << source.path;
-  if (source.line) {
-    out << ":" << source.line.value();
-  }
-  return out;
+  return out << source.to_string();
 }
 
 inline bool operator==(const Source& lhs, const Source& rhs) {
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
new file mode 100644
index 0000000..89ae9e8
--- /dev/null
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -0,0 +1,228 @@
+/*
+ * 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 <vector>
+
+#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
+
+#include "Flags.h"
+#include "LoadedApk.h"
+#include "ValueVisitor.h"
+#include "cmd/Util.h"
+#include "format/binary/TableFlattener.h"
+#include "format/binary/XmlFlattener.h"
+#include "format/proto/ProtoDeserialize.h"
+#include "io/BigBufferStream.h"
+#include "io/Util.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+
+using ::android::StringPiece;
+using ::std::unique_ptr;
+using ::std::vector;
+
+namespace aapt {
+
+static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml,
+                       const std::string& entry_path, bool utf16, IArchiveWriter* writer) {
+  BigBuffer buffer(4096);
+  XmlFlattenerOptions options = {};
+  options.use_utf16 = utf16;
+  XmlFlattener flattener(&buffer, options);
+  if (!flattener.Consume(context, &xml)) {
+    return false;
+  }
+  io::BigBufferInputStream input_stream(&buffer);
+  return io::CopyInputStreamToArchive(context, &input_stream, entry_path, ArchiveEntry::kCompress,
+                                      writer);
+}
+
+bool ConvertProtoApkToBinaryApk(IAaptContext* context, unique_ptr<LoadedApk> apk,
+                                const TableFlattenerOptions& options, IArchiveWriter* writer) {
+  if (!FlattenXml(context, *apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
+    return false;
+  }
+
+  BigBuffer buffer(4096);
+  TableFlattener table_flattener(options, &buffer);
+  if (!table_flattener.Consume(context, apk->GetResourceTable())) {
+    return false;
+  }
+
+  io::BigBufferInputStream input_stream(&buffer);
+  if (!io::CopyInputStreamToArchive(context, &input_stream, kApkResourceTablePath,
+                                    ArchiveEntry::kAlign, writer)) {
+    return false;
+  }
+
+  for (const auto& package : apk->GetResourceTable()->packages) {
+    for (const auto& type : package->types) {
+      for (const auto& entry : type->entries) {
+        for (const auto& config_value : entry->values) {
+          const FileReference* file = ValueCast<FileReference>(config_value->value.get());
+          if (file != nullptr) {
+            if (file->file == nullptr) {
+              context->GetDiagnostics()->Warn(DiagMessage(apk->GetSource())
+                                              << "no file associated with " << *file);
+              return false;
+            }
+
+            if (file->type == ResourceFile::Type::kProtoXml) {
+              unique_ptr<io::InputStream> in = file->file->OpenInputStream();
+              if (in == nullptr) {
+                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                 << "failed to open file " << *file->path);
+                return false;
+              }
+
+              pb::XmlNode pb_node;
+              io::ZeroCopyInputAdaptor adaptor(in.get());
+              if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
+                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                 << "failed to parse proto XML " << *file->path);
+                return false;
+              }
+
+              std::string error;
+              unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
+              if (xml == nullptr) {
+                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                 << "failed to deserialize proto XML "
+                                                 << *file->path << ": " << error);
+                return false;
+              }
+
+              if (!FlattenXml(context, *xml, *file->path, false /*utf16*/, writer)) {
+                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                 << "failed to serialize XML " << *file->path);
+                return false;
+              }
+            } else {
+              if (!io::CopyFileToArchive(context, file->file, *file->path,
+                                         file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u,
+                                         writer)) {
+                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                 << "failed to copy file " << *file->path);
+                return false;
+              }
+            }
+
+          } // file
+        } // config_value
+      } // entry
+    } // type
+  } // package
+  return true;
+}
+
+class Context : public IAaptContext {
+ public:
+  Context() : mangler_({}), symbols_(&mangler_) {
+  }
+
+  PackageType GetPackageType() override {
+    return PackageType::kApp;
+  }
+
+  SymbolTable* GetExternalSymbols() override {
+    return &symbols_;
+  }
+
+  IDiagnostics* GetDiagnostics() override {
+    return &diag_;
+  }
+
+  const std::string& GetCompilationPackage() override {
+    return package_;
+  }
+
+  uint8_t GetPackageId() override {
+    // Nothing should call this.
+    UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
+    return 0;
+  }
+
+  NameMangler* GetNameMangler() override {
+    UNIMPLEMENTED(FATAL);
+    return nullptr;
+  }
+
+  bool IsVerbose() override {
+    return verbose_;
+  }
+
+  int GetMinSdkVersion() override {
+    return 0u;
+  }
+
+  bool verbose_ = false;
+  std::string package_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Context);
+
+  NameMangler mangler_;
+  SymbolTable symbols_;
+  StdErrDiagnostics diag_;
+};
+
+int Convert(const vector<StringPiece>& args) {
+  Context context;
+  std::string output_path;
+  TableFlattenerOptions options;
+  Flags flags =
+      Flags()
+          .RequiredFlag("-o", "Output path", &output_path)
+          .OptionalSwitch("--enable-sparse-encoding",
+                          "Enables encoding sparse entries using a binary search tree.\n"
+                          "This decreases APK size at the cost of resource retrieval performance.",
+                          &options.use_sparse_entries)
+          .OptionalSwitch("-v", "Enables verbose logging", &context.verbose_);
+  if (!flags.Parse("aapt2 convert", args, &std::cerr)) {
+    return 1;
+  }
+
+  if (flags.GetArgs().size() != 1) {
+    std::cerr << "must supply a single proto APK\n";
+    flags.Usage("aapt2 convert", &std::cerr);
+    return 1;
+  }
+
+  const StringPiece& path = flags.GetArgs()[0];
+  unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
+  if (apk == nullptr) {
+    context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK");
+    return 1;
+  }
+
+  Maybe<AppInfo> app_info =
+      ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
+  if (!app_info) {
+    return 1;
+  }
+
+  context.package_ = app_info.value().package;
+
+  unique_ptr<IArchiveWriter> writer =
+      CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path);
+  if (writer == nullptr) {
+    return 1;
+  }
+  return ConvertProtoApkToBinaryApk(&context, std::move(apk), options, writer.get()) ? 0 : 1;
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 625c47c..fc1f1d6 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -357,8 +357,9 @@
     return 1;
   }
 
-  std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
-  std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[1]);
+  IDiagnostics* diag = context.GetDiagnostics();
+  std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(flags.GetArgs()[0], diag);
+  std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(flags.GetArgs()[1], diag);
   if (!apk_a || !apk_b) {
     return 1;
   }
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 090c3fb..bc7f5a8 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 
+#include <cinttypes>
 #include <vector>
 
+#include "android-base/stringprintf.h"
 #include "androidfw/StringPiece.h"
 
 #include "Debug.h"
@@ -27,9 +29,12 @@
 #include "io/FileStream.h"
 #include "io/ZipArchive.h"
 #include "process/IResourceTableConsumer.h"
+#include "text/Printer.h"
 #include "util/Files.h"
 
+using ::aapt::text::Printer;
 using ::android::StringPiece;
+using ::android::base::StringPrintf;
 
 namespace aapt {
 
@@ -48,16 +53,28 @@
 }
 
 static void DumpCompiledFile(const ResourceFile& file, const Source& source, off64_t offset,
-                             size_t len) {
-  std::cout << "Resource: " << file.name << "\n"
-            << "Config:   " << file.config << "\n"
-            << "Source:   " << file.source << "\n"
-            << "Type:     " << ResourceFileTypeToString(file.type) << "\n"
-            << "DataOff:  " << offset << "\n"
-            << "DataLen:  " << len << "\n";
+                             size_t len, Printer* printer) {
+  printer->Print("Resource: ");
+  printer->Println(file.name.to_string());
+
+  printer->Print("Config:   ");
+  printer->Println(file.config.to_string());
+
+  printer->Print("Source:   ");
+  printer->Println(file.source.to_string());
+
+  printer->Print("Type:     ");
+  printer->Println(ResourceFileTypeToString(file.type));
+
+  printer->Println(StringPrintf("Data:     offset=%" PRIi64 " length=%zd", offset, len));
 }
 
 static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
+  // Use a smaller buffer so that there is less latency for dumping to stdout.
+  constexpr size_t kStdOutBufferSize = 1024u;
+  io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+  Printer printer(&fout);
+
   DebugPrintTableOptions print_options;
   print_options.show_sources = true;
 
@@ -78,11 +95,13 @@
         return false;
       }
 
-      if (!DeserializeTableFromPb(pb_table, &table, &err)) {
+      if (!DeserializeTableFromPb(pb_table, zip.get(), &table, &err)) {
         context->GetDiagnostics()->Error(DiagMessage(file_path)
                                          << "failed to parse table: " << err);
         return false;
       }
+
+      printer.Println("Proto APK");
     } else if (io::IFile* file = zip->FindFile("resources.arsc")) {
       std::unique_ptr<io::IData> data = file->OpenAsData();
       if (!data) {
@@ -90,13 +109,16 @@
         return false;
       }
 
-      BinaryResourceParser parser(context, &table, Source(file_path), data->data(), data->size());
+      BinaryResourceParser parser(context->GetDiagnostics(), &table, Source(file_path),
+                                  data->data(), data->size());
       if (!parser.Parse()) {
         return false;
       }
+
+      printer.Println("Binary APK");
     }
 
-    Debug::PrintTable(table, print_options);
+    Debug::PrintTable(table, print_options, &printer);
     return true;
   }
 
@@ -117,9 +139,12 @@
     return false;
   }
 
+  printer.Println("AAPT2 Container (APC)");
   ContainerReaderEntry* entry;
   while ((entry = reader.Next()) != nullptr) {
     if (entry->Type() == ContainerEntryType::kResTable) {
+      printer.Println("kResTable");
+
       pb::ResourceTable pb_table;
       if (!entry->GetResTable(&pb_table)) {
         context->GetDiagnostics()->Error(DiagMessage(file_path)
@@ -129,14 +154,17 @@
 
       ResourceTable table;
       err.clear();
-      if (!DeserializeTableFromPb(pb_table, &table, &err)) {
+      if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &err)) {
         context->GetDiagnostics()->Error(DiagMessage(file_path)
                                          << "failed to parse table: " << err);
         continue;
       }
 
-      Debug::PrintTable(table, print_options);
+      printer.Indent();
+      Debug::PrintTable(table, print_options, &printer);
+      printer.Undent();
     } else if (entry->Type() == ContainerEntryType::kResFile) {
+      printer.Println("kResFile");
       pb::internal::CompiledFile pb_compiled_file;
       off64_t offset;
       size_t length;
@@ -154,7 +182,9 @@
         continue;
       }
 
-      DumpCompiledFile(file, Source(file_path), offset, length);
+      printer.Indent();
+      DumpCompiledFile(file, Source(file_path), offset, length, &printer);
+      printer.Undent();
     }
   }
   return true;
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index e0dae1b..b372923 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -29,6 +29,7 @@
 #include "AppInfo.h"
 #include "Debug.h"
 #include "Flags.h"
+#include "LoadedApk.h"
 #include "Locale.h"
 #include "NameMangler.h"
 #include "ResourceUtils.h"
@@ -39,7 +40,6 @@
 #include "filter/ConfigFilter.h"
 #include "format/Archive.h"
 #include "format/Container.h"
-#include "format/binary/BinaryResourceParser.h"
 #include "format/binary/TableFlattener.h"
 #include "format/binary/XmlFlattener.h"
 #include "format/proto/ProtoDeserialize.h"
@@ -71,9 +71,6 @@
 
 namespace aapt {
 
-constexpr static const char kApkResourceTablePath[] = "resources.arsc";
-constexpr static const char kProtoResourceTablePath[] = "resources.pb";
-
 enum class OutputFormat {
   kApk,
   kProto,
@@ -298,23 +295,6 @@
   return false;
 }
 
-static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, const void* data,
-                                                      size_t len, IDiagnostics* diag) {
-  pb::ResourceTable pb_table;
-  if (!pb_table.ParseFromArray(data, len)) {
-    diag->Error(DiagMessage(source) << "invalid compiled table");
-    return {};
-  }
-
-  std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
-  std::string error;
-  if (!DeserializeTableFromPb(pb_table, table.get(), &error)) {
-    diag->Error(DiagMessage(source) << "invalid compiled table: " << error);
-    return {};
-  }
-  return table;
-}
-
 // Inflates an XML file from the source path.
 static std::unique_ptr<xml::XmlResource> LoadXml(const std::string& path, IDiagnostics* diag) {
   FileInputStream fin(path);
@@ -587,7 +567,7 @@
               pb::XmlNode pb_xml_node;
               if (!pb_xml_node.ParseFromArray(data->data(), static_cast<int>(data->size()))) {
                 context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
-                                                  << "failed to parse proto xml");
+                                                  << "failed to parse proto XML");
                 return false;
               }
 
@@ -595,13 +575,15 @@
               file_op.xml_to_flatten = DeserializeXmlResourceFromPb(pb_xml_node, &error);
               if (file_op.xml_to_flatten == nullptr) {
                 context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
-                                                  << "failed to deserialize proto xml: " << error);
+                                                  << "failed to deserialize proto XML: " << error);
                 return false;
               }
             } else {
-              file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(),
-                                                    context_->GetDiagnostics(), file->GetSource());
+              std::string error_str;
+              file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(), &error_str);
               if (file_op.xml_to_flatten == nullptr) {
+                context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+                                                  << "failed to parse binary XML: " << error_str);
                 return false;
               }
             }
@@ -748,22 +730,29 @@
         file_collection_(util::make_unique<io::FileCollection>()) {
   }
 
-  /**
-   * Creates a SymbolTable that loads symbols from the various APKs and caches
-   * the results for faster lookup.
-   */
+  // Creates a SymbolTable that loads symbols from the various APKs.
   bool LoadSymbolsFromIncludePaths() {
-    std::unique_ptr<AssetManagerSymbolSource> asset_source =
-        util::make_unique<AssetManagerSymbolSource>();
+    auto asset_source = util::make_unique<AssetManagerSymbolSource>();
     for (const std::string& path : options_.include_paths) {
       if (context_->IsVerbose()) {
         context_->GetDiagnostics()->Note(DiagMessage() << "including " << path);
       }
 
-      // First try to load the file as a static lib.
-      std::string error_str;
-      std::unique_ptr<ResourceTable> include_static = LoadStaticLibrary(path, &error_str);
-      if (include_static) {
+      std::string error;
+      auto zip_collection = io::ZipFileCollection::Create(path, &error);
+      if (zip_collection == nullptr) {
+        context_->GetDiagnostics()->Error(DiagMessage() << "failed to open APK: " << error);
+        return false;
+      }
+
+      if (zip_collection->FindFile(kProtoResourceTablePath) != nullptr) {
+        // Load this as a static library include.
+        std::unique_ptr<LoadedApk> static_apk = LoadedApk::LoadProtoApkFromFileCollection(
+            Source(path), std::move(zip_collection), context_->GetDiagnostics());
+        if (static_apk == nullptr) {
+          return false;
+        }
+
         if (context_->GetPackageType() != PackageType::kStaticLib) {
           // Can't include static libraries when not building a static library (they have no IDs
           // assigned).
@@ -772,13 +761,15 @@
           return false;
         }
 
-        // If we are using --no-static-lib-packages, we need to rename the
-        // package of this table to our compilation package.
+        ResourceTable* table = static_apk->GetResourceTable();
+
+        // If we are using --no-static-lib-packages, we need to rename the package of this table to
+        // our compilation package.
         if (options_.no_static_lib_packages) {
           // Since package names can differ, and multiple packages can exist in a ResourceTable,
           // we place the requirement that all static libraries are built with the package
           // ID 0x7f. So if one is not found, this is an error.
-          if (ResourceTablePackage* pkg = include_static->FindPackageById(kAppPackageId)) {
+          if (ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId)) {
             pkg->name = context_->GetCompilationPackage();
           } else {
             context_->GetDiagnostics()->Error(DiagMessage(path)
@@ -788,19 +779,14 @@
         }
 
         context_->GetExternalSymbols()->AppendSource(
-            util::make_unique<ResourceTableSymbolSource>(include_static.get()));
-
-        static_table_includes_.push_back(std::move(include_static));
-
-      } else if (!error_str.empty()) {
-        // We had an error with reading, so fail.
-        context_->GetDiagnostics()->Error(DiagMessage(path) << error_str);
-        return false;
-      }
-
-      if (!asset_source->AddAssetPath(path)) {
-        context_->GetDiagnostics()->Error(DiagMessage(path) << "failed to load include path");
-        return false;
+            util::make_unique<ResourceTableSymbolSource>(table));
+        static_library_includes_.push_back(std::move(static_apk));
+      } else {
+        if (!asset_source->AddAssetPath(path)) {
+          context_->GetDiagnostics()->Error(DiagMessage()
+                                            << "failed to load include path " << path);
+          return false;
+        }
       }
     }
 
@@ -1194,46 +1180,18 @@
     return true;
   }
 
-  std::unique_ptr<ResourceTable> LoadStaticLibrary(const std::string& input,
-                                                   std::string* out_error) {
-    std::unique_ptr<io::ZipFileCollection> collection =
-        io::ZipFileCollection::Create(input, out_error);
-    if (!collection) {
-      return {};
-    }
-    return LoadTablePbFromCollection(collection.get());
-  }
-
-  std::unique_ptr<ResourceTable> LoadTablePbFromCollection(io::IFileCollection* collection) {
-    io::IFile* file = collection->FindFile(kProtoResourceTablePath);
-    if (!file) {
-      return {};
-    }
-
-    std::unique_ptr<io::IData> data = file->OpenAsData();
-    return LoadTableFromPb(file->GetSource(), data->data(), data->size(),
-                           context_->GetDiagnostics());
-  }
-
   bool MergeStaticLibrary(const std::string& input, bool override) {
     if (context_->IsVerbose()) {
       context_->GetDiagnostics()->Note(DiagMessage() << "merging static library " << input);
     }
 
-    std::string error_str;
-    std::unique_ptr<io::ZipFileCollection> collection =
-        io::ZipFileCollection::Create(input, &error_str);
-    if (!collection) {
-      context_->GetDiagnostics()->Error(DiagMessage(input) << error_str);
-      return false;
-    }
-
-    std::unique_ptr<ResourceTable> table = LoadTablePbFromCollection(collection.get());
-    if (!table) {
+    std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(input, context_->GetDiagnostics());
+    if (apk == nullptr) {
       context_->GetDiagnostics()->Error(DiagMessage(input) << "invalid static library");
       return false;
     }
 
+    ResourceTable* table = apk->GetResourceTable();
     ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId);
     if (!pkg) {
       context_->GetDiagnostics()->Error(DiagMessage(input) << "static library has no package");
@@ -1254,13 +1212,12 @@
       // Clear the package name, so as to make the resources look like they are coming from the
       // local package.
       pkg->name = "";
-      result = table_merger_->Merge(Source(input), table.get(), override, collection.get());
+      result = table_merger_->Merge(Source(input), table, override);
 
     } else {
       // This is the proper way to merge libraries, where the package name is
       // preserved and resource names are mangled.
-      result =
-          table_merger_->MergeAndMangle(Source(input), pkg->name, table.get(), collection.get());
+      result = table_merger_->MergeAndMangle(Source(input), pkg->name, table);
     }
 
     if (!result) {
@@ -1268,31 +1225,10 @@
     }
 
     // Make sure to move the collection into the set of IFileCollections.
-    collections_.push_back(std::move(collection));
+    merged_apks_.push_back(std::move(apk));
     return true;
   }
 
-  bool MergeResourceTable(io::IFile* file, bool override) {
-    if (context_->IsVerbose()) {
-      context_->GetDiagnostics()->Note(DiagMessage() << "merging resource table "
-                                                     << file->GetSource());
-    }
-
-    std::unique_ptr<io::IData> data = file->OpenAsData();
-    if (!data) {
-      context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "failed to open file");
-      return false;
-    }
-
-    std::unique_ptr<ResourceTable> table =
-        LoadTableFromPb(file->GetSource(), data->data(), data->size(), context_->GetDiagnostics());
-    if (!table) {
-      return false;
-    }
-
-    return table_merger_->Merge(file->GetSource(), table.get(), override);
-  }
-
   bool MergeCompiledFile(const ResourceFile& compiled_file, io::IFile* file, bool override) {
     if (context_->IsVerbose()) {
       context_->GetDiagnostics()->Note(DiagMessage()
@@ -1423,7 +1359,7 @@
 
         ResourceTable table;
         std::string error;
-        if (!DeserializeTableFromPb(pb_table, &table, &error)) {
+        if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &error)) {
           context_->GetDiagnostics()->Error(DiagMessage(src)
                                             << "failed to deserialize resource table: " << error);
           return false;
@@ -1868,13 +1804,15 @@
   // A pointer to the FileCollection representing the filesystem (not archives).
   std::unique_ptr<io::FileCollection> file_collection_;
 
-  // A vector of IFileCollections. This is mainly here to keep ownership of the
+  // A vector of IFileCollections. This is mainly here to retain ownership of the
   // collections.
   std::vector<std::unique_ptr<io::IFileCollection>> collections_;
 
-  // A vector of ResourceTables. This is here to retain ownership, so that the
-  // SymbolTable can use these.
-  std::vector<std::unique_ptr<ResourceTable>> static_table_includes_;
+  // The set of merged APKs. This is mainly here to retain ownership of the APKs.
+  std::vector<std::unique_ptr<LoadedApk>> merged_apks_;
+
+  // The set of included APKs (not merged). This is mainly here to retain ownership of the APKs.
+  std::vector<std::unique_ptr<LoadedApk>> static_library_includes_;
 
   // The set of shared libraries being used, mapping their assigned package ID to package name.
   std::map<size_t, std::string> shared_libs_;
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 44e148e..688b6a7 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -281,15 +281,14 @@
   OptimizeContext* context_;
 };
 
-bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
+bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
                                 OptimizeOptions* out_options) {
-  std::unique_ptr<xml::XmlResource> manifest = apk->InflateManifest(context);
+  const xml::XmlResource* manifest = apk->GetManifest();
   if (manifest == nullptr) {
     return false;
   }
 
-  Maybe<AppInfo> app_info =
-      ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics());
+  Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics());
   if (!app_info) {
     context->GetDiagnostics()->Error(DiagMessage()
                                      << "failed to extract data from AndroidManifest.xml");
@@ -355,7 +354,7 @@
   }
 
   const std::string& apk_path = flags.GetArgs()[0];
-  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, apk_path);
+  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
   if (!apk) {
     return 1;
   }
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 708bed8..d39f43e 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -214,9 +214,10 @@
   return doc;
 }
 
-static Maybe<std::string> ExtractCompiledString(xml::Attribute* attr, std::string* out_error) {
-  if (attr->compiled_value != nullptr) {
-    String* compiled_str = ValueCast<String>(attr->compiled_value.get());
+static Maybe<std::string> ExtractCompiledString(const xml::Attribute& attr,
+                                                std::string* out_error) {
+  if (attr.compiled_value != nullptr) {
+    const String* compiled_str = ValueCast<String>(attr.compiled_value.get());
     if (compiled_str != nullptr) {
       if (!compiled_str->value->empty()) {
         return *compiled_str->value;
@@ -230,16 +231,16 @@
   }
 
   // Fallback to the plain text value if there is one.
-  if (!attr->value.empty()) {
-    return attr->value;
+  if (!attr.value.empty()) {
+    return attr.value;
   }
   *out_error = "value is an empty string";
   return {};
 }
 
-static Maybe<uint32_t> ExtractCompiledInt(xml::Attribute* attr, std::string* out_error) {
-  if (attr->compiled_value != nullptr) {
-    BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get());
+static Maybe<uint32_t> ExtractCompiledInt(const xml::Attribute& attr, std::string* out_error) {
+  if (attr.compiled_value != nullptr) {
+    const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get());
     if (compiled_prim != nullptr) {
       if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT &&
           compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) {
@@ -251,19 +252,19 @@
   }
 
   // Fallback to the plain text value if there is one.
-  Maybe<uint32_t> integer = ResourceUtils::ParseInt(attr->value);
+  Maybe<uint32_t> integer = ResourceUtils::ParseInt(attr.value);
   if (integer) {
     return integer;
   }
   std::stringstream error_msg;
-  error_msg << "'" << attr->value << "' is not a valid integer";
+  error_msg << "'" << attr.value << "' is not a valid integer";
   *out_error = error_msg.str();
   return {};
 }
 
-static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error) {
-  if (attr->compiled_value != nullptr) {
-    BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get());
+static Maybe<int> ExtractSdkVersion(const xml::Attribute& attr, std::string* out_error) {
+  if (attr.compiled_value != nullptr) {
+    const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get());
     if (compiled_prim != nullptr) {
       if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT &&
           compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) {
@@ -273,7 +274,7 @@
       return {};
     }
 
-    String* compiled_str = ValueCast<String>(attr->compiled_value.get());
+    const String* compiled_str = ValueCast<String>(attr.compiled_value.get());
     if (compiled_str != nullptr) {
       Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(*compiled_str->value);
       if (sdk_version) {
@@ -288,19 +289,20 @@
   }
 
   // Fallback to the plain text value if there is one.
-  Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(attr->value);
+  Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
   if (sdk_version) {
     return sdk_version;
   }
   std::stringstream error_msg;
-  error_msg << "'" << attr->value << "' is not a valid SDK version";
+  error_msg << "'" << attr.value << "' is not a valid SDK version";
   *out_error = error_msg.str();
   return {};
 }
 
-Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag) {
+Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res,
+                                                IDiagnostics* diag) {
   // Make sure the first element is <manifest> with package attribute.
-  xml::Element* manifest_el = xml_res->root.get();
+  const xml::Element* manifest_el = xml_res.root.get();
   if (manifest_el == nullptr) {
     return {};
   }
@@ -308,63 +310,63 @@
   AppInfo app_info;
 
   if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
-    diag->Error(DiagMessage(xml_res->file.source) << "root tag must be <manifest>");
+    diag->Error(DiagMessage(xml_res.file.source) << "root tag must be <manifest>");
     return {};
   }
 
-  xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
+  const xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
   if (!package_attr) {
-    diag->Error(DiagMessage(xml_res->file.source) << "<manifest> must have a 'package' attribute");
+    diag->Error(DiagMessage(xml_res.file.source) << "<manifest> must have a 'package' attribute");
     return {};
   }
 
   std::string error_msg;
-  Maybe<std::string> maybe_package = ExtractCompiledString(package_attr, &error_msg);
+  Maybe<std::string> maybe_package = ExtractCompiledString(*package_attr, &error_msg);
   if (!maybe_package) {
-    diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+    diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
                 << "invalid package name: " << error_msg);
     return {};
   }
   app_info.package = maybe_package.value();
 
-  if (xml::Attribute* version_code_attr =
+  if (const xml::Attribute* version_code_attr =
           manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
-    Maybe<uint32_t> maybe_code = ExtractCompiledInt(version_code_attr, &error_msg);
+    Maybe<uint32_t> maybe_code = ExtractCompiledInt(*version_code_attr, &error_msg);
     if (!maybe_code) {
-      diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+      diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
                   << "invalid android:versionCode: " << error_msg);
       return {};
     }
     app_info.version_code = maybe_code.value();
   }
 
-  if (xml::Attribute* revision_code_attr =
+  if (const xml::Attribute* revision_code_attr =
           manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
-    Maybe<uint32_t> maybe_code = ExtractCompiledInt(revision_code_attr, &error_msg);
+    Maybe<uint32_t> maybe_code = ExtractCompiledInt(*revision_code_attr, &error_msg);
     if (!maybe_code) {
-      diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+      diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
                   << "invalid android:revisionCode: " << error_msg);
       return {};
     }
     app_info.revision_code = maybe_code.value();
   }
 
-  if (xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) {
-    Maybe<std::string> maybe_split_name = ExtractCompiledString(split_name_attr, &error_msg);
+  if (const xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) {
+    Maybe<std::string> maybe_split_name = ExtractCompiledString(*split_name_attr, &error_msg);
     if (!maybe_split_name) {
-      diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+      diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
                   << "invalid split name: " << error_msg);
       return {};
     }
     app_info.split_name = maybe_split_name.value();
   }
 
-  if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
-    if (xml::Attribute* min_sdk =
+  if (const xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
+    if (const xml::Attribute* min_sdk =
             uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
-      Maybe<int> maybe_sdk = ExtractSdkVersion(min_sdk, &error_msg);
+      Maybe<int> maybe_sdk = ExtractSdkVersion(*min_sdk, &error_msg);
       if (!maybe_sdk) {
-        diag->Error(DiagMessage(xml_res->file.source.WithLine(uses_sdk_el->line_number))
+        diag->Error(DiagMessage(xml_res.file.source.WithLine(uses_sdk_el->line_number))
                     << "invalid android:minSdkVersion: " << error_msg);
         return {};
       }
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index fd9b39c..7611c15 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -57,7 +57,8 @@
                                                         const SplitConstraints& constraints);
 
 // Extracts relevant info from the AndroidManifest.xml.
-Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag);
+Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res,
+                                                IDiagnostics* diag);
 
 }  // namespace aapt
 
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp
index 8b6c524..238e339 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser.cpp
+++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp
@@ -164,7 +164,7 @@
 
     // Add the inline attribute to the parent.
     parent_el->attributes.push_back(xml::Attribute{decl.attr_namespace_uri, decl.attr_name,
-                                                   "@" + new_doc->file.name.ToString()});
+                                                   "@" + new_doc->file.name.to_string()});
 
     // Delete the subtree.
     for (auto iter = parent_el->children.begin(); iter != parent_el->children.end(); ++iter) {
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 95eec4a..5078678 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -73,29 +73,22 @@
 
 }  // namespace
 
-BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
+BinaryResourceParser::BinaryResourceParser(IDiagnostics* diag, ResourceTable* table,
                                            const Source& source, const void* data, size_t len,
                                            io::IFileCollection* files)
-    : context_(context),
-      table_(table),
-      source_(source),
-      data_(data),
-      data_len_(len),
-      files_(files) {
+    : diag_(diag), table_(table), source_(source), data_(data), data_len_(len), files_(files) {
 }
 
 bool BinaryResourceParser::Parse() {
   ResChunkPullParser parser(data_, data_len_);
 
   if (!ResChunkPullParser::IsGoodEvent(parser.Next())) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "corrupt resources.arsc: " << parser.error());
+    diag_->Error(DiagMessage(source_) << "corrupt resources.arsc: " << parser.error());
     return false;
   }
 
   if (parser.chunk()->type != android::RES_TABLE_TYPE) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << StringPrintf("unknown chunk of type 0x%02x",
+    diag_->Error(DiagMessage(source_) << StringPrintf("unknown chunk of type 0x%02x",
                                                       static_cast<int>(parser.chunk()->type)));
     return false;
   }
@@ -106,13 +99,12 @@
 
   if (parser.Next() != ResChunkPullParser::Event::kEndDocument) {
     if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
-      context_->GetDiagnostics()->Warn(
-          DiagMessage(source_) << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error());
+      diag_->Warn(DiagMessage(source_)
+                  << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error());
     } else {
-      context_->GetDiagnostics()->Warn(
-          DiagMessage(source_) << StringPrintf(
-              "unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE",
-              static_cast<int>(parser.chunk()->type)));
+      diag_->Warn(DiagMessage(source_)
+                  << StringPrintf("unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE",
+                                  static_cast<int>(parser.chunk()->type)));
     }
   }
   return true;
@@ -122,7 +114,7 @@
 bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) {
   const ResTable_header* table_header = ConvertTo<ResTable_header>(chunk);
   if (!table_header) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_header chunk");
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_header chunk");
     return false;
   }
 
@@ -135,17 +127,15 @@
           status_t err =
               value_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
           if (err != NO_ERROR) {
-            context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                              << "corrupt string pool in ResTable: "
-                                              << value_pool_.getError());
+            diag_->Error(DiagMessage(source_)
+                         << "corrupt string pool in ResTable: " << value_pool_.getError());
             return false;
           }
 
           // Reserve some space for the strings we are going to add.
           table_->string_pool.HintWillAdd(value_pool_.size(), value_pool_.styleCount());
         } else {
-          context_->GetDiagnostics()->Warn(DiagMessage(source_)
-                                           << "unexpected string pool in ResTable");
+          diag_->Warn(DiagMessage(source_) << "unexpected string pool in ResTable");
         }
         break;
 
@@ -156,16 +146,15 @@
         break;
 
       default:
-        context_->GetDiagnostics()->Warn(
-            DiagMessage(source_) << "unexpected chunk type "
-                                 << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
+        diag_->Warn(DiagMessage(source_)
+                    << "unexpected chunk type "
+                    << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
         break;
     }
   }
 
   if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "corrupt resource table: " << parser.error());
+    diag_->Error(DiagMessage(source_) << "corrupt resource table: " << parser.error());
     return false;
   }
   return true;
@@ -176,14 +165,13 @@
       sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
   const ResTable_package* package_header = ConvertTo<ResTable_package, kMinPackageSize>(chunk);
   if (!package_header) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_package chunk");
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_package chunk");
     return false;
   }
 
   uint32_t package_id = util::DeviceToHost32(package_header->id);
   if (package_id > std::numeric_limits<uint8_t>::max()) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "package ID is too big (" << package_id << ")");
+    diag_->Error(DiagMessage(source_) << "package ID is too big (" << package_id << ")");
     return false;
   }
 
@@ -198,9 +186,8 @@
   ResourceTablePackage* package =
       table_->CreatePackage(util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id));
   if (!package) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "incompatible package '" << package_name << "' with ID "
-                                      << package_id);
+    diag_->Error(DiagMessage(source_)
+                 << "incompatible package '" << package_name << "' with ID " << package_id);
     return false;
   }
 
@@ -218,8 +205,7 @@
           status_t err =
               type_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
           if (err != NO_ERROR) {
-            context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                              << "corrupt type string pool in "
+            diag_->Error(DiagMessage(source_) << "corrupt type string pool in "
                                               << "ResTable_package: " << type_pool_.getError());
             return false;
           }
@@ -227,13 +213,12 @@
           status_t err =
               key_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
           if (err != NO_ERROR) {
-            context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                              << "corrupt key string pool in "
+            diag_->Error(DiagMessage(source_) << "corrupt key string pool in "
                                               << "ResTable_package: " << key_pool_.getError());
             return false;
           }
         } else {
-          context_->GetDiagnostics()->Warn(DiagMessage(source_) << "unexpected string pool");
+          diag_->Warn(DiagMessage(source_) << "unexpected string pool");
         }
         break;
 
@@ -256,16 +241,15 @@
         break;
 
       default:
-        context_->GetDiagnostics()->Warn(
-            DiagMessage(source_) << "unexpected chunk type "
-                                 << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
+        diag_->Warn(DiagMessage(source_)
+                    << "unexpected chunk type "
+                    << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
         break;
     }
   }
 
   if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "corrupt ResTable_package: " << parser.error());
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_package: " << parser.error());
     return false;
   }
 
@@ -278,19 +262,18 @@
 
 bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
   if (type_pool_.getError() != NO_ERROR) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing type string pool");
+    diag_->Error(DiagMessage(source_) << "missing type string pool");
     return false;
   }
 
   const ResTable_typeSpec* type_spec = ConvertTo<ResTable_typeSpec>(chunk);
   if (!type_spec) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk");
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk");
     return false;
   }
 
   if (type_spec->id == 0) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "ResTable_typeSpec has invalid id: " << type_spec->id);
+    diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id);
     return false;
   }
   return true;
@@ -299,12 +282,12 @@
 bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
                                      const ResChunk_header* chunk) {
   if (type_pool_.getError() != NO_ERROR) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing type string pool");
+    diag_->Error(DiagMessage(source_) << "missing type string pool");
     return false;
   }
 
   if (key_pool_.getError() != NO_ERROR) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing key string pool");
+    diag_->Error(DiagMessage(source_) << "missing key string pool");
     return false;
   }
 
@@ -312,13 +295,12 @@
   // a lot and has its own code to handle variable size.
   const ResTable_type* type = ConvertTo<ResTable_type, kResTableTypeMinSize>(chunk);
   if (!type) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_type chunk");
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_type chunk");
     return false;
   }
 
   if (type->id == 0) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "ResTable_type has invalid id: " << (int)type->id);
+    diag_->Error(DiagMessage(source_) << "ResTable_type has invalid id: " << (int)type->id);
     return false;
   }
 
@@ -329,9 +311,8 @@
 
   const ResourceType* parsed_type = ParseResourceType(type_str);
   if (!parsed_type) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "invalid type name '" << type_str << "' for type with ID "
-                                      << (int)type->id);
+    diag_->Error(DiagMessage(source_)
+                 << "invalid type name '" << type_str << "' for type with ID " << (int)type->id);
     return false;
   }
 
@@ -360,14 +341,13 @@
     }
 
     if (!resource_value) {
-      context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                        << "failed to parse value for resource " << name << " ("
+      diag_->Error(DiagMessage(source_) << "failed to parse value for resource " << name << " ("
                                         << res_id << ") with configuration '" << config << "'");
       return false;
     }
 
     if (!table_->AddResourceAllowMangled(name, res_id, config, {}, std::move(resource_value),
-                                         context_->GetDiagnostics())) {
+                                         diag_)) {
       return false;
     }
 
@@ -375,7 +355,7 @@
       Symbol symbol;
       symbol.state = SymbolState::kPublic;
       symbol.source = source_.WithLine(0);
-      if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, context_->GetDiagnostics())) {
+      if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, diag_)) {
         return false;
       }
     }
@@ -410,15 +390,14 @@
                                                        const android::Res_value& value) {
   std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(name.type, config, value_pool_,
                                                                   value, &table_->string_pool);
-  if (files_ != nullptr && item != nullptr) {
+  if (files_ != nullptr) {
     FileReference* file_ref = ValueCast<FileReference>(item.get());
     if (file_ref != nullptr) {
       file_ref->file = files_->FindFile(*file_ref->path);
       if (file_ref->file == nullptr) {
-        context_->GetDiagnostics()->Warn(DiagMessage()
-                                         << "resource " << name << " for config '" << config
-                                         << "' is a file reference to '" << *file_ref->path
-                                         << "' but no such path exists");
+        diag_->Warn(DiagMessage() << "resource " << name << " for config '" << config
+                                  << "' is a file reference to '" << *file_ref->path
+                                  << "' but no such path exists");
       }
     }
   }
@@ -432,7 +411,7 @@
     case ResourceType::kStyle:
       return ParseStyle(name, config, map);
     case ResourceType::kAttrPrivate:
-    // fallthrough
+      // fallthrough
     case ResourceType::kAttr:
       return ParseAttr(name, config, map);
     case ResourceType::kArray:
@@ -445,8 +424,8 @@
       // We can ignore the value here.
       return util::make_unique<Id>();
     default:
-      context_->GetDiagnostics()->Error(DiagMessage() << "illegal map type '" << ToString(name.type)
-                                                      << "' (" << (int)name.type << ")");
+      diag_->Error(DiagMessage() << "illegal map type '" << to_string(name.type) << "' ("
+                                 << (int)name.type << ")");
       break;
   }
   return {};
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index dc9a384..052f806 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -39,7 +39,7 @@
  public:
   // Creates a parser, which will read `len` bytes from `data`, and add any resources parsed to
   // `table`. `source` is for logging purposes.
-  BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source,
+  BinaryResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
                        const void* data, size_t data_len, io::IFileCollection* files = nullptr);
 
   // Parses the binary resource table and returns true if successful.
@@ -80,7 +80,7 @@
    */
   bool CollectMetaData(const android::ResTable_map& map_entry, Value* value);
 
-  IAaptContext* context_;
+  IDiagnostics* diag_;
   ResourceTable* table_;
 
   const Source source_;
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 57565a5..4a1b46c 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -472,7 +472,7 @@
         expected_type_id++;
       }
       expected_type_id++;
-      type_pool_.MakeRef(ToString(type->type));
+      type_pool_.MakeRef(to_string(type->type));
 
       std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type);
       if (sorted_entries.empty()) {
@@ -568,14 +568,6 @@
   ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
   table_header->packageCount = util::HostToDevice32(table->packages.size());
 
-  // Write a self mapping entry for this package if the ID is non-standard (0x7f).
-  if (context->GetPackageType() == PackageType::kApp) {
-    const uint8_t package_id = context->GetPackageId();
-    if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
-      table->included_packages_[package_id] = context->GetCompilationPackage();
-    }
-  }
-
   // Flatten the values string pool.
   StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool);
 
@@ -583,6 +575,14 @@
 
   // Flatten each package.
   for (auto& package : table->packages) {
+    if (context->GetPackageType() == PackageType::kApp) {
+      // Write a self mapping entry for this package if the ID is non-standard (0x7f).
+      const uint8_t package_id = package->id.value();
+      if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
+        table->included_packages_[package_id] = package->name;
+      }
+    }
+
     PackageFlattener flattener(context, package.get(), &table->included_packages_,
                                options_.use_sparse_entries);
     if (!flattener.FlattenPackage(&package_buffer)) {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 6d75973..e11890b 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -71,7 +71,8 @@
       return result;
     }
 
-    BinaryResourceParser parser(context, out_table, {}, content.data(), content.size());
+    BinaryResourceParser parser(context->GetDiagnostics(), out_table, {}, content.data(),
+                                content.size());
     if (!parser.Parse()) {
       return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
     }
@@ -278,7 +279,7 @@
   // Attempt to parse the sparse contents.
 
   ResourceTable sparse_table;
-  BinaryResourceParser parser(context.get(), &sparse_table, Source("test.arsc"),
+  BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
                               sparse_contents.data(), sparse_contents.size());
   ASSERT_TRUE(parser.Parse());
 
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 86bd865..0f0bce8 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -371,7 +371,8 @@
 }
 
 static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStringPool& src_pool,
-                                     ResourceTable* out_table, std::string* out_error) {
+                                     io::IFileCollection* files, ResourceTable* out_table,
+                                     std::string* out_error) {
   Maybe<uint8_t> id;
   if (pb_package.has_package_id()) {
     id = static_cast<uint8_t>(pb_package.package_id().id());
@@ -444,7 +445,7 @@
         }
 
         config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config,
-                                                     &out_table->string_pool, out_error);
+                                                     &out_table->string_pool, files, out_error);
         if (config_value->value == nullptr) {
           return false;
         }
@@ -457,8 +458,8 @@
   return true;
 }
 
-bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* out_table,
-                            std::string* out_error) {
+bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollection* files,
+                            ResourceTable* out_table, std::string* out_error) {
   // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
   // causes errors when qualifying it with android::
   using namespace android;
@@ -474,7 +475,7 @@
   }
 
   for (const pb::Package& pb_package : pb_table.package()) {
-    if (!DeserializePackageFromPb(pb_package, source_pool, out_table, out_error)) {
+    if (!DeserializePackageFromPb(pb_package, source_pool, files, out_table, out_error)) {
       return false;
     }
   }
@@ -600,10 +601,11 @@
 std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
                                               const android::ResStringPool& src_pool,
                                               const ConfigDescription& config,
-                                              StringPool* value_pool, std::string* out_error) {
+                                              StringPool* value_pool, io::IFileCollection* files,
+                                              std::string* out_error) {
   std::unique_ptr<Value> value;
   if (pb_value.has_item()) {
-    value = DeserializeItemFromPb(pb_value.item(), src_pool, config, value_pool, out_error);
+    value = DeserializeItemFromPb(pb_value.item(), src_pool, config, value_pool, files, out_error);
     if (value == nullptr) {
       return {};
     }
@@ -651,8 +653,8 @@
             return {};
           }
           DeserializeItemMetaDataFromPb(pb_entry, src_pool, &entry.key);
-          entry.value =
-              DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+          entry.value = DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, files,
+                                              out_error);
           if (entry.value == nullptr) {
             return {};
           }
@@ -680,8 +682,8 @@
         const pb::Array& pb_array = pb_compound_value.array();
         std::unique_ptr<Array> array = util::make_unique<Array>();
         for (const pb::Array_Element& pb_entry : pb_array.element()) {
-          std::unique_ptr<Item> item =
-              DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+          std::unique_ptr<Item> item = DeserializeItemFromPb(pb_entry.item(), src_pool, config,
+                                                             value_pool, files, out_error);
           if (item == nullptr) {
             return {};
           }
@@ -697,8 +699,8 @@
         std::unique_ptr<Plural> plural = util::make_unique<Plural>();
         for (const pb::Plural_Entry& pb_entry : pb_plural.entry()) {
           size_t plural_idx = DeserializePluralEnumFromPb(pb_entry.arity());
-          plural->values[plural_idx] =
-              DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+          plural->values[plural_idx] = DeserializeItemFromPb(pb_entry.item(), src_pool, config,
+                                                             value_pool, files, out_error);
           if (!plural->values[plural_idx]) {
             return {};
           }
@@ -727,7 +729,7 @@
 std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
                                             const android::ResStringPool& src_pool,
                                             const ConfigDescription& config, StringPool* value_pool,
-                                            std::string* out_error) {
+                                            io::IFileCollection* files, std::string* out_error) {
   switch (pb_item.value_case()) {
     case pb::Item::kRef: {
       const pb::Reference& pb_ref = pb_item.ref();
@@ -774,6 +776,9 @@
           util::make_unique<FileReference>(value_pool->MakeRef(
               pb_file.path(), StringPool::Context(StringPool::Context::kHighPriority, config)));
       file_ref->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
+      if (files != nullptr) {
+        file_ref->file = files->FindFile(*file_ref->path);
+      }
       return std::move(file_ref);
     } break;
 
@@ -825,7 +830,7 @@
     }
     if (pb_attr.has_compiled_item()) {
       attr.compiled_value =
-          DeserializeItemFromPb(pb_attr.compiled_item(), {}, {}, value_pool, out_error);
+          DeserializeItemFromPb(pb_attr.compiled_item(), {}, {}, value_pool, nullptr, out_error);
       if (attr.compiled_value == nullptr) {
         return {};
       }
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.h b/tools/aapt2/format/proto/ProtoDeserialize.h
index 7dc54f2..0c581a1 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.h
+++ b/tools/aapt2/format/proto/ProtoDeserialize.h
@@ -27,6 +27,7 @@
 #include "Resources.pb.h"
 #include "ResourcesInternal.pb.h"
 #include "StringPool.h"
+#include "io/File.h"
 #include "xml/XmlDom.h"
 
 namespace aapt {
@@ -34,12 +35,13 @@
 std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
                                               const android::ResStringPool& src_pool,
                                               const ConfigDescription& config,
-                                              StringPool* value_pool, std::string* out_error);
+                                              StringPool* value_pool, io::IFileCollection* files,
+                                              std::string* out_error);
 
 std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
                                             const android::ResStringPool& src_pool,
                                             const ConfigDescription& config, StringPool* value_pool,
-                                            std::string* out_error);
+                                            io::IFileCollection* files, std::string* out_error);
 
 std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode& pb_node,
                                                                std::string* out_error);
@@ -50,8 +52,9 @@
 bool DeserializeConfigFromPb(const pb::Configuration& pb_config, ConfigDescription* out_config,
                              std::string* out_error);
 
-bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* out_table,
-                            std::string* out_error);
+// Optional io::IFileCollection used to lookup references to files in the ResourceTable.
+bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollection* files,
+                            ResourceTable* out_table, std::string* out_error);
 
 bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file,
                                    ResourceFile* out_file, std::string* out_error);
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 1d184fe..97ce01a 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -284,7 +284,7 @@
       if (type->id) {
         pb_type->mutable_type_id()->set_id(type->id.value());
       }
-      pb_type->set_name(ToString(type->type).to_string());
+      pb_type->set_name(to_string(type->type).to_string());
 
       for (const std::unique_ptr<ResourceEntry>& entry : type->entries) {
         pb::Entry* pb_entry = pb_type->add_entry();
@@ -328,7 +328,7 @@
   pb_ref->set_id(ref.id.value_or_default(ResourceId(0x0)).id);
 
   if (ref.name) {
-    pb_ref->set_name(ref.name.value().ToString());
+    pb_ref->set_name(ref.name.value().to_string());
   }
 
   pb_ref->set_private_(ref.private_reference);
@@ -523,14 +523,14 @@
 }
 
 void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) {
-  out_file->set_resource_name(file.name.ToString());
+  out_file->set_resource_name(file.name.to_string());
   out_file->set_source_path(file.source.path);
   out_file->set_type(SerializeFileReferenceTypeToPb(file.type));
   SerializeConfig(file.config, out_file->mutable_config());
 
   for (const SourcedResourceName& exported : file.exported_symbols) {
     pb::internal::CompiledFile_Symbol* pb_symbol = out_file->add_exported_symbol();
-    pb_symbol->set_resource_name(exported.name.ToString());
+    pb_symbol->set_resource_name(exported.name.to_string());
     pb_symbol->mutable_source()->set_line_number(exported.line);
   }
 }
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 8efac8a..9649a4d 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -29,6 +29,12 @@
 
 namespace aapt {
 
+class MockFileCollection : public io::IFileCollection {
+ public:
+  MOCK_METHOD1(FindFile, io::IFile*(const StringPiece& path));
+  MOCK_METHOD0(Iterator, std::unique_ptr<io::IFileCollectionIterator>());
+};
+
 TEST(ProtoSerializeTest, SerializeSinglePackage) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   std::unique_ptr<ResourceTable> table =
@@ -86,9 +92,14 @@
   pb::ResourceTable pb_table;
   SerializeTableToPb(*table, &pb_table);
 
+  test::TestFile file_a("res/layout/main.xml");
+  MockFileCollection files;
+  EXPECT_CALL(files, FindFile(Eq("res/layout/main.xml")))
+      .WillRepeatedly(::testing::Return(&file_a));
+
   ResourceTable new_table;
   std::string error;
-  ASSERT_TRUE(DeserializeTableFromPb(pb_table, &new_table, &error));
+  ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
   EXPECT_THAT(error, IsEmpty());
 
   Id* new_id = test::GetValue<Id>(&new_table, "com.app.a:id/foo");
@@ -124,6 +135,11 @@
   ASSERT_TRUE(actual_ref->id);
   EXPECT_THAT(*actual_ref, Eq(expected_ref));
 
+  FileReference* actual_file_ref =
+      test::GetValue<FileReference>(&new_table, "com.app.a:layout/main");
+  ASSERT_THAT(actual_file_ref, NotNull());
+  EXPECT_THAT(actual_file_ref->file, Eq(&file_a));
+
   StyledString* actual_styled_str =
       test::GetValue<StyledString>(&new_table, "com.app.a:string/styled");
   ASSERT_THAT(actual_styled_str, NotNull());
diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
index 6ed07b0..94686c0 100644
--- a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
+++ b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
@@ -25,5 +25,4 @@
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     AaptTestNamespace_LibOne \
     AaptTestNamespace_LibTwo
-LOCAL_AAPT_FLAGS := -v
 include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/io/FileStream.cpp b/tools/aapt2/io/FileStream.cpp
index 2f7a4b3..4ff6d78 100644
--- a/tools/aapt2/io/FileStream.cpp
+++ b/tools/aapt2/io/FileStream.cpp
@@ -26,6 +26,7 @@
 #include "android-base/utf8.h"
 
 using ::android::base::SystemErrorCodeToString;
+using ::android::base::unique_fd;
 
 namespace aapt {
 namespace io {
@@ -100,7 +101,13 @@
 }
 
 FileOutputStream::FileOutputStream(const std::string& path, int mode, size_t buffer_capacity)
-    : FileOutputStream(::android::base::utf8::open(path.c_str(), mode), buffer_capacity) {
+    : FileOutputStream(unique_fd(::android::base::utf8::open(path.c_str(), mode)),
+                       buffer_capacity) {
+}
+
+FileOutputStream::FileOutputStream(unique_fd fd, size_t buffer_capacity)
+    : FileOutputStream(fd.get(), buffer_capacity) {
+  owned_fd_ = std::move(fd);
 }
 
 FileOutputStream::FileOutputStream(int fd, size_t buffer_capacity)
@@ -118,7 +125,7 @@
 }
 
 bool FileOutputStream::Next(void** data, size_t* size) {
-  if (fd_ == -1 || HadError()) {
+  if (HadError()) {
     return false;
   }
 
@@ -159,7 +166,8 @@
   ssize_t n = TEMP_FAILURE_RETRY(write(fd_, buffer_.get(), buffer_offset_));
   if (n < 0) {
     error_ = SystemErrorCodeToString(errno);
-    fd_.reset();
+    owned_fd_.reset();
+    fd_ = -1;
     buffer_.reset();
     return false;
   }
diff --git a/tools/aapt2/io/FileStream.h b/tools/aapt2/io/FileStream.h
index 3b07667..4ed1ad5 100644
--- a/tools/aapt2/io/FileStream.h
+++ b/tools/aapt2/io/FileStream.h
@@ -29,12 +29,15 @@
 namespace aapt {
 namespace io {
 
+constexpr size_t kDefaultBufferCapacity = 4096u;
+
 class FileInputStream : public InputStream {
  public:
-  explicit FileInputStream(const std::string& path, size_t buffer_capacity = 4096);
+  explicit FileInputStream(const std::string& path,
+                           size_t buffer_capacity = kDefaultBufferCapacity);
 
-  // Takes ownership of `fd`.
-  explicit FileInputStream(int fd, size_t buffer_capacity = 4096);
+  // Take ownership of `fd`.
+  explicit FileInputStream(int fd, size_t buffer_capacity = kDefaultBufferCapacity);
 
   bool Next(const void** data, size_t* size) override;
 
@@ -61,10 +64,14 @@
 class FileOutputStream : public OutputStream {
  public:
   explicit FileOutputStream(const std::string& path, int mode = O_RDWR | O_CREAT | O_BINARY,
-                            size_t buffer_capacity = 4096);
+                            size_t buffer_capacity = kDefaultBufferCapacity);
+
+  // Does not take ownership of `fd`.
+  explicit FileOutputStream(int fd, size_t buffer_capacity = kDefaultBufferCapacity);
 
   // Takes ownership of `fd`.
-  explicit FileOutputStream(int fd, size_t buffer_capacity = 4096);
+  explicit FileOutputStream(android::base::unique_fd fd,
+                            size_t buffer_capacity = kDefaultBufferCapacity);
 
   ~FileOutputStream();
 
@@ -86,7 +93,8 @@
 
   bool FlushImpl();
 
-  android::base::unique_fd fd_;
+  android::base::unique_fd owned_fd_;
+  int fd_;
   std::string error_;
   std::unique_ptr<uint8_t[]> buffer_;
   size_t buffer_capacity_;
diff --git a/tools/aapt2/io/FileStream_test.cpp b/tools/aapt2/io/FileStream_test.cpp
index 68c3cb1..a6d58ca 100644
--- a/tools/aapt2/io/FileStream_test.cpp
+++ b/tools/aapt2/io/FileStream_test.cpp
@@ -87,10 +87,8 @@
   const std::string input = "this is a cool string";
 
   TemporaryFile file;
-  int fd = file.release();
 
-  // FileOutputStream takes ownership.
-  FileOutputStream out(fd, 10u);
+  FileOutputStream out(file.fd, 10u);
   ASSERT_FALSE(out.HadError());
   EXPECT_THAT(out.ByteCount(), Eq(0u));
 
@@ -118,10 +116,10 @@
 
   ASSERT_TRUE(out.Flush());
 
-  lseek64(fd, 0, SEEK_SET);
+  lseek64(file.fd, 0, SEEK_SET);
 
   std::string actual;
-  ASSERT_TRUE(android::base::ReadFdToString(fd, &actual));
+  ASSERT_TRUE(android::base::ReadFdToString(file.fd, &actual));
   EXPECT_THAT(actual, StrEq(input));
 }
 
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index d270340..7ee1016 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -18,6 +18,7 @@
 
 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
 
+using ::android::StringPiece;
 using ::google::protobuf::io::ZeroCopyOutputStream;
 
 namespace aapt {
@@ -93,6 +94,25 @@
   return !in->HadError();
 }
 
+bool Copy(OutputStream* out, const StringPiece& in) {
+  const char* in_buffer = in.data();
+  size_t in_len = in.size();
+  while (in_len != 0) {
+    void* out_buffer;
+    size_t out_len;
+    if (!out->Next(&out_buffer, &out_len)) {
+      return false;
+    }
+
+    const size_t bytes_to_copy = in_len < out_len ? in_len : out_len;
+    memcpy(out_buffer, in_buffer, bytes_to_copy);
+    out->BackUp(out_len - bytes_to_copy);
+    in_buffer += bytes_to_copy;
+    in_len -= bytes_to_copy;
+  }
+  return true;
+}
+
 bool Copy(ZeroCopyOutputStream* out, InputStream* in) {
   OutputStreamAdaptor adaptor(out);
   return Copy(&adaptor, in);
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index 1e48508..de2ab39 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -42,6 +42,7 @@
 // Copies the data from in to out. Returns false if there was an error.
 // If there was an error, check the individual streams' HadError/GetError methods.
 bool Copy(OutputStream* out, InputStream* in);
+bool Copy(OutputStream* out, const ::android::StringPiece& in);
 bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in);
 
 class OutputStreamAdaptor : public io::OutputStream {
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 3ba4dd8..91cef64 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -461,7 +461,7 @@
   }
 
   if (out_rewrite_method != nullptr) {
-    const StringPiece& type_str = ToString(name.type);
+    const StringPiece& type_str = to_string(name.type);
     out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);",
                                                      type_str.data(), field_name.data(),
                                                      type_str.data(), field_name.data()));
@@ -584,7 +584,7 @@
           (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
 
       std::unique_ptr<ClassDefinition> class_def = util::make_unique<ClassDefinition>(
-          ToString(type->type), ClassQualifier::kStatic, force_creation_if_empty);
+          to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty);
       if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
                        rewrite_method.get(), out_r_txt)) {
         return false;
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 21c6b11..58d0607 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -37,17 +37,14 @@
   CHECK(master_package_ != nullptr) << "package name or ID already taken";
 }
 
-bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay,
-                        io::IFileCollection* collection) {
+bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay) {
   // We allow adding new resources if this is not an overlay, or if the options allow overlays
   // to add new resources.
-  return MergeImpl(src, table, collection, overlay,
-                   options_.auto_add_overlay || !overlay /*allow_new*/);
+  return MergeImpl(src, table, overlay, options_.auto_add_overlay || !overlay /*allow_new*/);
 }
 
 // This will merge packages with the same package name (or no package name).
-bool TableMerger::MergeImpl(const Source& src, ResourceTable* table,
-                            io::IFileCollection* collection, bool overlay, bool allow_new) {
+bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, bool overlay, bool allow_new) {
   bool error = false;
   for (auto& package : table->packages) {
     // Only merge an empty package or the package we're building.
@@ -55,37 +52,20 @@
     // This is because at compile time it is unknown if the attributes are
     // simply uses of the attribute or definitions.
     if (package->name.empty() || context_->GetCompilationPackage() == package->name) {
-      FileMergeCallback callback;
-      if (collection) {
-        callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
-                       FileReference* new_file, FileReference* old_file) -> bool {
-          // The old file's path points inside the APK, so we can use it as is.
-          io::IFile* f = collection->FindFile(*old_file->path);
-          if (!f) {
-            context_->GetDiagnostics()->Error(DiagMessage(src)
-                                              << "file '" << *old_file->path << "' not found");
-            return false;
-          }
-
-          new_file->file = f;
-          return true;
-        };
-      }
-
       // Merge here. Once the entries are merged and mangled, any references to them are still
       // valid. This is because un-mangled references are mangled, then looked up at resolution
       // time. Also, when linking, we convert references with no package name to use the compilation
       // package name.
-      error |=
-          !DoMerge(src, table, package.get(), false /* mangle */, overlay, allow_new, callback);
+      error |= !DoMerge(src, table, package.get(), false /*mangle*/, overlay, allow_new);
     }
   }
   return !error;
 }
 
-// This will merge and mangle resources from a static library.
+// This will merge and mangle resources from a static library. It is assumed that all FileReferences
+// have correctly set their io::IFile*.
 bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_name,
-                                 ResourceTable* table, io::IFileCollection* collection) {
+                                 ResourceTable* table) {
   bool error = false;
   for (auto& package : table->packages) {
     // Warn of packages with an unrelated ID.
@@ -96,23 +76,7 @@
 
     bool mangle = package_name != context_->GetCompilationPackage();
     merged_packages_.insert(package->name);
-
-    auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
-                        FileReference* new_file, FileReference* old_file) -> bool {
-      // The old file's path points inside the APK, so we can use it as is.
-      io::IFile* f = collection->FindFile(*old_file->path);
-      if (!f) {
-        context_->GetDiagnostics()->Error(DiagMessage(src)
-                                          << "file '" << *old_file->path << "' not found");
-        return false;
-      }
-
-      new_file->file = f;
-      return true;
-    };
-
-    error |= !DoMerge(src, table, package.get(), mangle, false /*overlay*/, true /*allow_new*/,
-                      callback);
+    error |= !DoMerge(src, table, package.get(), mangle, false /*overlay*/, true /*allow_new*/);
   }
   return !error;
 }
@@ -187,7 +151,7 @@
 
 static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
                                                        const ResourceNameRef& res_name,
-                                                       const bool overlay,
+                                                       bool overlay,
                                                        ResourceConfigValue* dst_config_value,
                                                        ResourceConfigValue* src_config_value,
                                                        StringPool* pool) {
@@ -220,10 +184,8 @@
 }
 
 bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
-                          ResourceTablePackage* src_package,
-                          const bool mangle_package, const bool overlay,
-                          const bool allow_new_resources,
-                          const FileMergeCallback& callback) {
+                          ResourceTablePackage* src_package, bool mangle_package, bool overlay,
+                          bool allow_new_resources) {
   bool error = false;
 
   for (auto& src_type : src_package->types) {
@@ -292,13 +254,6 @@
           } else {
             new_file_ref = std::unique_ptr<FileReference>(f->Clone(&master_table_->string_pool));
           }
-
-          if (callback) {
-            if (!callback(res_name, src_config_value->config, new_file_ref.get(), f)) {
-              error = true;
-              continue;
-            }
-          }
           dst_config_value->value = std::move(new_file_ref);
 
         } else {
@@ -343,8 +298,8 @@
       ->FindOrCreateValue(file_desc.config, {})
       ->value = std::move(file_ref);
 
-  return DoMerge(file->GetSource(), &table, pkg, false /* mangle */, overlay /* overlay */,
-                 true /* allow_new */, {});
+  return DoMerge(file->GetSource(), &table, pkg, false /*mangle*/, overlay /*overlay*/,
+                 true /*allow_new*/);
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index d024aa4..47e23dd 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -40,6 +40,9 @@
 // TableMerger takes resource tables and merges all packages within the tables that have the same
 // package ID.
 //
+// It is assumed that any FileReference values have their io::IFile pointer set to point to the
+// file they represent.
+//
 // If a package has a different name, all the entries in that table have their names mangled
 // to include the package name. This way there are no collisions. In order to do this correctly,
 // the TableMerger needs to also mangle any FileReference paths. Once these are mangled, the
@@ -60,14 +63,11 @@
 
   // Merges resources from the same or empty package. This is for local sources.
   // If overlay is true, the resources are treated as overlays.
-  // An io::IFileCollection is optional and used to find the referenced Files and process them.
-  bool Merge(const Source& src, ResourceTable* table, bool overlay,
-             io::IFileCollection* collection = nullptr);
+  bool Merge(const Source& src, ResourceTable* table, bool overlay);
 
   // Merges resources from the given package, mangling the name. This is for static libraries.
-  // An io::IFileCollection is needed in order to find the referenced Files and process them.
-  bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table,
-                      io::IFileCollection* collection);
+  // All FileReference values must have their io::IFile set.
+  bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table);
 
   // Merges a compiled file that belongs to this same or empty package.
   bool MergeFile(const ResourceFile& fileDesc, bool overlay, io::IFile* file);
@@ -75,23 +75,16 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(TableMerger);
 
-  using FileMergeCallback = std::function<bool(const ResourceNameRef&,
-                                               const ConfigDescription& config,
-                                               FileReference*, FileReference*)>;
-
   IAaptContext* context_;
   ResourceTable* master_table_;
   TableMergerOptions options_;
   ResourceTablePackage* master_package_;
   std::set<std::string> merged_packages_;
 
-  bool MergeImpl(const Source& src, ResourceTable* src_table,
-                 io::IFileCollection* collection, bool overlay, bool allow_new);
+  bool MergeImpl(const Source& src, ResourceTable* src_table, bool overlay, bool allow_new);
 
-  bool DoMerge(const Source& src, ResourceTable* src_table,
-               ResourceTablePackage* src_package, const bool mangle_package,
-               const bool overlay, const bool allow_new_resources,
-               const FileMergeCallback& callback);
+  bool DoMerge(const Source& src, ResourceTable* src_table, ResourceTablePackage* src_package,
+               bool mangle_package, bool overlay, bool allow_new_resources);
 
   std::unique_ptr<FileReference> CloneAndMangleFile(const std::string& package,
                                                     const FileReference& value);
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 3499809..6aab8de 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -22,11 +22,12 @@
 
 using ::aapt::test::ValueEq;
 using ::testing::Contains;
-using ::testing::NotNull;
-using ::testing::UnorderedElementsAreArray;
-using ::testing::Pointee;
-using ::testing::Field;
 using ::testing::Eq;
+using ::testing::Field;
+using ::testing::NotNull;
+using ::testing::Pointee;
+using ::testing::StrEq;
+using ::testing::UnorderedElementsAreArray;
 
 namespace aapt {
 
@@ -67,10 +68,9 @@
 
   ResourceTable final_table;
   TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
-  io::FileCollection collection;
 
   ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
-  ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
+  ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get()));
 
   EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0);
 
@@ -122,32 +122,35 @@
 }
 
 TEST_F(TableMergerTest, MergeFileReferences) {
+  test::TestFile file_a("res/xml/file.xml");
+  test::TestFile file_b("res/xml/file.xml");
+
   std::unique_ptr<ResourceTable> table_a =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.a", 0x7f)
-          .AddFileReference("com.app.a:xml/file", "res/xml/file.xml")
+          .AddFileReference("com.app.a:xml/file", "res/xml/file.xml", &file_a)
           .Build();
   std::unique_ptr<ResourceTable> table_b =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.b", 0x7f)
-          .AddFileReference("com.app.b:xml/file", "res/xml/file.xml")
+          .AddFileReference("com.app.b:xml/file", "res/xml/file.xml", &file_b)
           .Build();
 
   ResourceTable final_table;
   TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
-  io::FileCollection collection;
-  collection.InsertFile("res/xml/file.xml");
 
   ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
-  ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
+  ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get()));
 
   FileReference* f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/file");
   ASSERT_THAT(f, NotNull());
-  EXPECT_EQ(std::string("res/xml/file.xml"), *f->path);
+  EXPECT_THAT(*f->path, StrEq("res/xml/file.xml"));
+  EXPECT_THAT(f->file, Eq(&file_a));
 
   f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/com.app.b$file");
   ASSERT_THAT(f, NotNull());
-  EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path);
+  EXPECT_THAT(*f->path, StrEq("res/xml/com.app.b$file.xml"));
+  EXPECT_THAT(f->file, Eq(&file_b));
 }
 
 TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 6803088..473693c 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -265,12 +265,14 @@
                                        const PostProcessingConfiguration& config,
                                        std::unique_ptr<XmlResource>* updated_manifest,
                                        IDiagnostics* diag) {
-  *updated_manifest = apk_->InflateManifest(context_);
-  XmlResource* manifest = updated_manifest->get();
-  if (manifest == nullptr) {
+  const xml::XmlResource* apk_manifest = apk_->GetManifest();
+  if (apk_manifest == nullptr) {
     return false;
   }
 
+  *updated_manifest = apk_manifest->Clone();
+  XmlResource* manifest = updated_manifest->get();
+
   // Make sure the first element is <manifest> with package attribute.
   xml::Element* manifest_el = manifest->root.get();
   if (manifest_el == nullptr) {
diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
index c8f3524..30c9146 100644
--- a/tools/aapt2/optimize/MultiApkGenerator_test.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
@@ -106,7 +106,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterNewerVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(19).Build();
   PostProcessingConfiguration empty_config;
   TableFlattenerOptions table_flattener_options;
@@ -147,7 +147,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterOlderVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
   PostProcessingConfiguration empty_config;
   TableFlattenerOptions table_flattener_options;
@@ -186,7 +186,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterNoVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
   PostProcessingConfiguration empty_config;
   TableFlattenerOptions table_flattener_options;
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 882a85b..2d517c7 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -289,7 +289,7 @@
   const android::ResTable& table = assets_.getResources(false);
 
   const std::u16string package16 = util::Utf8ToUtf16(name.package);
-  const std::u16string type16 = util::Utf8ToUtf16(ToString(name.type));
+  const std::u16string type16 = util::Utf8ToUtf16(to_string(name.type));
   const std::u16string entry16 = util::Utf8ToUtf16(name.entry);
 
   uint32_t type_spec_flags = 0;
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 5a62e97..ecec63f 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -78,21 +78,27 @@
 }
 
 ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
-                                                             const StringPiece& path) {
-  return AddFileReference(name, {}, path);
+                                                             const StringPiece& path,
+                                                             io::IFile* file) {
+  return AddFileReference(name, {}, path, file);
 }
 
 ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
                                                              const ResourceId& id,
-                                                             const StringPiece& path) {
-  return AddValue(name, id, util::make_unique<FileReference>(table_->string_pool.MakeRef(path)));
+                                                             const StringPiece& path,
+                                                             io::IFile* file) {
+  auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
+  file_ref->file = file;
+  return AddValue(name, id, std::move(file_ref));
 }
 
 ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
                                                              const StringPiece& path,
-                                                             const ConfigDescription& config) {
-  return AddValue(name, config, {},
-                  util::make_unique<FileReference>(table_->string_pool.MakeRef(path)));
+                                                             const ConfigDescription& config,
+                                                             io::IFile* file) {
+  auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
+  file_ref->file = file;
+  return AddValue(name, config, {}, std::move(file_ref));
 }
 
 ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 263fb55..4cdfc33 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -52,12 +52,15 @@
   ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
                                   const ConfigDescription& config, const android::StringPiece& str);
   ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
-                                         const android::StringPiece& path);
+                                         const android::StringPiece& path,
+                                         io::IFile* file = nullptr);
   ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id,
-                                         const android::StringPiece& path);
+                                         const android::StringPiece& path,
+                                         io::IFile* file = nullptr);
   ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
                                          const android::StringPiece& path,
-                                         const ConfigDescription& config);
+                                         const ConfigDescription& config,
+                                         io::IFile* file = nullptr);
   ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value);
   ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id,
                                  std::unique_ptr<Value> value);
diff --git a/tools/aapt2/text/Printer.cpp b/tools/aapt2/text/Printer.cpp
new file mode 100644
index 0000000..38b3585
--- /dev/null
+++ b/tools/aapt2/text/Printer.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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 "text/Printer.h"
+
+#include <algorithm>
+
+#include "io/Util.h"
+
+using ::aapt::io::OutputStream;
+using ::android::StringPiece;
+
+namespace aapt {
+namespace text {
+
+void Printer::Println(const StringPiece& str) {
+  Print(str);
+  Print("\n");
+}
+
+void Printer::Println() {
+  Print("\n");
+}
+
+void Printer::Print(const StringPiece& str) {
+  if (error_) {
+    return;
+  }
+
+  auto remaining_str_begin = str.begin();
+  const auto remaining_str_end = str.end();
+  while (remaining_str_end != remaining_str_begin) {
+    // Find the next new-line.
+    const auto new_line_iter = std::find(remaining_str_begin, remaining_str_end, '\n');
+
+    // We will copy the string up until the next new-line (or end of string).
+    const StringPiece str_to_copy = str.substr(remaining_str_begin, new_line_iter);
+    if (!str_to_copy.empty()) {
+      if (needs_indent_) {
+        for (int i = 0; i < indent_level_; i++) {
+          if (!io::Copy(out_, "  ")) {
+            error_ = true;
+            return;
+          }
+        }
+        needs_indent_ = false;
+      }
+
+      if (!io::Copy(out_, str_to_copy)) {
+        error_ = true;
+        return;
+      }
+    }
+
+    // If we found a new-line.
+    if (new_line_iter != remaining_str_end) {
+      if (!io::Copy(out_, "\n")) {
+        error_ = true;
+        return;
+      }
+      needs_indent_ = true;
+      // Ok to increment iterator here because we know that the '\n' character is one byte.
+      remaining_str_begin = new_line_iter + 1;
+    } else {
+      remaining_str_begin = new_line_iter;
+    }
+  }
+}
+
+void Printer::Indent() {
+  ++indent_level_;
+}
+
+void Printer::Undent() {
+  --indent_level_;
+}
+
+}  // namespace text
+}  // namespace aapt
diff --git a/tools/aapt2/text/Printer.h b/tools/aapt2/text/Printer.h
new file mode 100644
index 0000000..94b3c0b
--- /dev/null
+++ b/tools/aapt2/text/Printer.h
@@ -0,0 +1,53 @@
+/*
+ * 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 AAPT_TEXT_PRINTER_H
+#define AAPT_TEXT_PRINTER_H
+
+#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
+
+#include "io/Io.h"
+
+namespace aapt {
+namespace text {
+
+// An indenting Printer that helps write formatted text to the OutputStream.
+class Printer {
+ public:
+  explicit Printer(::aapt::io::OutputStream* out) : out_(out) {
+  }
+
+  void Print(const ::android::StringPiece& str);
+  void Println(const ::android::StringPiece& str);
+  void Println();
+
+  void Indent();
+  void Undent();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Printer);
+
+  ::aapt::io::OutputStream* out_;
+  int indent_level_ = 0;
+  bool needs_indent_ = false;
+  bool error_ = false;
+};
+
+}  // namespace text
+}  // namespace aapt
+
+#endif  // AAPT_TEXT_PRINTER_H
diff --git a/tools/aapt2/text/Printer_test.cpp b/tools/aapt2/text/Printer_test.cpp
new file mode 100644
index 0000000..58beae7
--- /dev/null
+++ b/tools/aapt2/text/Printer_test.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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 "text/Printer.h"
+
+#include "io/StringStream.h"
+#include "test/Test.h"
+
+using ::aapt::io::StringOutputStream;
+using ::android::StringPiece;
+using ::testing::StrEq;
+
+namespace aapt {
+namespace text {
+
+TEST(PrinterTest, PrintsToStreamWithIndents) {
+  std::string result;
+  StringOutputStream out(&result);
+  Printer printer(&out);
+
+  printer.Print("Hello");
+  out.Flush();
+  EXPECT_THAT(result, StrEq("Hello"));
+
+  printer.Println();
+  out.Flush();
+  EXPECT_THAT(result, StrEq("Hello\n"));
+
+  // This shouldn't print anything yet.
+  printer.Indent();
+  out.Flush();
+  EXPECT_THAT(result, StrEq("Hello\n"));
+
+  // Now we should see the indent.
+  printer.Print("world!");
+  out.Flush();
+  EXPECT_THAT(result, StrEq("Hello\n  world!"));
+
+  printer.Println(" What a\nlovely day.");
+  out.Flush();
+  EXPECT_THAT(result, StrEq("Hello\n  world! What a\n  lovely day.\n"));
+
+  // This shouldn't print anything yet.
+  printer.Undent();
+  out.Flush();
+  EXPECT_THAT(result, StrEq("Hello\n  world! What a\n  lovely day.\n"));
+
+  printer.Println("Isn't it?");
+  out.Flush();
+  EXPECT_THAT(result, StrEq("Hello\n  world! What a\n  lovely day.\nIsn't it?\n"));
+}
+
+}  // namespace text
+}  // namespace aapt
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 3522506..b0cf44a 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -258,8 +258,7 @@
   }
 }
 
-std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag,
-                                     const Source& source) {
+std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string* out_error) {
   // We import the android namespace because on Windows NO_ERROR is a macro, not
   // an enum, which causes errors when qualifying it with android::
   using namespace android;
@@ -270,7 +269,10 @@
   std::unique_ptr<Element> pending_element;
 
   ResXMLTree tree;
-  if (tree.setTo(data, data_len) != NO_ERROR) {
+  if (tree.setTo(data, len) != NO_ERROR) {
+    if (out_error != nullptr) {
+      *out_error = "failed to initialize ResXMLTree";
+    }
     return {};
   }
 
@@ -361,6 +363,27 @@
   return util::make_unique<XmlResource>(ResourceFile{}, std::move(string_pool), std::move(root));
 }
 
+std::unique_ptr<XmlResource> XmlResource::Clone() const {
+  std::unique_ptr<XmlResource> cloned = util::make_unique<XmlResource>(file);
+  if (root != nullptr) {
+    cloned->root = root->CloneElement([&](const xml::Element& src, xml::Element* dst) {
+      dst->attributes.reserve(src.attributes.size());
+      for (const xml::Attribute& attr : src.attributes) {
+        xml::Attribute cloned_attr;
+        cloned_attr.name = attr.name;
+        cloned_attr.namespace_uri = attr.namespace_uri;
+        cloned_attr.value = attr.value;
+        cloned_attr.compiled_attribute = attr.compiled_attribute;
+        if (attr.compiled_value != nullptr) {
+          cloned_attr.compiled_value.reset(attr.compiled_value->Clone(&cloned->string_pool));
+        }
+        dst->attributes.push_back(std::move(cloned_attr));
+      }
+    });
+  }
+  return cloned;
+}
+
 Element* FindRootElement(Node* node) {
   if (node == nullptr) {
     return nullptr;
@@ -383,12 +406,7 @@
 }
 
 Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) {
-  for (auto& attr : attributes) {
-    if (ns == attr.namespace_uri && name == attr.name) {
-      return &attr;
-    }
-  }
-  return nullptr;
+  return const_cast<Attribute*>(static_cast<const Element*>(this)->FindAttribute(ns, name));
 }
 
 const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const {
@@ -404,17 +422,29 @@
   return FindChildWithAttribute(ns, name, {}, {}, {});
 }
 
+const Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) const {
+  return FindChildWithAttribute(ns, name, {}, {}, {});
+}
+
 Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
                                          const StringPiece& attr_ns, const StringPiece& attr_name,
                                          const StringPiece& attr_value) {
-  for (auto& child : children) {
-    if (Element* el = NodeCast<Element>(child.get())) {
+  return const_cast<Element*>(static_cast<const Element*>(this)->FindChildWithAttribute(
+      ns, name, attr_ns, attr_name, attr_value));
+}
+
+const Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
+                                               const StringPiece& attr_ns,
+                                               const StringPiece& attr_name,
+                                               const StringPiece& attr_value) const {
+  for (const auto& child : children) {
+    if (const Element* el = NodeCast<Element>(child.get())) {
       if (ns == el->namespace_uri && name == el->name) {
         if (attr_ns.empty() && attr_name.empty()) {
           return el;
         }
 
-        Attribute* attr = el->FindAttribute(attr_ns, attr_name);
+        const Attribute* attr = el->FindAttribute(attr_ns, attr_name);
         if (attr && attr_value == attr->value) {
           return el;
         }
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 063d7b9..cf06ba5 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -100,11 +100,21 @@
   Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);
   const Attribute* FindAttribute(const android::StringPiece& ns,
                                  const android::StringPiece& name) const;
+
   Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name);
+  const Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name) const;
+
   Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name,
                                   const android::StringPiece& attr_ns,
                                   const android::StringPiece& attr_name,
                                   const android::StringPiece& attr_value);
+
+  const Element* FindChildWithAttribute(const android::StringPiece& ns,
+                                        const android::StringPiece& name,
+                                        const android::StringPiece& attr_ns,
+                                        const android::StringPiece& attr_name,
+                                        const android::StringPiece& attr_value) const;
+
   std::vector<Element*> GetChildElements();
 
   // Due to overriding of subtypes not working with unique_ptr, define a convenience Clone method
@@ -139,16 +149,16 @@
   StringPool string_pool;
 
   std::unique_ptr<xml::Element> root;
+
+  std::unique_ptr<XmlResource> Clone() const;
 };
 
 // Inflates an XML DOM from an InputStream, logging errors to the logger.
-// Returns the root node on success, or nullptr on failure.
 std::unique_ptr<XmlResource> Inflate(io::InputStream* in, IDiagnostics* diag, const Source& source);
 
-// Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger.
-// Returns the root node on success, or nullptr on failure.
-std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag,
-                                     const Source& source);
+// Inflates an XML DOM from a binary ResXMLTree.
+std::unique_ptr<XmlResource> Inflate(const void* data, size_t len,
+                                     std::string* out_error = nullptr);
 
 Element* FindRootElement(Node* node);
 
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 34e6d3f..e5012d6 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -70,8 +70,7 @@
   ASSERT_TRUE(flattener.Consume(context.get(), doc.get()));
 
   auto block = util::Copy(buffer);
-  std::unique_ptr<XmlResource> new_doc =
-      Inflate(block.get(), buffer.size(), context->GetDiagnostics(), Source("test.xml"));
+  std::unique_ptr<XmlResource> new_doc = Inflate(block.get(), buffer.size(), nullptr);
   ASSERT_THAT(new_doc, NotNull());
 
   EXPECT_THAT(new_doc->root->name, StrEq("Layout"));
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 4f3acd6..04efd56 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -3437,6 +3437,7 @@
      * Set wifi verbose log. Called from developer settings.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     public void enableVerboseLogging (int verbose) {
         try {
             mService.enableVerboseLogging(verbose);