Merge "Move event logging to TransportClient and add connection event"
diff --git a/Android.bp b/Android.bp
index e5d4b8b..2685ac3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -471,8 +471,6 @@
         "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl",
         "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl",
         "telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl",
-        "telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl",
-        "telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl",
         "telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl",
         "telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl",
 	"telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl",
@@ -492,6 +490,8 @@
         "telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl",
         "telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl",
         "telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl",
+        "telephony/java/com/android/ims/internal/IImsRegistration.aidl",
+        "telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl",
         "telephony/java/com/android/ims/internal/IImsRcsFeature.aidl",
         "telephony/java/com/android/ims/internal/IImsService.aidl",
         "telephony/java/com/android/ims/internal/IImsServiceController.aidl",
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index 5653a03..93a0fc3 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -190,7 +190,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final PremeasuredText text = PremeasuredText.build(
+            final MeasuredText text = MeasuredText.build(
                     generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
             state.resumeTiming();
 
@@ -206,7 +206,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final PremeasuredText text = PremeasuredText.build(
+            final MeasuredText text = MeasuredText.build(
                     generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
             state.resumeTiming();
 
@@ -222,7 +222,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final PremeasuredText text = PremeasuredText.build(
+            final MeasuredText text = MeasuredText.build(
                     generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
             state.resumeTiming();
 
@@ -238,7 +238,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final PremeasuredText text = PremeasuredText.build(
+            final MeasuredText text = MeasuredText.build(
                     generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
             state.resumeTiming();
 
@@ -254,7 +254,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final PremeasuredText text = PremeasuredText.build(
+            final MeasuredText text = MeasuredText.build(
                     generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR);
             state.resumeTiming();
 
diff --git a/api/current.txt b/api/current.txt
index a560e96..c179ca8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6367,7 +6367,7 @@
     method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName);
     method public void addUserRestriction(android.content.ComponentName, java.lang.String);
     method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
-    method public boolean clearApplicationUserData(android.content.ComponentName, java.lang.String, android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener, java.util.concurrent.Executor);
+    method public boolean clearApplicationUserData(android.content.ComponentName, java.lang.String, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener);
     method public void clearCrossProfileIntentFilters(android.content.ComponentName);
     method public deprecated void clearDeviceOwnerApp(java.lang.String);
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
@@ -6403,6 +6403,7 @@
     method public int getLockTaskFeatures(android.content.ComponentName);
     method public java.lang.String[] getLockTaskPackages(android.content.ComponentName);
     method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName);
+    method public android.content.ComponentName getMandatoryBackupTransport();
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
     method public long getMaximumTimeToLock(android.content.ComponentName);
     method public int getOrganizationColor(android.content.ComponentName);
@@ -6502,6 +6503,7 @@
     method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException;
     method public void setLogoutEnabled(android.content.ComponentName, boolean);
     method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence);
+    method public void setMandatoryBackupTransport(android.content.ComponentName, android.content.ComponentName);
     method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
     method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
     method public void setMaximumTimeToLock(android.content.ComponentName, long);
@@ -20882,7 +20884,6 @@
     method public int getMaxWidth();
     method public java.lang.CharSequence getTextForImeAction(int);
     method public android.app.Dialog getWindow();
-    method public void hideSoftInputFromInputMethod(int);
     method public void hideStatusIcon();
     method public void hideWindow();
     method public boolean isExtractViewShown();
@@ -20930,6 +20931,7 @@
     method public void onWindowHidden();
     method public void onWindowShown();
     method public void requestHideSelf(int);
+    method public void requestShowSelf(int);
     method public boolean sendDefaultEditorAction(boolean);
     method public void sendDownUpKeyEvents(int);
     method public void sendKeyChar(char);
@@ -20942,7 +20944,6 @@
     method public void setInputMethodAndSubtype(java.lang.String, android.view.inputmethod.InputMethodSubtype);
     method public void setInputView(android.view.View);
     method public boolean shouldOfferSwitchingToNextInputMethod();
-    method public void showSoftInputFromInputMethod(int);
     method public void showStatusIcon(int);
     method public void showWindow(boolean);
     method public void switchInputMethod(java.lang.String);
@@ -26218,12 +26219,18 @@
   }
 
   public final class IpSecManager {
-    method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
-    method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    method public void applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+    method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
+    method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    method public void applyTransportModeTransform(java.net.Socket, int, android.net.IpSecTransform) throws java.io.IOException;
+    method public void applyTransportModeTransform(java.net.DatagramSocket, int, android.net.IpSecTransform) throws java.io.IOException;
+    method public void applyTransportModeTransform(java.io.FileDescriptor, int, android.net.IpSecTransform) throws java.io.IOException;
     method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
     method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
-    method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+    method public void removeTransportModeTransforms(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException;
+    method public void removeTransportModeTransforms(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException;
+    method public void removeTransportModeTransforms(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+    field public static final int DIRECTION_IN = 0; // 0x0
+    field public static final int DIRECTION_OUT = 1; // 0x1
   }
 
   public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
@@ -26246,18 +26253,15 @@
 
   public final class IpSecTransform implements java.lang.AutoCloseable {
     method public void close();
-    field public static final int DIRECTION_IN = 0; // 0x0
-    field public static final int DIRECTION_OUT = 1; // 0x1
   }
 
   public static class IpSecTransform.Builder {
     ctor public IpSecTransform.Builder(android.content.Context);
-    method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(int, android.net.IpSecAlgorithm);
-    method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
-    method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setAuthentication(android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setEncryption(android.net.IpSecAlgorithm);
     method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
-    method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
   }
 
   public class LinkAddress implements android.os.Parcelable {
@@ -26340,10 +26344,10 @@
   }
 
   public final class MacAddress implements android.os.Parcelable {
-    method public int addressType();
     method public int describeContents();
     method public static android.net.MacAddress fromBytes(byte[]);
     method public static android.net.MacAddress fromString(java.lang.String);
+    method public int getAddressType();
     method public boolean isLocallyAssigned();
     method public byte[] toByteArray();
     method public java.lang.String toOuiString();
@@ -26353,7 +26357,6 @@
     field public static final int TYPE_BROADCAST = 3; // 0x3
     field public static final int TYPE_MULTICAST = 2; // 0x2
     field public static final int TYPE_UNICAST = 1; // 0x1
-    field public static final int TYPE_UNKNOWN = 0; // 0x0
   }
 
   public class MailTo {
@@ -32462,6 +32465,7 @@
     field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale";
     field public static final java.lang.String DISALLOW_CONFIG_LOCATION_MODE = "no_config_location_mode";
     field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
+    field public static final java.lang.String DISALLOW_CONFIG_SCREEN_TIMEOUT = "no_config_screen_timeout";
     field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
     field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
     field public static final java.lang.String DISALLOW_CONFIG_WIFI = "no_config_wifi";
@@ -42296,20 +42300,9 @@
     method public boolean isAllowed(char);
   }
 
-  public abstract interface NoCopySpan {
-  }
-
-  public static class NoCopySpan.Concrete implements android.text.NoCopySpan {
-    ctor public NoCopySpan.Concrete();
-  }
-
-  public abstract interface ParcelableSpan implements android.os.Parcelable {
-    method public abstract int getSpanTypeId();
-  }
-
-  public class PremeasuredText implements android.text.Spanned {
-    method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic);
-    method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int);
+  public class MeasuredText implements android.text.Spanned {
+    method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic);
+    method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int);
     method public char charAt(int);
     method public int getEnd();
     method public android.text.TextPaint getPaint();
@@ -42328,6 +42321,17 @@
     method public java.lang.CharSequence subSequence(int, int);
   }
 
+  public abstract interface NoCopySpan {
+  }
+
+  public static class NoCopySpan.Concrete implements android.text.NoCopySpan {
+    ctor public NoCopySpan.Concrete();
+  }
+
+  public abstract interface ParcelableSpan implements android.os.Parcelable {
+    method public abstract int getSpanTypeId();
+  }
+
   public class Selection {
     method public static boolean extendDown(android.text.Spannable, android.text.Layout);
     method public static boolean extendLeft(android.text.Spannable, android.text.Layout);
@@ -47889,7 +47893,6 @@
     field public static final int FIRST_APPLICATION_WINDOW = 1; // 0x1
     field public static final int FIRST_SUB_WINDOW = 1000; // 0x3e8
     field public static final int FIRST_SYSTEM_WINDOW = 2000; // 0x7d0
-    field public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 1L; // 0x1L
     field public static final int FLAGS_CHANGED = 4; // 0x4
     field public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 1; // 0x1
     field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000
@@ -47927,6 +47930,9 @@
     field public static final int LAST_SUB_WINDOW = 1999; // 0x7cf
     field public static final int LAST_SYSTEM_WINDOW = 2999; // 0xbb7
     field public static final int LAYOUT_CHANGED = 1; // 0x1
+    field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; // 0x1
+    field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; // 0x0
+    field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2; // 0x2
     field public static final int MEMORY_TYPE_CHANGED = 256; // 0x100
     field public static final deprecated int MEMORY_TYPE_GPU = 2; // 0x2
     field public static final deprecated int MEMORY_TYPE_HARDWARE = 1; // 0x1
@@ -47984,11 +47990,11 @@
     field public float buttonBrightness;
     field public float dimAmount;
     field public int flags;
-    field public long flags2;
     field public int format;
     field public int gravity;
     field public float horizontalMargin;
     field public float horizontalWeight;
+    field public int layoutInDisplayCutoutMode;
     field public deprecated int memoryType;
     field public java.lang.String packageName;
     field public int preferredDisplayModeId;
@@ -48036,6 +48042,8 @@
     method public void setPackageName(java.lang.CharSequence);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+    field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
+    field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
     field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
     field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1
     field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index 002c2bd..762b6e8 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4454,6 +4454,8 @@
     method public deprecated void setDataEnabled(int, boolean);
     method public boolean setRadio(boolean);
     method public boolean setRadioPower(boolean);
+    method public void setSimPowerState(int);
+    method public void setSimPowerStateForSlot(int, int);
     method public deprecated void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
     method public void setVoiceActivationState(int);
     method public deprecated void silenceRinger();
@@ -4463,8 +4465,6 @@
     method public int[] supplyPukReportResult(java.lang.String, java.lang.String);
     method public void toggleRadioOnOff();
     method public void updateServiceLocation();
-    method public void setSimPowerState(int);
-    method public void setSimPowerStateForSlot(int, int);
     field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
     field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
@@ -4647,9 +4647,12 @@
   }
 
   public final class StatsManager {
+    method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String);
     method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String);
+    method public byte[] getData(java.lang.String);
     method public byte[] getData(long);
     method public byte[] getMetadata();
+    method public boolean removeConfiguration(java.lang.String);
     method public boolean removeConfiguration(long);
   }
 
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index ffe652f..2c35d41 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -186,7 +186,8 @@
     tests/statsd_test_util.cpp \
     tests/e2e/WakelockDuration_e2e_test.cpp \
     tests/e2e/MetricConditionLink_e2e_test.cpp \
-    tests/e2e/Attribution_e2e_test.cpp
+    tests/e2e/Attribution_e2e_test.cpp \
+    tests/e2e/GaugeMetric_e2e_test.cpp
 
 LOCAL_STATIC_LIBRARIES := \
     $(statsd_common_static_libraries) \
@@ -198,6 +199,24 @@
 
 include $(BUILD_NATIVE_TEST)
 
+##############################
+# stats proto static java lib
+##############################
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := statsdprotolite
+
+LOCAL_SRC_FILES := \
+    src/stats_log.proto \
+    src/statsd_config.proto \
+    src/atoms.proto
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    platformprotoslite
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
 
 statsd_common_src:=
 statsd_common_aidl_includes:=
@@ -208,4 +227,4 @@
 
 ##############################
 
-include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 09e10a1..301e3a5 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -99,6 +99,7 @@
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
+    FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
 
 };
 
diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h
index 845c138..d0f96a2 100644
--- a/cmds/statsd/src/dimension.h
+++ b/cmds/statsd/src/dimension.h
@@ -32,6 +32,10 @@
 const DimensionsValue* getSingleLeafValue(const DimensionsValue* value);
 DimensionsValue getSingleLeafValue(const DimensionsValue& value);
 
+// Appends the leaf node to the parent tree.
+void appendLeafNodeToParent(const Field& field, const DimensionsValue& value,
+                            DimensionsValue* parentValue);
+
 // Constructs the DimensionsValue protos from the FieldMatcher. Each DimensionsValue proto
 // represents a tree. When the input proto has repeated fields and the input "dimensions" wants
 // "ANY" locations, it will return multiple trees.
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 1a4888c..ae47bd8 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -114,6 +114,9 @@
 
 void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
     flushIfNeededLocked(dumpTimeNs);
+    ProtoOutputStream pbOutput;
+    onDumpReportLocked(dumpTimeNs, &pbOutput);
+    parseProtoOutputStream(pbOutput, report);
 }
 
 void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 08b0981..a0239fc 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -141,6 +141,7 @@
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
+    FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index 09a43f5..cee9200 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -43,6 +43,19 @@
 // Helper function to write PulledAtomStats to ProtoOutputStream
 void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats>& pair,
                               util::ProtoOutputStream* protoOutput);
+
+template<class T>
+bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) {
+    std::string pbBytes;
+    auto iter = protoOutput.data();
+    while (iter.readBuffer() != NULL) {
+        size_t toRead = iter.currentToRead();
+         pbBytes.append(reinterpret_cast<const char*>(iter.readBuffer()), toRead);
+        iter.rp()->move(toRead);
+    }
+    return message->ParseFromArray(pbBytes.c_str(), pbBytes.size());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp
new file mode 100644
index 0000000..10a6c36
--- /dev/null
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp
@@ -0,0 +1,193 @@
+// 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 <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+namespace {
+
+StatsdConfig CreateStatsdConfigForPushedEvent() {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
+    *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
+
+    auto atomMatcher = CreateSimpleAtomMatcher("", android::util::APP_START_CHANGED);
+    *config.add_atom_matcher() = atomMatcher;
+
+    auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
+    *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
+        CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
+    *config.add_predicate() = isInBackgroundPredicate;
+
+    auto gaugeMetric = config.add_gauge_metric();
+    gaugeMetric->set_id(123456);
+    gaugeMetric->set_what(atomMatcher.id());
+    gaugeMetric->set_condition(isInBackgroundPredicate.id());
+    gaugeMetric->mutable_gauge_fields_filter()->set_include_all(false);
+    auto fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields();
+    fieldMatcher->set_field(android::util::APP_START_CHANGED);
+    fieldMatcher->add_child()->set_field(3);  // type (enum)
+    fieldMatcher->add_child()->set_field(4);  // activity_name(str)
+    fieldMatcher->add_child()->set_field(7);  // activity_start_msec(int64)
+    *gaugeMetric->mutable_dimensions() =
+        CreateDimensions(android::util::APP_START_CHANGED, {1 /* uid field */ });
+    gaugeMetric->set_bucket(ONE_MINUTE);
+
+    auto links = gaugeMetric->add_links();
+    links->set_condition(isInBackgroundPredicate.id());
+    auto dimensionWhat = links->mutable_dimensions_in_what();
+    dimensionWhat->set_field(android::util::APP_START_CHANGED);
+    dimensionWhat->add_child()->set_field(1);  // uid field.
+    auto dimensionCondition = links->mutable_dimensions_in_condition();
+    dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+    dimensionCondition->add_child()->set_field(1);  // uid field.
+    return config;
+}
+
+std::unique_ptr<LogEvent> CreateAppStartChangedEvent(
+    const int uid, const string& pkg_name, AppStartChanged::TransitionType type,
+    const string& activity_name, const string& calling_pkg_name, const bool is_instant_app,
+    int64_t activity_start_msec, uint64_t timestampNs) {
+    auto logEvent = std::make_unique<LogEvent>(
+        android::util::APP_START_CHANGED, timestampNs);
+    logEvent->write(uid);
+    logEvent->write(pkg_name);
+    logEvent->write(type);
+    logEvent->write(activity_name);
+    logEvent->write(calling_pkg_name);
+    logEvent->write(is_instant_app);
+    logEvent->write(activity_start_msec);
+    logEvent->init();
+    return logEvent;
+}
+
+}  // namespace
+
+TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) {
+    auto config = CreateStatsdConfigForPushedEvent();
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs =
+        TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+    int appUid1 = 123;
+    int appUid2 = 456;
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + 15));
+    events.push_back(CreateMoveToForegroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 250));
+    events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 350));
+    events.push_back(CreateMoveToForegroundEvent(
+        appUid1, bucketStartTimeNs + 2 * bucketSizeNs + 100));
+
+
+    events.push_back(CreateAppStartChangedEvent(
+        appUid1, "app1", AppStartChanged::WARM, "activity_name1", "calling_pkg_name1",
+        true /*is_instant_app*/, 101 /*activity_start_msec*/, bucketStartTimeNs + 10));
+    events.push_back(CreateAppStartChangedEvent(
+        appUid1, "app1", AppStartChanged::HOT, "activity_name2", "calling_pkg_name2",
+        true /*is_instant_app*/, 102 /*activity_start_msec*/, bucketStartTimeNs + 20));
+    events.push_back(CreateAppStartChangedEvent(
+        appUid1, "app1", AppStartChanged::COLD, "activity_name3", "calling_pkg_name3",
+        true /*is_instant_app*/, 103 /*activity_start_msec*/, bucketStartTimeNs + 30));
+    events.push_back(CreateAppStartChangedEvent(
+        appUid1, "app1", AppStartChanged::WARM, "activity_name4", "calling_pkg_name4",
+        true /*is_instant_app*/, 104 /*activity_start_msec*/,
+        bucketStartTimeNs + bucketSizeNs + 30));
+    events.push_back(CreateAppStartChangedEvent(
+        appUid1, "app1", AppStartChanged::COLD, "activity_name5", "calling_pkg_name5",
+        true /*is_instant_app*/, 105 /*activity_start_msec*/,
+        bucketStartTimeNs + 2 * bucketSizeNs));
+    events.push_back(CreateAppStartChangedEvent(
+        appUid1, "app1", AppStartChanged::HOT, "activity_name6", "calling_pkg_name6",
+        false /*is_instant_app*/, 106 /*activity_start_msec*/,
+        bucketStartTimeNs + 2 * bucketSizeNs + 10));
+
+    events.push_back(CreateMoveToBackgroundEvent(appUid2, bucketStartTimeNs + bucketSizeNs + 10));
+    events.push_back(CreateAppStartChangedEvent(
+        appUid2, "app2", AppStartChanged::COLD, "activity_name7", "calling_pkg_name7",
+        true /*is_instant_app*/, 201 /*activity_start_msec*/,
+        bucketStartTimeNs + 2 * bucketSizeNs + 10));
+
+    sortLogEventsByTimestamp(&events);
+
+    for (const auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, &reports);
+    EXPECT_EQ(reports.reports_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    EXPECT_EQ(gaugeMetrics.data_size(), 2);
+
+    auto data = gaugeMetrics.data(0);
+    EXPECT_EQ(data.dimension().field(), android::util::APP_START_CHANGED);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1 /* uid field */);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid1);
+    EXPECT_EQ(data.bucket_info_size(), 3);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::HOT);
+    EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name2");
+    EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 102L);
+
+    EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().type(), AppStartChanged::WARM);
+    EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_name(), "activity_name4");
+    EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_start_msec(), 104L);
+
+    EXPECT_EQ(data.bucket_info(2).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(2).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().type(), AppStartChanged::COLD);
+    EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_name(), "activity_name5");
+    EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_start_msec(), 105L);
+
+    data = gaugeMetrics.data(1);
+    EXPECT_EQ(data.dimension().field(), android::util::APP_START_CHANGED);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1 /* uid field */);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid2);
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::COLD);
+    EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name7");
+    EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 201L);
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
index 2783356..fcdaafc 100644
--- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
@@ -26,6 +26,8 @@
 
 #ifdef __ANDROID__
 
+namespace {
+
 StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) {
     StatsdConfig config;
     *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
@@ -56,6 +58,8 @@
     return config;
 }
 
+}  // namespace
+
 TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) {
     ConfigKey cfgKey;
     for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) {
@@ -94,6 +98,7 @@
         auto releaseEvent2 = CreateReleaseWakelockEvent(
             attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15);
 
+
         std::vector<std::unique_ptr<LogEvent>> events;
 
         events.push_back(std::move(screenTurnedOnEvent));
@@ -119,18 +124,8 @@
 
         auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
         // Validate dimension value.
-        EXPECT_EQ(data.dimension().field(),
-                  android::util::WAKELOCK_STATE_CHANGED);
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
-        // Attribution field.
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
-        // Uid only.
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
-            .value_tuple().dimensions_value_size(), 1);
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
-            .value_tuple().dimensions_value(0).field(), 1);
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
-            .value_tuple().dimensions_value(0).value_int(), 111);
+        ValidateAttributionUidDimension(
+            data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111);
         // Validate bucket info.
         EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
         data = reports.reports(0).metrics(0).duration_metrics().data(0);
@@ -147,18 +142,8 @@
         EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
         data = reports.reports(0).metrics(0).duration_metrics().data(0);
         // Validate dimension value.
-        EXPECT_EQ(data.dimension().field(),
-                  android::util::WAKELOCK_STATE_CHANGED);
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
-        // Attribution field.
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
-        // Uid only.
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
-            .value_tuple().dimensions_value_size(), 1);
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
-            .value_tuple().dimensions_value(0).field(), 1);
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
-            .value_tuple().dimensions_value(0).value_int(), 111);
+        ValidateAttributionUidDimension(
+            data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111);
         // Two output buckets.
         // The wakelock holding interval in the 1st bucket starts from the screen off event and to
         // the end of the 1st bucket.
@@ -167,6 +152,32 @@
         // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and
         // ends at the second screen on event.
         EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL);
+
+        events.clear();
+        events.push_back(CreateScreenStateChangedEvent(
+            ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 2 * bucketSizeNs + 90));
+        events.push_back(CreateAcquireWakelockEvent(
+            attributions1, "wl3", bucketStartTimeNs + 2 * bucketSizeNs + 100));
+        events.push_back(CreateReleaseWakelockEvent(
+            attributions1, "wl3", bucketStartTimeNs + 5 * bucketSizeNs + 100));
+        sortLogEventsByTimestamp(&events);
+        for (const auto& event : events) {
+            processor->OnLogEvent(event.get());
+        }
+        reports.Clear();
+        processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports);
+        EXPECT_EQ(reports.reports_size(), 1);
+        EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+        EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+        EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6);
+        data = reports.reports(0).metrics(0).duration_metrics().data(0);
+        ValidateAttributionUidDimension(
+            data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111);
+        // The last wakelock holding spans 4 buckets.
+        EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100);
+        EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs);
+        EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs);
+        EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL);
     }
 }
 
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index e788235..718b2e1 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -19,6 +19,14 @@
 namespace os {
 namespace statsd {
 
+AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId(name));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(atomId);
+    return atom_matcher;
+}
+
 AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name,
                                                   WakelockStateChanged::State state) {
     AtomMatcher atom_matcher;
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 1bbbd9a..7eb93b9 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -23,6 +23,9 @@
 namespace os {
 namespace statsd {
 
+// Create AtomMatcher proto to simply match a specific atom type.
+AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
+
 // Create AtomMatcher proto for acquiring wakelock.
 AtomMatcher CreateAcquireWakelockAtomMatcher();
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e610ac4..70c383b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1752,9 +1752,11 @@
                     handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
                             (IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
                     break;
-                case ATTACH_AGENT:
-                    handleAttachAgent((String) msg.obj);
+                case ATTACH_AGENT: {
+                    Application app = getApplication();
+                    handleAttachAgent((String) msg.obj, app != null ? app.mLoadedApk : null);
                     break;
+                }
                 case APPLICATION_INFO_CHANGED:
                     mUpdatingSystemConfig = true;
                     try {
@@ -3246,11 +3248,23 @@
         }
     }
 
-    static final void handleAttachAgent(String agent) {
+    private static boolean attemptAttachAgent(String agent, ClassLoader classLoader) {
         try {
-            VMDebug.attachAgent(agent);
+            VMDebug.attachAgent(agent, classLoader);
+            return true;
         } catch (IOException e) {
-            Slog.e(TAG, "Attaching agent failed: " + agent);
+            Slog.e(TAG, "Attaching agent with " + classLoader + " failed: " + agent);
+            return false;
+        }
+    }
+
+    static void handleAttachAgent(String agent, LoadedApk loadedApk) {
+        ClassLoader classLoader = loadedApk != null ? loadedApk.getClassLoader() : null;
+        if (attemptAttachAgent(agent, classLoader)) {
+            return;
+        }
+        if (classLoader != null) {
+            attemptAttachAgent(agent, null);
         }
     }
 
@@ -5542,12 +5556,16 @@
         mCompatConfiguration = new Configuration(data.config);
 
         mProfiler = new Profiler();
+        String agent = null;
         if (data.initProfilerInfo != null) {
             mProfiler.profileFile = data.initProfilerInfo.profileFile;
             mProfiler.profileFd = data.initProfilerInfo.profileFd;
             mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
             mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
             mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
+            if (data.initProfilerInfo.attachAgentDuringBind) {
+                agent = data.initProfilerInfo.agent;
+            }
         }
 
         // send up app name; do this *before* waiting for debugger
@@ -5597,6 +5615,10 @@
 
         data.loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo);
 
+        if (agent != null) {
+            handleAttachAgent(agent, data.loadedApk);
+        }
+
         /**
          * Switch this process to density compatibility mode if needed.
          */
diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java
index d523427..a295c4c 100644
--- a/core/java/android/app/ProfilerInfo.java
+++ b/core/java/android/app/ProfilerInfo.java
@@ -55,14 +55,24 @@
      */
     public final String agent;
 
+    /**
+     * Whether the {@link agent} should be attached early (before bind-application) or during
+     * bind-application. Agents attached prior to binding cannot be loaded from the app's APK
+     * directly and must be given as an absolute path (or available in the default LD_LIBRARY_PATH).
+     * Agents attached during bind-application will miss early setup (e.g., resource initialization
+     * and classloader generation), but are searched in the app's library search path.
+     */
+    public final boolean attachAgentDuringBind;
+
     public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop,
-            boolean streaming, String agent) {
+            boolean streaming, String agent, boolean attachAgentDuringBind) {
         profileFile = filename;
         profileFd = fd;
         samplingInterval = interval;
         autoStopProfiler = autoStop;
         streamingOutput = streaming;
         this.agent = agent;
+        this.attachAgentDuringBind = attachAgentDuringBind;
     }
 
     public ProfilerInfo(ProfilerInfo in) {
@@ -72,6 +82,7 @@
         autoStopProfiler = in.autoStopProfiler;
         streamingOutput = in.streamingOutput;
         agent = in.agent;
+        attachAgentDuringBind = in.attachAgentDuringBind;
     }
 
     /**
@@ -110,6 +121,7 @@
         out.writeInt(autoStopProfiler ? 1 : 0);
         out.writeInt(streamingOutput ? 1 : 0);
         out.writeString(agent);
+        out.writeBoolean(attachAgentDuringBind);
     }
 
     public static final Parcelable.Creator<ProfilerInfo> CREATOR =
@@ -132,6 +144,7 @@
         autoStopProfiler = in.readInt() != 0;
         streamingOutput = in.readInt() != 0;
         agent = in.readString();
+        attachAgentDuringBind = in.readBoolean();
     }
 
     @Override
diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java
index f06a925..d511c57 100644
--- a/core/java/android/app/admin/ConnectEvent.java
+++ b/core/java/android/app/admin/ConnectEvent.java
@@ -68,7 +68,7 @@
 
     @Override
     public String toString() {
-        return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp,
+        return String.format("ConnectEvent(%d, %s, %d, %d, %s)", mId, mIpAddress, mPort, mTimestamp,
                 mPackageName);
     }
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e334aab..0455949 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -18,7 +18,6 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
-import android.annotation.Condemned;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -50,8 +49,6 @@
 import android.net.ProxyInfo;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -8633,6 +8630,13 @@
      *
      * <p> Backup service is off by default when device owner is present.
      *
+     * <p> If backups are made mandatory by specifying a non-null mandatory backup transport using
+     * the {@link DevicePolicyManager#setMandatoryBackupTransport} method, the backup service is
+     * automatically enabled.
+     *
+     * <p> If the backup service is disabled using this method after the mandatory backup transport
+     * has been set, the mandatory backup transport is cleared.
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param enabled {@code true} to enable the backup service, {@code false} to disable it.
      * @throws SecurityException if {@code admin} is not a device owner.
@@ -8664,6 +8668,43 @@
     }
 
     /**
+     * Makes backups mandatory and enforces the usage of the specified backup transport.
+     *
+     * <p>When a {@code null} backup transport is specified, backups are made optional again.
+     * <p>Only device owner can call this method.
+     * <p>If backups were disabled and a non-null backup transport {@link ComponentName} is
+     * specified, backups will be enabled.
+     *
+     * @param admin admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param backupTransportComponent The backup transport layer to be used for mandatory backups.
+     * @throws SecurityException if {@code admin} is not a device owner.
+     */
+    public void setMandatoryBackupTransport(
+            @NonNull ComponentName admin, @Nullable ComponentName backupTransportComponent) {
+        try {
+            mService.setMandatoryBackupTransport(admin, backupTransportComponent);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the backup transport which has to be used for backups if backups are mandatory or
+     * {@code null} if backups are not mandatory.
+     *
+     * @return a {@link ComponentName} of the backup transport layer to be used if backups are
+     *         mandatory or {@code null} if backups are not mandatory.
+     */
+    public ComponentName getMandatoryBackupTransport() {
+        try {
+            return mService.getMandatoryBackupTransport();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
      * Called by a device owner to control the network logging feature.
      *
      * <p> Network logs contain DNS lookup and connect() library call events. The following library
@@ -8939,15 +8980,6 @@
         }
     }
 
-    /** {@hide} */
-    @Condemned
-    @Deprecated
-    public boolean clearApplicationUserData(@NonNull ComponentName admin,
-            @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener,
-            @NonNull Handler handler) {
-        return clearApplicationUserData(admin, packageName, listener, new HandlerExecutor(handler));
-    }
-
     /**
      * Called by the device owner or profile owner to clear application user data of a given
      * package. The behaviour of this is equivalent to the target application calling
@@ -8958,14 +8990,14 @@
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param packageName The name of the package which will have its user data wiped.
-     * @param listener A callback object that will inform the caller when the clearing is done.
      * @param executor The executor through which the listener should be invoked.
+     * @param listener A callback object that will inform the caller when the clearing is done.
      * @throws SecurityException if the caller is not the device owner/profile owner.
      * @return whether the clearing succeeded.
      */
     public boolean clearApplicationUserData(@NonNull ComponentName admin,
-            @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener,
-            @NonNull @CallbackExecutor Executor executor) {
+            @NonNull String packageName, @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnClearApplicationUserDataListener listener) {
         throwIfParentInstance("clearAppData");
         Preconditions.checkNotNull(executor);
         try {
diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java
index 4ddf13e..a2d704b 100644
--- a/core/java/android/app/admin/DnsEvent.java
+++ b/core/java/android/app/admin/DnsEvent.java
@@ -96,7 +96,7 @@
 
     @Override
     public String toString() {
-        return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname,
+        return String.format("DnsEvent(%d, %s, %s, %d, %d, %s)", mId, mHostname,
                 (mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses),
                 mIpAddressesCount, mTimestamp, mPackageName);
     }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7154053..9cdd1f8 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -359,6 +359,8 @@
 
     void setBackupServiceEnabled(in ComponentName admin, boolean enabled);
     boolean isBackupServiceEnabled(in ComponentName admin);
+    void setMandatoryBackupTransport(in ComponentName admin, in ComponentName backupTransportComponent);
+    ComponentName getMandatoryBackupTransport();
 
     void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled);
     boolean isNetworkLoggingEnabled(in ComponentName admin);
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index da81d19..3558e34 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -55,6 +55,22 @@
     // Transport should ignore its own moratoriums for call with this flag set.
     public static final int FLAG_USER_INITIATED = 1;
 
+    /**
+     * For key value backup, indicates that the backup data is a diff from a previous backup. The
+     * transport must apply this diff to an existing backup to build the new backup set.
+     *
+     * @hide
+     */
+    public static final int FLAG_INCREMENTAL = 1 << 1;
+
+    /**
+     * For key value backup, indicates that the backup data is a complete set, not a diff from a
+     * previous backup. The transport should clear any previous backup when storing this backup.
+     *
+     * @hide
+     */
+    public static final int FLAG_NON_INCREMENTAL = 1 << 2;
+
     IBackupTransport mBinderImpl = new TransportImpl();
 
     public IBinder getBinder() {
@@ -231,12 +247,18 @@
      * {@link #TRANSPORT_OK}, {@link #finishBackup} will then be called to ensure the data
      * is sent and recorded successfully.
      *
+     * If the backup data is a diff against the previous backup then the flag {@link
+     * BackupTransport#FLAG_INCREMENTAL} will be set. Otherwise, if the data is a complete backup
+     * set then {@link BackupTransport#FLAG_NON_INCREMENTAL} will be set. Before P neither flag will
+     * be set regardless of whether the backup is incremental or not.
+     *
      * @param packageInfo The identity of the application whose data is being backed up.
      *   This specifically includes the signature list for the package.
      * @param inFd Descriptor of file with data that resulted from invoking the application's
      *   BackupService.doBackup() method.  This may be a pipe rather than a file on
      *   persistent media, so it may not be seekable.
-     * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0.
+     * @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link
+     *   BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0.
      * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
      *  {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
      *  specific package, but allow others to proceed),
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 792cb5f..f3ca746 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -294,7 +294,8 @@
      *
      * @param transport ComponentName of the service hosting the transport. This is different from
      *                  the transport's name that is returned by {@link BackupTransport#name()}.
-     * @param listener A listener object to get a callback on the transport being selected.
+     * @param listener A listener object to get a callback on the transport being selected. It may
+     *                 be {@code null}.
      *
      * @hide
      */
diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java
index 5053dc6..c71bf2e 100644
--- a/core/java/android/app/job/JobParameters.java
+++ b/core/java/android/app/job/JobParameters.java
@@ -70,6 +70,7 @@
     private final Network network;
 
     private int stopReason; // Default value of stopReason is REASON_CANCELED
+    private String debugStopReason; // Human readable stop reason for debugging.
 
     /** @hide */
     public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
@@ -104,6 +105,14 @@
     }
 
     /**
+     * Reason onStopJob() was called on this job.
+     * @hide
+     */
+    public String getDebugStopReason() {
+        return debugStopReason;
+    }
+
+    /**
      * @return The extras you passed in when constructing this job with
      * {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will
      * never be null. If you did not set any extras this will be an empty bundle.
@@ -288,11 +297,13 @@
             network = null;
         }
         stopReason = in.readInt();
+        debugStopReason = in.readString();
     }
 
     /** @hide */
-    public void setStopReason(int reason) {
+    public void setStopReason(int reason, String debugStopReason) {
         stopReason = reason;
+        this.debugStopReason = debugStopReason;
     }
 
     @Override
@@ -323,6 +334,7 @@
             dest.writeInt(0);
         }
         dest.writeInt(stopReason);
+        dest.writeString(debugStopReason);
     }
 
     public static final Creator<JobParameters> CREATOR = new Creator<JobParameters>() {
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index a18f22e..6d6c02a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6285,6 +6285,31 @@
                 + " " + packageName + "}";
         }
 
+        public String dumpState_temp() {
+            String flags = "";
+            flags += ((applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : "");
+            flags += ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : "");
+            if ("".equals(flags)) {
+                flags = "-";
+            }
+            String privFlags = "";
+            privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : "");
+            privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : "");
+            privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : "");
+            if ("".equals(privFlags)) {
+                privFlags = "-";
+            }
+            return "Package{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + packageName
+            + ", ver:" + getLongVersionCode()
+            + ", path: " + codePath
+            + ", flags: " + flags
+            + ", privFlags: " + privFlags
+            + ", extra: " + (mExtras == null ? "<<NULL>>" : Integer.toHexString(System.identityHashCode(mExtras)) + "}")
+            + "}";
+        }
+
         @Override
         public int describeContents() {
             return 0;
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 3f6dd2e..078958a 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -179,6 +179,11 @@
     public abstract void persistBrightnessSliderEvents();
 
     /**
+     * Notifies the display manager that resource overlays have changed.
+     */
+    public abstract void onOverlayChanged();
+
+    /**
      * Describes the requested power state of the display.
      *
      * This object is intended to describe the general characteristics of the
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index e941279..7528bc3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1082,33 +1082,6 @@
     }
 
     /**
-     * Close/hide the input method's soft input area, so the user no longer
-     * sees it or can interact with it.  This can only be called
-     * from the currently active input method, as validated by the given token.
-     *
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY},
-     * {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set.
-     */
-    public void hideSoftInputFromInputMethod(int flags) {
-        mImm.hideSoftInputFromInputMethodInternal(mToken, flags);
-    }
-
-    /**
-     * Show the input method's soft input area, so the user
-     * sees the input method window and can interact with it.
-     * This can only be called from the currently active input method,
-     * as validated by the given token.
-     *
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT} or
-     * {@link InputMethodManager#SHOW_FORCED} bit set.
-     */
-    public void showSoftInputFromInputMethod(int flags) {
-        mImm.showSoftInputFromInputMethodInternal(mToken, flags);
-    }
-
-    /**
      * Force switch to the last used input method and subtype. If the last input method didn't have
      * any subtypes, the framework will simply switch to the last input method with no subtype
      * specified.
@@ -1738,7 +1711,7 @@
             // Rethrow the exception to preserve the existing behavior.  Some IMEs may have directly
             // called this method and relied on this exception for some clean-up tasks.
             // TODO: Give developers a clear guideline of whether it's OK to call this method or
-            // InputMethodManager#showSoftInputFromInputMethod() should always be used instead.
+            // InputMethodService#requestShowSelf(int) should always be used instead.
             throw e;
         } finally {
             // TODO: Is it OK to set true when we get BadTokenException?
@@ -2060,27 +2033,30 @@
 
     /**
      * Close this input method's soft input area, removing it from the display.
-     * The input method will continue running, but the user can no longer use
-     * it to generate input by touching the screen.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY
-     * InputMethodManager.HIDE_IMPLICIT_ONLY} bit set.
+     *
+     * The input method will continue running, but the user can no longer use it to generate input
+     * by touching the screen.
+     *
+     * @see InputMethodManager#HIDE_IMPLICIT_ONLY
+     * @see InputMethodManager#HIDE_NOT_ALWAYS
+     * @param flags Provides additional operating flags.
      */
     public void requestHideSelf(int flags) {
-        mImm.hideSoftInputFromInputMethod(mToken, flags);
+        mImm.hideSoftInputFromInputMethodInternal(mToken, flags);
     }
-    
+
     /**
-     * Show the input method. This is a call back to the
-     * IMF to handle showing the input method.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link InputMethodManager#SHOW_FORCED
-     * InputMethodManager.} bit set.
+     * Show the input method's soft input area, so the user sees the input method window and can
+     * interact with it.
+     *
+     * @see InputMethodManager#SHOW_IMPLICIT
+     * @see InputMethodManager#SHOW_FORCED
+     * @param flags Provides additional operating flags.
      */
-    private void requestShowSelf(int flags) {
-        mImm.showSoftInputFromInputMethod(mToken, flags);
+    public void requestShowSelf(int flags) {
+        mImm.showSoftInputFromInputMethodInternal(mToken, flags);
     }
-    
+
     private boolean handleBack(boolean doIt) {
         if (mShowInputRequested) {
             // If the soft input area is shown, back closes it and we
diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl
index d9b57db..3fe531f 100644
--- a/core/java/android/net/IIpSecService.aidl
+++ b/core/java/android/net/IIpSecService.aidl
@@ -31,7 +31,7 @@
 interface IIpSecService
 {
     IpSecSpiResponse allocateSecurityParameterIndex(
-            int direction, in String remoteAddress, int requestedSpi, in IBinder binder);
+            in String destinationAddress, int requestedSpi, in IBinder binder);
 
     void releaseSecurityParameterIndex(int resourceId);
 
@@ -43,7 +43,7 @@
 
     void deleteTransportModeTransform(int transformId);
 
-    void applyTransportModeTransform(in ParcelFileDescriptor socket, int transformId);
+    void applyTransportModeTransform(in ParcelFileDescriptor socket, int direction, int transformId);
 
-    void removeTransportModeTransform(in ParcelFileDescriptor socket, int transformId);
+    void removeTransportModeTransforms(in ParcelFileDescriptor socket, int transformId);
 }
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index 7d752e8..c69a4d4 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -256,13 +256,19 @@
         return getName().equals(AUTH_CRYPT_AES_GCM);
     }
 
+    // Because encryption keys are sensitive and userdebug builds are used by large user pools
+    // such as beta testers, we only allow sensitive info such as keys on eng builds.
+    private static boolean isUnsafeBuild() {
+        return Build.IS_DEBUGGABLE && Build.IS_ENG;
+    }
+
     @Override
     public String toString() {
         return new StringBuilder()
                 .append("{mName=")
                 .append(mName)
                 .append(", mKey=")
-                .append(Build.IS_DEBUGGABLE ? HexDump.toHexString(mKey) : "<hidden>")
+                .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>")
                 .append(", mTruncLenBits=")
                 .append(mTruncLenBits)
                 .append("}")
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index f54ceb5..80b0af3 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -32,59 +32,29 @@
     // MODE_TRANSPORT or MODE_TUNNEL
     private int mMode = IpSecTransform.MODE_TRANSPORT;
 
-    // Needs to be valid only for tunnel mode
     // Preventing this from being null simplifies Java->Native binder
-    private String mLocalAddress = "";
+    private String mSourceAddress = "";
 
     // Preventing this from being null simplifies Java->Native binder
-    private String mRemoteAddress = "";
+    private String mDestinationAddress = "";
 
     // The underlying Network that represents the "gateway" Network
     // for outbound packets. It may also be used to select packets.
     private Network mNetwork;
 
-    /**
-     * This class captures the parameters that specifically apply to inbound or outbound traffic.
-     */
-    public static class Flow {
-        // Minimum requirements for identifying a transform
-        // SPI identifying the IPsec flow in packet processing
-        // and a remote IP address
-        private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID;
+    // Minimum requirements for identifying a transform
+    // SPI identifying the IPsec SA in packet processing
+    // and a destination IP address
+    private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID;
 
-        // Encryption Algorithm
-        private IpSecAlgorithm mEncryption;
+    // Encryption Algorithm
+    private IpSecAlgorithm mEncryption;
 
-        // Authentication Algorithm
-        private IpSecAlgorithm mAuthentication;
+    // Authentication Algorithm
+    private IpSecAlgorithm mAuthentication;
 
-        // Authenticated Encryption Algorithm
-        private IpSecAlgorithm mAuthenticatedEncryption;
-
-        @Override
-        public String toString() {
-            return new StringBuilder()
-                    .append("{mSpiResourceId=")
-                    .append(mSpiResourceId)
-                    .append(", mEncryption=")
-                    .append(mEncryption)
-                    .append(", mAuthentication=")
-                    .append(mAuthentication)
-                    .append(", mAuthenticatedEncryption=")
-                    .append(mAuthenticatedEncryption)
-                    .append("}")
-                    .toString();
-        }
-
-        static boolean equals(IpSecConfig.Flow lhs, IpSecConfig.Flow rhs) {
-            if (lhs == null || rhs == null) return (lhs == rhs);
-            return (lhs.mSpiResourceId == rhs.mSpiResourceId
-                    && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption)
-                    && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication));
-        }
-    }
-
-    private final Flow[] mFlow = new Flow[] {new Flow(), new Flow()};
+    // Authenticated Encryption Algorithm
+    private IpSecAlgorithm mAuthenticatedEncryption;
 
     // For tunnel mode IPv4 UDP Encapsulation
     // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
@@ -100,36 +70,37 @@
         mMode = mode;
     }
 
-    /** Set the local IP address for Tunnel mode */
-    public void setLocalAddress(String localAddress) {
-        mLocalAddress = localAddress;
+    /** Set the source IP addres for this IPsec transform */
+    public void setSourceAddress(String sourceAddress) {
+        mSourceAddress = sourceAddress;
     }
 
-    /** Set the remote IP address for this IPsec transform */
-    public void setRemoteAddress(String remoteAddress) {
-        mRemoteAddress = remoteAddress;
+    /** Set the destination IP address for this IPsec transform */
+    public void setDestinationAddress(String destinationAddress) {
+        mDestinationAddress = destinationAddress;
     }
 
-    /** Set the SPI for a given direction by resource ID */
-    public void setSpiResourceId(int direction, int resourceId) {
-        mFlow[direction].mSpiResourceId = resourceId;
+    /** Set the SPI by resource ID */
+    public void setSpiResourceId(int resourceId) {
+        mSpiResourceId = resourceId;
     }
 
-    /** Set the encryption algorithm for a given direction */
-    public void setEncryption(int direction, IpSecAlgorithm encryption) {
-        mFlow[direction].mEncryption = encryption;
+    /** Set the encryption algorithm */
+    public void setEncryption(IpSecAlgorithm encryption) {
+        mEncryption = encryption;
     }
 
-    /** Set the authentication algorithm for a given direction */
-    public void setAuthentication(int direction, IpSecAlgorithm authentication) {
-        mFlow[direction].mAuthentication = authentication;
+    /** Set the authentication algorithm */
+    public void setAuthentication(IpSecAlgorithm authentication) {
+        mAuthentication = authentication;
     }
 
-    /** Set the authenticated encryption algorithm for a given direction */
-    public void setAuthenticatedEncryption(int direction, IpSecAlgorithm authenticatedEncryption) {
-        mFlow[direction].mAuthenticatedEncryption = authenticatedEncryption;
+    /** Set the authenticated encryption algorithm */
+    public void setAuthenticatedEncryption(IpSecAlgorithm authenticatedEncryption) {
+        mAuthenticatedEncryption = authenticatedEncryption;
     }
 
+    /** Set the underlying network that will carry traffic for this transform */
     public void setNetwork(Network network) {
         mNetwork = network;
     }
@@ -155,28 +126,28 @@
         return mMode;
     }
 
-    public String getLocalAddress() {
-        return mLocalAddress;
+    public String getSourceAddress() {
+        return mSourceAddress;
     }
 
-    public int getSpiResourceId(int direction) {
-        return mFlow[direction].mSpiResourceId;
+    public int getSpiResourceId() {
+        return mSpiResourceId;
     }
 
-    public String getRemoteAddress() {
-        return mRemoteAddress;
+    public String getDestinationAddress() {
+        return mDestinationAddress;
     }
 
-    public IpSecAlgorithm getEncryption(int direction) {
-        return mFlow[direction].mEncryption;
+    public IpSecAlgorithm getEncryption() {
+        return mEncryption;
     }
 
-    public IpSecAlgorithm getAuthentication(int direction) {
-        return mFlow[direction].mAuthentication;
+    public IpSecAlgorithm getAuthentication() {
+        return mAuthentication;
     }
 
-    public IpSecAlgorithm getAuthenticatedEncryption(int direction) {
-        return mFlow[direction].mAuthenticatedEncryption;
+    public IpSecAlgorithm getAuthenticatedEncryption() {
+        return mAuthenticatedEncryption;
     }
 
     public Network getNetwork() {
@@ -209,17 +180,13 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mMode);
-        out.writeString(mLocalAddress);
-        out.writeString(mRemoteAddress);
+        out.writeString(mSourceAddress);
+        out.writeString(mDestinationAddress);
         out.writeParcelable(mNetwork, flags);
-        out.writeInt(mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId);
-        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mEncryption, flags);
-        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthentication, flags);
-        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption, flags);
-        out.writeInt(mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId);
-        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mEncryption, flags);
-        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication, flags);
-        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption, flags);
+        out.writeInt(mSpiResourceId);
+        out.writeParcelable(mEncryption, flags);
+        out.writeParcelable(mAuthentication, flags);
+        out.writeParcelable(mAuthenticatedEncryption, flags);
         out.writeInt(mEncapType);
         out.writeInt(mEncapSocketResourceId);
         out.writeInt(mEncapRemotePort);
@@ -231,22 +198,15 @@
 
     private IpSecConfig(Parcel in) {
         mMode = in.readInt();
-        mLocalAddress = in.readString();
-        mRemoteAddress = in.readString();
+        mSourceAddress = in.readString();
+        mDestinationAddress = in.readString();
         mNetwork = (Network) in.readParcelable(Network.class.getClassLoader());
-        mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId = in.readInt();
-        mFlow[IpSecTransform.DIRECTION_IN].mEncryption =
+        mSpiResourceId = in.readInt();
+        mEncryption =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        mFlow[IpSecTransform.DIRECTION_IN].mAuthentication =
+        mAuthentication =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId = in.readInt();
-        mFlow[IpSecTransform.DIRECTION_OUT].mEncryption =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption =
+        mAuthenticatedEncryption =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         mEncapType = in.readInt();
         mEncapSocketResourceId = in.readInt();
@@ -260,10 +220,10 @@
         strBuilder
                 .append("{mMode=")
                 .append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
-                .append(", mLocalAddress=")
-                .append(mLocalAddress)
-                .append(", mRemoteAddress=")
-                .append(mRemoteAddress)
+                .append(", mSourceAddress=")
+                .append(mSourceAddress)
+                .append(", mDestinationAddress=")
+                .append(mDestinationAddress)
                 .append(", mNetwork=")
                 .append(mNetwork)
                 .append(", mEncapType=")
@@ -274,10 +234,14 @@
                 .append(mEncapRemotePort)
                 .append(", mNattKeepaliveInterval=")
                 .append(mNattKeepaliveInterval)
-                .append(", mFlow[OUT]=")
-                .append(mFlow[IpSecTransform.DIRECTION_OUT])
-                .append(", mFlow[IN]=")
-                .append(mFlow[IpSecTransform.DIRECTION_IN])
+                .append("{mSpiResourceId=")
+                .append(mSpiResourceId)
+                .append(", mEncryption=")
+                .append(mEncryption)
+                .append(", mAuthentication=")
+                .append(mAuthentication)
+                .append(", mAuthenticatedEncryption=")
+                .append(mAuthenticatedEncryption)
                 .append("}");
 
         return strBuilder.toString();
@@ -299,17 +263,18 @@
     public static boolean equals(IpSecConfig lhs, IpSecConfig rhs) {
         if (lhs == null || rhs == null) return (lhs == rhs);
         return (lhs.mMode == rhs.mMode
-                && lhs.mLocalAddress.equals(rhs.mLocalAddress)
-                && lhs.mRemoteAddress.equals(rhs.mRemoteAddress)
+                && lhs.mSourceAddress.equals(rhs.mSourceAddress)
+                && lhs.mDestinationAddress.equals(rhs.mDestinationAddress)
                 && ((lhs.mNetwork != null && lhs.mNetwork.equals(rhs.mNetwork))
                         || (lhs.mNetwork == rhs.mNetwork))
                 && lhs.mEncapType == rhs.mEncapType
                 && lhs.mEncapSocketResourceId == rhs.mEncapSocketResourceId
                 && lhs.mEncapRemotePort == rhs.mEncapRemotePort
                 && lhs.mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
-                && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_OUT],
-                        rhs.mFlow[IpSecTransform.DIRECTION_OUT])
-                && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_IN],
-                        rhs.mFlow[IpSecTransform.DIRECTION_IN]));
+                && lhs.mSpiResourceId == rhs.mSpiResourceId
+                && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption)
+                && IpSecAlgorithm.equals(
+                        lhs.mAuthenticatedEncryption, rhs.mAuthenticatedEncryption)
+                && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication));
     }
 }
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 34cfa9b..2202df3 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -17,6 +17,7 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -33,6 +34,8 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.DatagramSocket;
 import java.net.InetAddress;
 import java.net.Socket;
@@ -53,6 +56,23 @@
     private static final String TAG = "IpSecManager";
 
     /**
+     * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
+     * applies to traffic towards the host.
+     */
+    public static final int DIRECTION_IN = 0;
+
+    /**
+     * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
+     * applies to traffic from the host.
+     */
+    public static final int DIRECTION_OUT = 1;
+
+    /** @hide */
+    @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PolicyDirection {}
+
+    /**
      * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
      *
      * <p>No IPsec packet may contain an SPI of 0.
@@ -125,7 +145,7 @@
      */
     public static final class SecurityParameterIndex implements AutoCloseable {
         private final IIpSecService mService;
-        private final InetAddress mRemoteAddress;
+        private final InetAddress mDestinationAddress;
         private final CloseGuard mCloseGuard = CloseGuard.get();
         private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
         private int mResourceId = INVALID_RESOURCE_ID;
@@ -164,14 +184,14 @@
         }
 
         private SecurityParameterIndex(
-                @NonNull IIpSecService service, int direction, InetAddress remoteAddress, int spi)
+                @NonNull IIpSecService service, InetAddress destinationAddress, int spi)
                 throws ResourceUnavailableException, SpiUnavailableException {
             mService = service;
-            mRemoteAddress = remoteAddress;
+            mDestinationAddress = destinationAddress;
             try {
                 IpSecSpiResponse result =
                         mService.allocateSecurityParameterIndex(
-                                direction, remoteAddress.getHostAddress(), spi, new Binder());
+                                destinationAddress.getHostAddress(), spi, new Binder());
 
                 if (result == null) {
                     throw new NullPointerException("Received null response from IpSecService");
@@ -216,25 +236,23 @@
     }
 
     /**
-     * Reserve a random SPI for traffic bound to or from the specified remote address.
+     * Reserve a random SPI for traffic bound to or from the specified destination address.
      *
      * <p>If successful, this SPI is guaranteed available until released by a call to {@link
      * SecurityParameterIndex#close()}.
      *
-     * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
-     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress
+     * @param destinationAddress the destination address for traffic bearing the requested SPI.
+     *     For inbound traffic, the destination should be an address currently assigned on-device.
      * @return the reserved SecurityParameterIndex
-     * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
-     *     for this user
-     * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved
+     * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are
+     *     currently allocated for this user
      */
-    public SecurityParameterIndex allocateSecurityParameterIndex(
-            int direction, InetAddress remoteAddress) throws ResourceUnavailableException {
+    public SecurityParameterIndex allocateSecurityParameterIndex(InetAddress destinationAddress)
+            throws ResourceUnavailableException {
         try {
             return new SecurityParameterIndex(
                     mService,
-                    direction,
-                    remoteAddress,
+                    destinationAddress,
                     IpSecManager.INVALID_SECURITY_PARAMETER_INDEX);
         } catch (SpiUnavailableException unlikely) {
             throw new ResourceUnavailableException("No SPIs available");
@@ -242,26 +260,27 @@
     }
 
     /**
-     * Reserve the requested SPI for traffic bound to or from the specified remote address.
+     * Reserve the requested SPI for traffic bound to or from the specified destination address.
      *
      * <p>If successful, this SPI is guaranteed available until released by a call to {@link
      * SecurityParameterIndex#close()}.
      *
-     * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
-     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress
+     * @param destinationAddress the destination address for traffic bearing the requested SPI.
+     *     For inbound traffic, the destination should be an address currently assigned on-device.
      * @param requestedSpi the requested SPI, or '0' to allocate a random SPI
      * @return the reserved SecurityParameterIndex
-     * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
-     *     for this user
-     * @throws SpiUnavailableException indicating that the requested SPI could not be reserved
+     * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are
+     *     currently allocated for this user
+     * @throws {@link #SpiUnavailableException} indicating that the requested SPI could not be
+     *     reserved
      */
     public SecurityParameterIndex allocateSecurityParameterIndex(
-            int direction, InetAddress remoteAddress, int requestedSpi)
+            InetAddress destinationAddress, int requestedSpi)
             throws SpiUnavailableException, ResourceUnavailableException {
         if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) {
             throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI");
         }
-        return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi);
+        return new SecurityParameterIndex(mService, destinationAddress, requestedSpi);
     }
 
     /**
@@ -269,14 +288,14 @@
      *
      * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
      * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
-     * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+     * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
      * unprotected traffic can resume on that socket.
      *
      * <p>For security reasons, the destination address of any traffic on the socket must match the
      * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
      * other IP address will result in an IOException. In addition, reads and writes on the socket
      * will throw IOException if the user deactivates the transform (by calling {@link
-     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
      *
      * <h4>Rekey Procedure</h4>
      *
@@ -287,15 +306,14 @@
      * in-flight packets have been received.
      *
      * @param socket a stream socket
+     * @param direction the policy direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
      * @param transform a transport mode {@code IpSecTransform}
      * @throws IOException indicating that the transform could not be applied
-     * @hide
      */
-    public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
+    public void applyTransportModeTransform(
+            Socket socket, int direction, IpSecTransform transform)
             throws IOException {
-        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) {
-            applyTransportModeTransform(pfd, transform);
-        }
+        applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform);
     }
 
     /**
@@ -303,14 +321,14 @@
      *
      * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
      * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
-     * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+     * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
      * unprotected traffic can resume on that socket.
      *
      * <p>For security reasons, the destination address of any traffic on the socket must match the
      * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
      * other IP address will result in an IOException. In addition, reads and writes on the socket
      * will throw IOException if the user deactivates the transform (by calling {@link
-     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
      *
      * <h4>Rekey Procedure</h4>
      *
@@ -321,15 +339,13 @@
      * in-flight packets have been received.
      *
      * @param socket a datagram socket
+     * @param direction the policy direction either DIRECTION_IN or DIRECTION_OUT
      * @param transform a transport mode {@code IpSecTransform}
      * @throws IOException indicating that the transform could not be applied
-     * @hide
      */
-    public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
-            throws IOException {
-        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) {
-            applyTransportModeTransform(pfd, transform);
-        }
+    public void applyTransportModeTransform(
+            DatagramSocket socket, int direction, IpSecTransform transform) throws IOException {
+        applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform);
     }
 
     /**
@@ -337,14 +353,14 @@
      *
      * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
      * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
-     * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+     * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
      * unprotected traffic can resume on that socket.
      *
      * <p>For security reasons, the destination address of any traffic on the socket must match the
      * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
      * other IP address will result in an IOException. In addition, reads and writes on the socket
      * will throw IOException if the user deactivates the transform (by calling {@link
-     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
      *
      * <h4>Rekey Procedure</h4>
      *
@@ -355,24 +371,17 @@
      * in-flight packets have been received.
      *
      * @param socket a socket file descriptor
+     * @param direction the policy direction either DIRECTION_IN or DIRECTION_OUT
      * @param transform a transport mode {@code IpSecTransform}
      * @throws IOException indicating that the transform could not be applied
      */
-    public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
+    public void applyTransportModeTransform(
+            FileDescriptor socket, int direction, IpSecTransform transform)
             throws IOException {
         // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor()
-        // constructor takes control and closes the user's FD when we exit the method
-        // This is behaviorally the same as the other versions, but the PFD constructor does not
-        // dup() automatically, whereas PFD.fromSocket() and PDF.fromDatagramSocket() do dup().
+        // constructor takes control and closes the user's FD when we exit the method.
         try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
-            applyTransportModeTransform(pfd, transform);
-        }
-    }
-
-    /* Call down to activate a transform */
-    private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
-        try {
-            mService.applyTransportModeTransform(pfd, transform.getResourceId());
+            mService.applyTransportModeTransform(pfd, direction, transform.getResourceId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -407,13 +416,10 @@
      * @param socket a socket that previously had a transform applied to it
      * @param transform the IPsec Transform that was previously applied to the given socket
      * @throws IOException indicating that the transform could not be removed from the socket
-     * @hide
      */
-    public void removeTransportModeTransform(Socket socket, IpSecTransform transform)
+    public void removeTransportModeTransforms(Socket socket, IpSecTransform transform)
             throws IOException {
-        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) {
-            removeTransportModeTransform(pfd, transform);
-        }
+        removeTransportModeTransforms(socket.getFileDescriptor$(), transform);
     }
 
     /**
@@ -430,13 +436,10 @@
      * @param socket a socket that previously had a transform applied to it
      * @param transform the IPsec Transform that was previously applied to the given socket
      * @throws IOException indicating that the transform could not be removed from the socket
-     * @hide
      */
-    public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
+    public void removeTransportModeTransforms(DatagramSocket socket, IpSecTransform transform)
             throws IOException {
-        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) {
-            removeTransportModeTransform(pfd, transform);
-        }
+        removeTransportModeTransforms(socket.getFileDescriptor$(), transform);
     }
 
     /**
@@ -454,17 +457,10 @@
      * @param transform the IPsec Transform that was previously applied to the given socket
      * @throws IOException indicating that the transform could not be removed from the socket
      */
-    public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
+    public void removeTransportModeTransforms(FileDescriptor socket, IpSecTransform transform)
             throws IOException {
         try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
-            removeTransportModeTransform(pfd, transform);
-        }
-    }
-
-    /* Call down to remove a transform */
-    private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
-        try {
-            mService.removeTransportModeTransform(pfd, transform.getResourceId());
+            mService.removeTransportModeTransforms(pfd, transform.getResourceId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 102ba6d..7b9b483 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -38,13 +38,11 @@
 import java.net.InetAddress;
 
 /**
- * This class represents an IPsec transform, which comprises security associations in one or both
- * directions.
+ * This class represents a transform, which roughly corresponds to an IPsec Security Association.
  *
  * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform}
- * object encapsulates the properties and state of an inbound and outbound IPsec security
- * association. That includes, but is not limited to, algorithm choice, key material, and allocated
- * system resources.
+ * object encapsulates the properties and state of an IPsec security association. That includes,
+ * but is not limited to, algorithm choice, key material, and allocated system resources.
  *
  * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
  *     Internet Protocol</a>
@@ -52,23 +50,6 @@
 public final class IpSecTransform implements AutoCloseable {
     private static final String TAG = "IpSecTransform";
 
-    /**
-     * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
-     * applies to traffic towards the host.
-     */
-    public static final int DIRECTION_IN = 0;
-
-    /**
-     * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
-     * applies to traffic from the host.
-     */
-    public static final int DIRECTION_OUT = 1;
-
-    /** @hide */
-    @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface TransformDirection {}
-
     /** @hide */
     public static final int MODE_TRANSPORT = 0;
 
@@ -170,7 +151,7 @@
      *
      * <p>Deactivating a transform while it is still applied to a socket will result in errors on
      * that socket. Make sure to remove transforms by calling {@link
-     * IpSecManager#removeTransportModeTransform}. Note, removing an {@code IpSecTransform} from a
+     * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a
      * socket will not deactivate it (because one transform may be applied to multiple sockets).
      *
      * <p>It is safe to call this method on a transform that has already been deactivated.
@@ -272,85 +253,49 @@
         private IpSecConfig mConfig;
 
         /**
-         * Set the encryption algorithm for the given direction.
-         *
-         * <p>If encryption is set for a direction without also providing an SPI for that direction,
-         * creation of an {@code IpSecTransform} will fail when attempting to build the transform.
+         * Set the encryption algorithm.
          *
          * <p>Encryption is mutually exclusive with authenticated encryption.
          *
-         * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
          */
-        public IpSecTransform.Builder setEncryption(
-                @TransformDirection int direction, IpSecAlgorithm algo) {
+        public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) {
             // TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
-            mConfig.setEncryption(direction, algo);
+            Preconditions.checkNotNull(algo);
+            mConfig.setEncryption(algo);
             return this;
         }
 
         /**
-         * Set the authentication (integrity) algorithm for the given direction.
-         *
-         * <p>If authentication is set for a direction without also providing an SPI for that
-         * direction, creation of an {@code IpSecTransform} will fail when attempting to build the
-         * transform.
+         * Set the authentication (integrity) algorithm.
          *
          * <p>Authentication is mutually exclusive with authenticated encryption.
          *
-         * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
          */
-        public IpSecTransform.Builder setAuthentication(
-                @TransformDirection int direction, IpSecAlgorithm algo) {
+        public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) {
             // TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
-            mConfig.setAuthentication(direction, algo);
+            Preconditions.checkNotNull(algo);
+            mConfig.setAuthentication(algo);
             return this;
         }
 
         /**
-         * Set the authenticated encryption algorithm for the given direction.
+         * Set the authenticated encryption algorithm.
          *
-         * <p>If an authenticated encryption algorithm is set for a given direction without also
-         * providing an SPI for that direction, creation of an {@code IpSecTransform} will fail when
-         * attempting to build the transform.
-         *
-         * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated
-         * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as
-         * referred to in <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
+         * <p>The Authenticated Encryption (AE) class of algorithms are also known as
+         * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode
+         * algorithms (as referred to in
+         * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
          *
          * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
          *
-         * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
          *     be applied.
          */
-        public IpSecTransform.Builder setAuthenticatedEncryption(
-                @TransformDirection int direction, IpSecAlgorithm algo) {
-            mConfig.setAuthenticatedEncryption(direction, algo);
-            return this;
-        }
-
-        /**
-         * Set the SPI for the given direction.
-         *
-         * <p>Because IPsec operates at the IP layer, this 32-bit identifier uniquely identifies
-         * packets to a given destination address. To prevent SPI collisions, values should be
-         * reserved by calling {@link IpSecManager#allocateSecurityParameterIndex}.
-         *
-         * <p>If the SPI and algorithms are omitted for one direction, traffic in that direction
-         * will not be encrypted or authenticated.
-         *
-         * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
-         * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
-         *     traffic
-         */
-        public IpSecTransform.Builder setSpi(
-                @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
-            if (spi.getResourceId() == INVALID_RESOURCE_ID) {
-                throw new IllegalArgumentException("Invalid SecurityParameterIndex");
-            }
-            mConfig.setSpiResourceId(direction, spi.getResourceId());
+        public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) {
+            Preconditions.checkNotNull(algo);
+            mConfig.setAuthenticatedEncryption(algo);
             return this;
         }
 
@@ -363,7 +308,8 @@
          * @hide
          */
         @SystemApi
-        public IpSecTransform.Builder setUnderlyingNetwork(Network net) {
+        public IpSecTransform.Builder setUnderlyingNetwork(@NonNull Network net) {
+            Preconditions.checkNotNull(net);
             mConfig.setNetwork(net);
             return this;
         }
@@ -382,7 +328,8 @@
          *     encapsulated traffic. In the case of IKEv2, this should be port 4500.
          */
         public IpSecTransform.Builder setIpv4Encapsulation(
-                IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
+                @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
+            Preconditions.checkNotNull(localSocket);
             mConfig.setEncapType(ENCAP_ESPINUDP);
             if (localSocket.getResourceId() == INVALID_RESOURCE_ID) {
                 throw new IllegalArgumentException("Invalid UdpEncapsulationSocket");
@@ -419,24 +366,33 @@
          * will not affect any network traffic until it has been applied to one or more sockets.
          *
          * @see IpSecManager#applyTransportModeTransform
-         * @param remoteAddress the remote {@code InetAddress} of traffic on sockets that will use
-         *     this transform
+         * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use
+         *     this transform; this address must belong to the Network used by all sockets that
+         *     utilize this transform; if provided, then only traffic originating from the
+         *     specified source address will be processed.
+         * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
+         *     traffic
          * @throws IllegalArgumentException indicating that a particular combination of transform
          *     properties is invalid
-         * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms are
-         *     active
+         * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
+         *     are active
          * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
          *     collides with an existing transform
          * @throws IOException indicating other errors
          */
-        public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
+        public IpSecTransform buildTransportModeTransform(
+                @NonNull InetAddress sourceAddress,
+                @NonNull IpSecManager.SecurityParameterIndex spi)
                 throws IpSecManager.ResourceUnavailableException,
                         IpSecManager.SpiUnavailableException, IOException {
-            if (remoteAddress == null) {
-                throw new IllegalArgumentException("Remote address may not be null or empty!");
+            Preconditions.checkNotNull(sourceAddress);
+            Preconditions.checkNotNull(spi);
+            if (spi.getResourceId() == INVALID_RESOURCE_ID) {
+                throw new IllegalArgumentException("Invalid SecurityParameterIndex");
             }
             mConfig.setMode(MODE_TRANSPORT);
-            mConfig.setRemoteAddress(remoteAddress.getHostAddress());
+            mConfig.setSourceAddress(sourceAddress.getHostAddress());
+            mConfig.setSpiResourceId(spi.getResourceId());
             // FIXME: modifying a builder after calling build can change the built transform.
             return new IpSecTransform(mContext, mConfig).activate();
         }
@@ -445,26 +401,33 @@
          * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some
          * parameters have interdependencies that are checked at build time.
          *
-         * @param localAddress the {@link InetAddress} that provides the local endpoint for this
+         * @param sourceAddress the {@link InetAddress} that provides the source address for this
          *     IPsec tunnel. This is almost certainly an address belonging to the {@link Network}
          *     that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}.
-         * @param remoteAddress the {@link InetAddress} representing the remote endpoint of this
-         *     IPsec tunnel.
+         * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
+         *     traffic
          * @throws IllegalArgumentException indicating that a particular combination of transform
          *     properties is invalid.
+         * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
+         *     are active
+         * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
+         *     collides with an existing transform
+         * @throws IOException indicating other errors
          * @hide
          */
         public IpSecTransform buildTunnelModeTransform(
-                InetAddress localAddress, InetAddress remoteAddress) {
-            if (localAddress == null) {
-                throw new IllegalArgumentException("Local address may not be null or empty!");
+                @NonNull InetAddress sourceAddress,
+                @NonNull IpSecManager.SecurityParameterIndex spi)
+                throws IpSecManager.ResourceUnavailableException,
+                        IpSecManager.SpiUnavailableException, IOException {
+            Preconditions.checkNotNull(sourceAddress);
+            Preconditions.checkNotNull(spi);
+            if (spi.getResourceId() == INVALID_RESOURCE_ID) {
+                throw new IllegalArgumentException("Invalid SecurityParameterIndex");
             }
-            if (remoteAddress == null) {
-                throw new IllegalArgumentException("Remote address may not be null or empty!");
-            }
-            mConfig.setLocalAddress(localAddress.getHostAddress());
-            mConfig.setRemoteAddress(remoteAddress.getHostAddress());
             mConfig.setMode(MODE_TUNNEL);
+            mConfig.setSourceAddress(sourceAddress.getHostAddress());
+            mConfig.setSpiResourceId(spi.getResourceId());
             return new IpSecTransform(mContext, mConfig);
         }
 
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index d6992aa..287bdc8 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -60,7 +61,7 @@
     })
     public @interface MacAddressType { }
 
-    /** Indicates a MAC address of unknown type. */
+    /** @hide Indicates a MAC address of unknown type. */
     public static final int TYPE_UNKNOWN = 0;
     /** Indicates a MAC address is a unicast address. */
     public static final int TYPE_UNICAST = 1;
@@ -92,7 +93,7 @@
      *
      * @return the int constant representing the MAC address type of this MacAddress.
      */
-    public @MacAddressType int addressType() {
+    public @MacAddressType int getAddressType() {
         if (equals(BROADCAST_ADDRESS)) {
             return TYPE_BROADCAST;
         }
@@ -120,12 +121,12 @@
     /**
      * @return a byte array representation of this MacAddress.
      */
-    public byte[] toByteArray() {
+    public @NonNull byte[] toByteArray() {
         return byteAddrFromLongAddr(mAddr);
     }
 
     @Override
-    public String toString() {
+    public @NonNull String toString() {
         return stringAddrFromLongAddr(mAddr);
     }
 
@@ -133,7 +134,7 @@
      * @return a String representation of the OUI part of this MacAddress made of 3 hexadecimal
      * numbers in [0,ff] joined by ':' characters.
      */
-    public String toOuiString() {
+    public @NonNull String toOuiString() {
         return String.format(
                 "%02x:%02x:%02x", (mAddr >> 40) & 0xff, (mAddr >> 32) & 0xff, (mAddr >> 24) & 0xff);
     }
@@ -197,7 +198,7 @@
         if (!isMacAddress(addr)) {
             return TYPE_UNKNOWN;
         }
-        return MacAddress.fromBytes(addr).addressType();
+        return MacAddress.fromBytes(addr).getAddressType();
     }
 
     /**
@@ -211,7 +212,7 @@
      *
      * @hide
      */
-    public static byte[] byteAddrFromStringAddr(String addr) {
+    public static @NonNull byte[] byteAddrFromStringAddr(String addr) {
         Preconditions.checkNotNull(addr);
         String[] parts = addr.split(":");
         if (parts.length != ETHER_ADDR_LEN) {
@@ -239,7 +240,7 @@
      *
      * @hide
      */
-    public static String stringAddrFromByteAddr(byte[] addr) {
+    public static @NonNull String stringAddrFromByteAddr(byte[] addr) {
         if (!isMacAddress(addr)) {
             return null;
         }
@@ -291,7 +292,7 @@
 
     // Internal conversion function equivalent to stringAddrFromByteAddr(byteAddrFromLongAddr(addr))
     // that avoids the allocation of an intermediary byte[].
-    private static String stringAddrFromLongAddr(long addr) {
+    private static @NonNull String stringAddrFromLongAddr(long addr) {
         return String.format("%02x:%02x:%02x:%02x:%02x:%02x",
                 (addr >> 40) & 0xff,
                 (addr >> 32) & 0xff,
@@ -310,7 +311,7 @@
      * @return the MacAddress corresponding to the given String representation.
      * @throws IllegalArgumentException if the given String is not a valid representation.
      */
-    public static MacAddress fromString(String addr) {
+    public static @NonNull MacAddress fromString(@NonNull String addr) {
         return new MacAddress(longAddrFromStringAddr(addr));
     }
 
@@ -322,7 +323,7 @@
      * @return the MacAddress corresponding to the given byte array representation.
      * @throws IllegalArgumentException if the given byte array is not a valid representation.
      */
-    public static MacAddress fromBytes(byte[] addr) {
+    public static @NonNull MacAddress fromBytes(@NonNull byte[] addr) {
         return new MacAddress(longAddrFromByteAddr(addr));
     }
 
@@ -336,7 +337,7 @@
      *
      * @hide
      */
-    public static MacAddress createRandomUnicastAddress() {
+    public static @NonNull MacAddress createRandomUnicastAddress() {
         return createRandomUnicastAddress(BASE_GOOGLE_MAC, new Random());
     }
 
@@ -352,7 +353,7 @@
      *
      * @hide
      */
-    public static MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
+    public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
         long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
         addr = addr | LOCALLY_ASSIGNED_MASK;
         addr = addr & ~MULTICAST_MASK;
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 1a3ce91..5df168d 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -357,13 +357,13 @@
         // Multiple Provisioning Domains API recommendations, as made by the
         // IETF mif working group.
         //
-        // The HANDLE_MAGIC value MUST be kept in sync with the corresponding
+        // The handleMagic value MUST be kept in sync with the corresponding
         // value in the native/android/net.c NDK implementation.
         if (netId == 0) {
             return 0L;  // make this zero condition obvious for debugging
         }
-        final long HANDLE_MAGIC = 0xfacade;
-        return (((long) netId) << 32) | HANDLE_MAGIC;
+        final long handleMagic = 0xcafed00dL;
+        return (((long) netId) << 32) | handleMagic;
     }
 
     // implement the Parcelable interface
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 81c49a3..9ef26a9 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -29,7 +29,6 @@
 import android.net.wifi.WifiInfo;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.telephony.SubscriptionPlan;
 import android.util.DebugUtils;
 import android.util.Pair;
 
@@ -329,7 +328,7 @@
      * to access network when the device is idle or in battery saver mode. Otherwise, false.
      */
     public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) {
-        return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+        return procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
     }
 
     /**
@@ -337,7 +336,7 @@
      * to access network when the device is in data saver mode. Otherwise, false.
      */
     public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) {
-        return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+        return procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
     }
 
     public static String resolveNetworkId(WifiConfiguration config) {
diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java
index 7277ba3..bb36536 100644
--- a/core/java/android/net/metrics/WakeupStats.java
+++ b/core/java/android/net/metrics/WakeupStats.java
@@ -80,7 +80,7 @@
                 break;
         }
 
-        switch (ev.dstHwAddr.addressType()) {
+        switch (ev.dstHwAddr.getAddressType()) {
             case MacAddress.TYPE_UNICAST:
                 l2UnicastCount++;
                 break;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 2197484..7b0c153 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -238,6 +238,20 @@
     public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
 
     /**
+     * Specifies if a user is disallowed from changing screen off timeout.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>This user restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_SCREEN_TIMEOUT = "no_config_screen_timeout";
+
+    /**
      * Specifies if a user is disallowed from enabling the
      * "Unknown Sources" setting, that allows installation of apps from unknown sources.
      * The default value is <code>false</code>.
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 070b8c1..839a8bf 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -394,4 +394,32 @@
         parcel.writeString(mFsUuid);
         parcel.writeString(mState);
     }
+
+    /** {@hide} */
+    public static final class ScopedAccessProviderContract {
+
+        private ScopedAccessProviderContract() {
+            throw new UnsupportedOperationException("contains constants only");
+        }
+
+        public static final String AUTHORITY = "com.android.documentsui.scopedAccess";
+
+        public static final String TABLE_PACKAGES = "packages";
+        public static final String TABLE_PERMISSIONS = "permissions";
+
+        public static final String COL_PACKAGE = "package_name";
+        public static final String COL_VOLUME_UUID = "volume_uuid";
+        public static final String COL_DIRECTORY = "directory";
+        public static final String COL_GRANTED = "granted";
+
+        public static final String[] TABLE_PACKAGES_COLUMNS = new String[] { COL_PACKAGE };
+        public static final String[] TABLE_PERMISSIONS_COLUMNS =
+                new String[] { COL_PACKAGE, COL_VOLUME_UUID, COL_DIRECTORY, COL_GRANTED };
+
+        public static final int TABLE_PACKAGES_COL_PACKAGE = 0;
+        public static final int TABLE_PERMISSIONS_COL_PACKAGE = 0;
+        public static final int TABLE_PERMISSIONS_COL_VOLUME_UUID = 1;
+        public static final int TABLE_PERMISSIONS_COL_DIRECTORY = 2;
+        public static final int TABLE_PERMISSIONS_COL_GRANTED = 3;
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 850aedd..6d91f59 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6830,7 +6830,7 @@
          * @hide
          */
         public static final int SHOW_ROTATION_SUGGESTIONS_DEFAULT =
-                SHOW_ROTATION_SUGGESTIONS_DISABLED;
+                SHOW_ROTATION_SUGGESTIONS_ENABLED;
 
         /**
          * Read only list of the service components that the current user has explicitly allowed to
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index bf4b6ac..aa97b2a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1917,10 +1917,10 @@
 
     private static float measurePara(TextPaint paint, CharSequence text, int start, int end,
             TextDirectionHeuristic textDir) {
-        MeasuredText mt = null;
+        MeasuredParagraph mt = null;
         TextLine tl = TextLine.obtain();
         try {
-            mt = MeasuredText.buildForBidi(text, start, end, textDir, mt);
+            mt = MeasuredParagraph.buildForBidi(text, start, end, textDir, mt);
             final char[] chars = mt.getChars();
             final int len = chars.length;
             final Directions directions = mt.getDirections(0, len);
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
new file mode 100644
index 0000000..c93e036
--- /dev/null
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -0,0 +1,677 @@
+/*
+ * Copyright (C) 2010 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.text;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Paint;
+import android.text.AutoGrowArray.ByteArray;
+import android.text.AutoGrowArray.FloatArray;
+import android.text.AutoGrowArray.IntArray;
+import android.text.Layout.Directions;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+import android.util.Pools.SynchronizedPool;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.util.Arrays;
+
+/**
+ * MeasuredParagraph provides text information for rendering purpose.
+ *
+ * The first motivation of this class is identify the text directions and retrieving individual
+ * character widths. However retrieving character widths is slower than identifying text directions.
+ * Thus, this class provides several builder methods for specific purposes.
+ *
+ * - buildForBidi:
+ *   Compute only text directions.
+ * - buildForMeasurement:
+ *   Compute text direction and all character widths.
+ * - buildForStaticLayout:
+ *   This is bit special. StaticLayout also needs to know text direction and character widths for
+ *   line breaking, but all things are done in native code. Similarly, text measurement is done
+ *   in native code. So instead of storing result to Java array, this keeps the result in native
+ *   code since there is no good reason to move the results to Java layer.
+ *
+ * In addition to the character widths, some additional information is computed for each purposes,
+ * e.g. whole text length for measurement or font metrics for static layout.
+ *
+ * MeasuredParagraph is NOT a thread safe object.
+ * @hide
+ */
+public class MeasuredParagraph {
+    private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
+
+    private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+            MeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024);
+
+    private MeasuredParagraph() {}  // Use build static functions instead.
+
+    private static final SynchronizedPool<MeasuredParagraph> sPool = new SynchronizedPool<>(1);
+
+    private static @NonNull MeasuredParagraph obtain() { // Use build static functions instead.
+        final MeasuredParagraph mt = sPool.acquire();
+        return mt != null ? mt : new MeasuredParagraph();
+    }
+
+    /**
+     * Recycle the MeasuredParagraph.
+     *
+     * Do not call any methods after you call this method.
+     */
+    public void recycle() {
+        release();
+        sPool.release(this);
+    }
+
+    // The casted original text.
+    //
+    // This may be null if the passed text is not a Spanned.
+    private @Nullable Spanned mSpanned;
+
+    // The start offset of the target range in the original text (mSpanned);
+    private @IntRange(from = 0) int mTextStart;
+
+    // The length of the target range in the original text.
+    private @IntRange(from = 0) int mTextLength;
+
+    // The copied character buffer for measuring text.
+    //
+    // The length of this array is mTextLength.
+    private @Nullable char[] mCopiedBuffer;
+
+    // The whole paragraph direction.
+    private @Layout.Direction int mParaDir;
+
+    // True if the text is LTR direction and doesn't contain any bidi characters.
+    private boolean mLtrWithoutBidi;
+
+    // The bidi level for individual characters.
+    //
+    // This is empty if mLtrWithoutBidi is true.
+    private @NonNull ByteArray mLevels = new ByteArray();
+
+    // The whole width of the text.
+    // See getWholeWidth comments.
+    private @FloatRange(from = 0.0f) float mWholeWidth;
+
+    // Individual characters' widths.
+    // See getWidths comments.
+    private @Nullable FloatArray mWidths = new FloatArray();
+
+    // The span end positions.
+    // See getSpanEndCache comments.
+    private @Nullable IntArray mSpanEndCache = new IntArray(4);
+
+    // The font metrics.
+    // See getFontMetrics comments.
+    private @Nullable IntArray mFontMetrics = new IntArray(4 * 4);
+
+    // The native MeasuredParagraph.
+    // See getNativePtr comments.
+    // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead.
+    private /* Maybe Zero */ long mNativePtr = 0;
+    private @Nullable Runnable mNativeObjectCleaner;
+
+    // Associate the native object to this Java object.
+    private void bindNativeObject(/* Non Zero*/ long nativePtr) {
+        mNativePtr = nativePtr;
+        mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr);
+    }
+
+    // Decouple the native object from this Java object and release the native object.
+    private void unbindNativeObject() {
+        if (mNativePtr != 0) {
+            mNativeObjectCleaner.run();
+            mNativePtr = 0;
+        }
+    }
+
+    // Following two objects are for avoiding object allocation.
+    private @NonNull TextPaint mCachedPaint = new TextPaint();
+    private @Nullable Paint.FontMetricsInt mCachedFm;
+
+    /**
+     * Releases internal buffers.
+     */
+    public void release() {
+        reset();
+        mLevels.clearWithReleasingLargeArray();
+        mWidths.clearWithReleasingLargeArray();
+        mFontMetrics.clearWithReleasingLargeArray();
+        mSpanEndCache.clearWithReleasingLargeArray();
+    }
+
+    /**
+     * Resets the internal state for starting new text.
+     */
+    private void reset() {
+        mSpanned = null;
+        mCopiedBuffer = null;
+        mWholeWidth = 0;
+        mLevels.clear();
+        mWidths.clear();
+        mFontMetrics.clear();
+        mSpanEndCache.clear();
+        unbindNativeObject();
+    }
+
+    /**
+     * Returns the characters to be measured.
+     *
+     * This is always available.
+     */
+    public @NonNull char[] getChars() {
+        return mCopiedBuffer;
+    }
+
+    /**
+     * Returns the paragraph direction.
+     *
+     * This is always available.
+     */
+    public @Layout.Direction int getParagraphDir() {
+        return mParaDir;
+    }
+
+    /**
+     * Returns the directions.
+     *
+     * This is always available.
+     */
+    public Directions getDirections(@IntRange(from = 0) int start,  // inclusive
+                                    @IntRange(from = 0) int end) {  // exclusive
+        if (mLtrWithoutBidi) {
+            return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+        }
+
+        final int length = end - start;
+        return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start,
+                length);
+    }
+
+    /**
+     * Returns the whole text width.
+     *
+     * This is available only if the MeasureText is computed with computeForMeasurement.
+     * Returns 0 in other cases.
+     */
+    public @FloatRange(from = 0.0f) float getWholeWidth() {
+        return mWholeWidth;
+    }
+
+    /**
+     * Returns the individual character's width.
+     *
+     * This is available only if the MeasureText is computed with computeForMeasurement.
+     * Returns empty array in other cases.
+     */
+    public @NonNull FloatArray getWidths() {
+        return mWidths;
+    }
+
+    /**
+     * Returns the MetricsAffectingSpan end indices.
+     *
+     * If the input text is not a spanned string, this has one value that is the length of the text.
+     *
+     * This is available only if the MeasureText is computed with computeForStaticLayout.
+     * Returns empty array in other cases.
+     */
+    public @NonNull IntArray getSpanEndCache() {
+        return mSpanEndCache;
+    }
+
+    /**
+     * Returns the int array which holds FontMetrics.
+     *
+     * This array holds the repeat of top, bottom, ascent, descent of font metrics value.
+     *
+     * This is available only if the MeasureText is computed with computeForStaticLayout.
+     * Returns empty array in other cases.
+     */
+    public @NonNull IntArray getFontMetrics() {
+        return mFontMetrics;
+    }
+
+    /**
+     * Returns the native ptr of the MeasuredParagraph.
+     *
+     * This is available only if the MeasureText is computed with computeForStaticLayout.
+     * Returns 0 in other cases.
+     */
+    public /* Maybe Zero */ long getNativePtr() {
+        return mNativePtr;
+    }
+
+    /**
+     * Generates new MeasuredParagraph for Bidi computation.
+     *
+     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+     * result to recycle and returns recycle.
+     *
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     * @param recycle pass existing MeasuredParagraph if you want to recycle it.
+     *
+     * @return measured text
+     */
+    public static @NonNull MeasuredParagraph buildForBidi(@NonNull CharSequence text,
+                                                     @IntRange(from = 0) int start,
+                                                     @IntRange(from = 0) int end,
+                                                     @NonNull TextDirectionHeuristic textDir,
+                                                     @Nullable MeasuredParagraph recycle) {
+        final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
+        mt.resetAndAnalyzeBidi(text, start, end, textDir);
+        return mt;
+    }
+
+    /**
+     * Generates new MeasuredParagraph for measuring texts.
+     *
+     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+     * result to recycle and returns recycle.
+     *
+     * @param paint the paint to be used for rendering the text.
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     * @param recycle pass existing MeasuredParagraph if you want to recycle it.
+     *
+     * @return measured text
+     */
+    public static @NonNull MeasuredParagraph buildForMeasurement(@NonNull TextPaint paint,
+                                                            @NonNull CharSequence text,
+                                                            @IntRange(from = 0) int start,
+                                                            @IntRange(from = 0) int end,
+                                                            @NonNull TextDirectionHeuristic textDir,
+                                                            @Nullable MeasuredParagraph recycle) {
+        final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
+        mt.resetAndAnalyzeBidi(text, start, end, textDir);
+
+        mt.mWidths.resize(mt.mTextLength);
+        if (mt.mTextLength == 0) {
+            return mt;
+        }
+
+        if (mt.mSpanned == null) {
+            // No style change by MetricsAffectingSpan. Just measure all text.
+            mt.applyMetricsAffectingSpan(
+                    paint, null /* spans */, start, end, 0 /* native static layout ptr */);
+        } else {
+            // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
+            int spanEnd;
+            for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
+                spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
+                MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
+                        MetricAffectingSpan.class);
+                spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
+                mt.applyMetricsAffectingSpan(
+                        paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */);
+            }
+        }
+        return mt;
+    }
+
+    /**
+     * Generates new MeasuredParagraph for StaticLayout.
+     *
+     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+     * result to recycle and returns recycle.
+     *
+     * @param paint the paint to be used for rendering the text.
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     * @param recycle pass existing MeasuredParagraph if you want to recycle it.
+     *
+     * @return measured text
+     */
+    public static @NonNull MeasuredParagraph buildForStaticLayout(
+            @NonNull TextPaint paint,
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int start,
+            @IntRange(from = 0) int end,
+            @NonNull TextDirectionHeuristic textDir,
+            @Nullable MeasuredParagraph recycle) {
+        final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
+        mt.resetAndAnalyzeBidi(text, start, end, textDir);
+        if (mt.mTextLength == 0) {
+            // Need to build empty native measured text for StaticLayout.
+            // TODO: Stop creating empty measured text for empty lines.
+            long nativeBuilderPtr = nInitBuilder();
+            try {
+                mt.bindNativeObject(
+                        nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
+            } finally {
+                nFreeBuilder(nativeBuilderPtr);
+            }
+            return mt;
+        }
+
+        long nativeBuilderPtr = nInitBuilder();
+        try {
+            if (mt.mSpanned == null) {
+                // No style change by MetricsAffectingSpan. Just measure all text.
+                mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr);
+                mt.mSpanEndCache.append(end);
+            } else {
+                // There may be a MetricsAffectingSpan. Split into span transitions and apply
+                // styles.
+                int spanEnd;
+                for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
+                    spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
+                                                             MetricAffectingSpan.class);
+                    MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
+                            MetricAffectingSpan.class);
+                    spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
+                                                       MetricAffectingSpan.class);
+                    mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
+                                                 nativeBuilderPtr);
+                    mt.mSpanEndCache.append(spanEnd);
+                }
+            }
+            mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
+        } finally {
+            nFreeBuilder(nativeBuilderPtr);
+        }
+
+        return mt;
+    }
+
+    /**
+     * Reset internal state and analyzes text for bidirectional runs.
+     *
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     */
+    private void resetAndAnalyzeBidi(@NonNull CharSequence text,
+                                     @IntRange(from = 0) int start,  // inclusive
+                                     @IntRange(from = 0) int end,  // exclusive
+                                     @NonNull TextDirectionHeuristic textDir) {
+        reset();
+        mSpanned = text instanceof Spanned ? (Spanned) text : null;
+        mTextStart = start;
+        mTextLength = end - start;
+
+        if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) {
+            mCopiedBuffer = new char[mTextLength];
+        }
+        TextUtils.getChars(text, start, end, mCopiedBuffer, 0);
+
+        // Replace characters associated with ReplacementSpan to U+FFFC.
+        if (mSpanned != null) {
+            ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class);
+
+            for (int i = 0; i < spans.length; i++) {
+                int startInPara = mSpanned.getSpanStart(spans[i]) - start;
+                int endInPara = mSpanned.getSpanEnd(spans[i]) - start;
+                // The span interval may be larger and must be restricted to [start, end)
+                if (startInPara < 0) startInPara = 0;
+                if (endInPara > mTextLength) endInPara = mTextLength;
+                Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER);
+            }
+        }
+
+        if ((textDir == TextDirectionHeuristics.LTR
+                || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
+                || textDir == TextDirectionHeuristics.ANYRTL_LTR)
+                && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
+            mLevels.clear();
+            mParaDir = Layout.DIR_LEFT_TO_RIGHT;
+            mLtrWithoutBidi = true;
+        } else {
+            final int bidiRequest;
+            if (textDir == TextDirectionHeuristics.LTR) {
+                bidiRequest = Layout.DIR_REQUEST_LTR;
+            } else if (textDir == TextDirectionHeuristics.RTL) {
+                bidiRequest = Layout.DIR_REQUEST_RTL;
+            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
+                bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
+            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
+                bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
+            } else {
+                final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
+                bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
+            }
+            mLevels.resize(mTextLength);
+            mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray());
+            mLtrWithoutBidi = false;
+        }
+    }
+
+    private void applyReplacementRun(@NonNull ReplacementSpan replacement,
+                                     @IntRange(from = 0) int start,  // inclusive, in copied buffer
+                                     @IntRange(from = 0) int end,  // exclusive, in copied buffer
+                                     /* Maybe Zero */ long nativeBuilderPtr) {
+        // Use original text. Shouldn't matter.
+        // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for
+        //       backward compatibility? or Should we initialize them for getFontMetricsInt?
+        final float width = replacement.getSize(
+                mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm);
+        if (nativeBuilderPtr == 0) {
+            // Assigns all width to the first character. This is the same behavior as minikin.
+            mWidths.set(start, width);
+            if (end > start + 1) {
+                Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f);
+            }
+            mWholeWidth += width;
+        } else {
+            nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
+                               width);
+        }
+    }
+
+    private void applyStyleRun(@IntRange(from = 0) int start,  // inclusive, in copied buffer
+                               @IntRange(from = 0) int end,  // exclusive, in copied buffer
+                               /* Maybe Zero */ long nativeBuilderPtr) {
+        if (nativeBuilderPtr != 0) {
+            mCachedPaint.getFontMetricsInt(mCachedFm);
+        }
+
+        if (mLtrWithoutBidi) {
+            // If the whole text is LTR direction, just apply whole region.
+            if (nativeBuilderPtr == 0) {
+                mWholeWidth += mCachedPaint.getTextRunAdvances(
+                        mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
+                        mWidths.getRawArray(), start);
+            } else {
+                nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
+                        false /* isRtl */);
+            }
+        } else {
+            // If there is multiple bidi levels, split into individual bidi level and apply style.
+            byte level = mLevels.get(start);
+            // Note that the empty text or empty range won't reach this method.
+            // Safe to search from start + 1.
+            for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) {
+                if (levelEnd == end || mLevels.get(levelEnd) != level) {  // transition point
+                    final boolean isRtl = (level & 0x1) != 0;
+                    if (nativeBuilderPtr == 0) {
+                        final int levelLength = levelEnd - levelStart;
+                        mWholeWidth += mCachedPaint.getTextRunAdvances(
+                                mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
+                                isRtl, mWidths.getRawArray(), levelStart);
+                    } else {
+                        nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart,
+                                levelEnd, isRtl);
+                    }
+                    if (levelEnd == end) {
+                        break;
+                    }
+                    levelStart = levelEnd;
+                    level = mLevels.get(levelEnd);
+                }
+            }
+        }
+    }
+
+    private void applyMetricsAffectingSpan(
+            @NonNull TextPaint paint,
+            @Nullable MetricAffectingSpan[] spans,
+            @IntRange(from = 0) int start,  // inclusive, in original text buffer
+            @IntRange(from = 0) int end,  // exclusive, in original text buffer
+            /* Maybe Zero */ long nativeBuilderPtr) {
+        mCachedPaint.set(paint);
+        // XXX paint should not have a baseline shift, but...
+        mCachedPaint.baselineShift = 0;
+
+        final boolean needFontMetrics = nativeBuilderPtr != 0;
+
+        if (needFontMetrics && mCachedFm == null) {
+            mCachedFm = new Paint.FontMetricsInt();
+        }
+
+        ReplacementSpan replacement = null;
+        if (spans != null) {
+            for (int i = 0; i < spans.length; i++) {
+                MetricAffectingSpan span = spans[i];
+                if (span instanceof ReplacementSpan) {
+                    // The last ReplacementSpan is effective for backward compatibility reasons.
+                    replacement = (ReplacementSpan) span;
+                } else {
+                    // TODO: No need to call updateMeasureState for ReplacementSpan as well?
+                    span.updateMeasureState(mCachedPaint);
+                }
+            }
+        }
+
+        final int startInCopiedBuffer = start - mTextStart;
+        final int endInCopiedBuffer = end - mTextStart;
+
+        if (replacement != null) {
+            applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer,
+                                nativeBuilderPtr);
+        } else {
+            applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr);
+        }
+
+        if (needFontMetrics) {
+            if (mCachedPaint.baselineShift < 0) {
+                mCachedFm.ascent += mCachedPaint.baselineShift;
+                mCachedFm.top += mCachedPaint.baselineShift;
+            } else {
+                mCachedFm.descent += mCachedPaint.baselineShift;
+                mCachedFm.bottom += mCachedPaint.baselineShift;
+            }
+
+            mFontMetrics.append(mCachedFm.top);
+            mFontMetrics.append(mCachedFm.bottom);
+            mFontMetrics.append(mCachedFm.ascent);
+            mFontMetrics.append(mCachedFm.descent);
+        }
+    }
+
+    /**
+     * Returns the maximum index that the accumulated width not exceeds the width.
+     *
+     * If forward=false is passed, returns the minimum index from the end instead.
+     *
+     * This only works if the MeasuredParagraph is computed with computeForMeasurement.
+     * Undefined behavior in other case.
+     */
+    @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) {
+        float[] w = mWidths.getRawArray();
+        if (forwards) {
+            int i = 0;
+            while (i < limit) {
+                width -= w[i];
+                if (width < 0.0f) break;
+                i++;
+            }
+            while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--;
+            return i;
+        } else {
+            int i = limit - 1;
+            while (i >= 0) {
+                width -= w[i];
+                if (width < 0.0f) break;
+                i--;
+            }
+            while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) {
+                i++;
+            }
+            return limit - i - 1;
+        }
+    }
+
+    /**
+     * Returns the length of the substring.
+     *
+     * This only works if the MeasuredParagraph is computed with computeForMeasurement.
+     * Undefined behavior in other case.
+     */
+    @FloatRange(from = 0.0f) float measure(int start, int limit) {
+        float width = 0;
+        float[] w = mWidths.getRawArray();
+        for (int i = start; i < limit; ++i) {
+            width += w[i];
+        }
+        return width;
+    }
+
+    private static native /* Non Zero */ long nInitBuilder();
+
+    /**
+     * Apply style to make native measured text.
+     *
+     * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
+     * @param paintPtr The native paint pointer to be applied.
+     * @param start The start offset in the copied buffer.
+     * @param end The end offset in the copied buffer.
+     * @param isRtl True if the text is RTL.
+     */
+    private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
+                                            /* Non Zero */ long paintPtr,
+                                            @IntRange(from = 0) int start,
+                                            @IntRange(from = 0) int end,
+                                            boolean isRtl);
+
+    /**
+     * Apply ReplacementRun to make native measured text.
+     *
+     * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
+     * @param paintPtr The native paint pointer to be applied.
+     * @param start The start offset in the copied buffer.
+     * @param end The end offset in the copied buffer.
+     * @param width The width of the replacement.
+     */
+    private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr,
+                                                  /* Non Zero */ long paintPtr,
+                                                  @IntRange(from = 0) int start,
+                                                  @IntRange(from = 0) int end,
+                                                  @FloatRange(from = 0) float width);
+
+    private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr,
+                                                 @NonNull char[] text);
+
+    private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
+
+    @CriticalNative
+    private static native /* Non Zero */ long nGetReleaseFunc();
+}
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 14d6f9e..2c30360 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -16,661 +16,255 @@
 
 package android.text;
 
-import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.Paint;
-import android.text.AutoGrowArray.ByteArray;
-import android.text.AutoGrowArray.FloatArray;
-import android.text.AutoGrowArray.IntArray;
-import android.text.Layout.Directions;
-import android.text.style.MetricAffectingSpan;
-import android.text.style.ReplacementSpan;
-import android.util.Pools.SynchronizedPool;
+import android.util.IntArray;
 
-import dalvik.annotation.optimization.CriticalNative;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
 
-import libcore.util.NativeAllocationRegistry;
-
-import java.util.Arrays;
+import java.util.ArrayList;
 
 /**
- * MeasuredText provides text information for rendering purpose.
- *
- * The first motivation of this class is identify the text directions and retrieving individual
- * character widths. However retrieving character widths is slower than identifying text directions.
- * Thus, this class provides several builder methods for specific purposes.
- *
- * - buildForBidi:
- *   Compute only text directions.
- * - buildForMeasurement:
- *   Compute text direction and all character widths.
- * - buildForStaticLayout:
- *   This is bit special. StaticLayout also needs to know text direction and character widths for
- *   line breaking, but all things are done in native code. Similarly, text measurement is done
- *   in native code. So instead of storing result to Java array, this keeps the result in native
- *   code since there is no good reason to move the results to Java layer.
- *
- * In addition to the character widths, some additional information is computed for each purposes,
- * e.g. whole text length for measurement or font metrics for static layout.
- *
- * MeasuredText is NOT a thread safe object.
- * @hide
+ * A text which has already been measured.
  */
-public class MeasuredText {
-    private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
+public class MeasuredText implements Spanned {
+    private static final char LINE_FEED = '\n';
 
-    private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
-            MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024);
+    // The original text.
+    private final @NonNull CharSequence mText;
 
-    private MeasuredText() {}  // Use build static functions instead.
+    // The inclusive start offset of the measuring target.
+    private final @IntRange(from = 0) int mStart;
 
-    private static final SynchronizedPool<MeasuredText> sPool = new SynchronizedPool<>(1);
+    // The exclusive end offset of the measuring target.
+    private final @IntRange(from = 0) int mEnd;
 
-    private static @NonNull MeasuredText obtain() { // Use build static functions instead.
-        final MeasuredText mt = sPool.acquire();
-        return mt != null ? mt : new MeasuredText();
+    // The TextPaint used for measurement.
+    private final @NonNull TextPaint mPaint;
+
+    // The requested text direction.
+    private final @NonNull TextDirectionHeuristic mTextDir;
+
+    // The measured paragraph texts.
+    private final @NonNull MeasuredParagraph[] mMeasuredParagraphs;
+
+    // The sorted paragraph end offsets.
+    private final @NonNull int[] mParagraphBreakPoints;
+
+    /**
+     * Build MeasuredText from the text.
+     *
+     * @param text The text to be measured.
+     * @param paint The paint to be used for drawing.
+     * @param textDir The text direction.
+     * @return The measured text.
+     */
+    public static @NonNull MeasuredText build(@NonNull CharSequence text,
+                                              @NonNull TextPaint paint,
+                                              @NonNull TextDirectionHeuristic textDir) {
+        return MeasuredText.build(text, paint, textDir, 0, text.length());
     }
 
     /**
-     * Recycle the MeasuredText.
+     * Build MeasuredText from the specific range of the text..
      *
-     * Do not call any methods after you call this method.
+     * @param text The text to be measured.
+     * @param paint The paint to be used for drawing.
+     * @param textDir The text direction.
+     * @param start The inclusive start offset of the text.
+     * @param end The exclusive start offset of the text.
+     * @return The measured text.
      */
-    public void recycle() {
-        release();
-        sPool.release(this);
+    public static @NonNull MeasuredText build(@NonNull CharSequence text,
+                                              @NonNull TextPaint paint,
+                                              @NonNull TextDirectionHeuristic textDir,
+                                              @IntRange(from = 0) int start,
+                                              @IntRange(from = 0) int end) {
+        Preconditions.checkNotNull(text);
+        Preconditions.checkNotNull(paint);
+        Preconditions.checkNotNull(textDir);
+        Preconditions.checkArgumentInRange(start, 0, text.length(), "start");
+        Preconditions.checkArgumentInRange(end, 0, text.length(), "end");
+
+        final IntArray paragraphEnds = new IntArray();
+        final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>();
+
+        int paraEnd = 0;
+        for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
+            paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
+            if (paraEnd < 0) {
+                // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end.
+                paraEnd = end;
+            } else {
+                paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
+            }
+
+            paragraphEnds.add(paraEnd);
+            measuredTexts.add(MeasuredParagraph.buildForStaticLayout(
+                    paint, text, paraStart, paraEnd, textDir, null /* no recycle */));
+        }
+
+        return new MeasuredText(text, start, end, paint, textDir,
+                                measuredTexts.toArray(new MeasuredParagraph[measuredTexts.size()]),
+                                paragraphEnds.toArray());
     }
 
-    // The casted original text.
+    // Use MeasuredText.build instead.
+    private MeasuredText(@NonNull CharSequence text,
+                         @IntRange(from = 0) int start,
+                         @IntRange(from = 0) int end,
+                         @NonNull TextPaint paint,
+                         @NonNull TextDirectionHeuristic textDir,
+                         @NonNull MeasuredParagraph[] measuredTexts,
+                         @NonNull int[] paragraphBreakPoints) {
+        mText = text;
+        mStart = start;
+        mEnd = end;
+        mPaint = paint;
+        mMeasuredParagraphs = measuredTexts;
+        mParagraphBreakPoints = paragraphBreakPoints;
+        mTextDir = textDir;
+    }
+
+    /**
+     * Return the underlying text.
+     */
+    public @NonNull CharSequence getText() {
+        return mText;
+    }
+
+    /**
+     * Returns the inclusive start offset of measured region.
+     */
+    public @IntRange(from = 0) int getStart() {
+        return mStart;
+    }
+
+    /**
+     * Returns the exclusive end offset of measured region.
+     */
+    public @IntRange(from = 0) int getEnd() {
+        return mEnd;
+    }
+
+    /**
+     * Returns the text direction associated with char sequence.
+     */
+    public @NonNull TextDirectionHeuristic getTextDir() {
+        return mTextDir;
+    }
+
+    /**
+     * Returns the paint used to measure this text.
+     */
+    public @NonNull TextPaint getPaint() {
+        return mPaint;
+    }
+
+    /**
+     * Returns the length of the paragraph of this text.
+     */
+    public @IntRange(from = 0) int getParagraphCount() {
+        return mParagraphBreakPoints.length;
+    }
+
+    /**
+     * Returns the paragraph start offset of the text.
+     */
+    public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
+        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
+        return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1];
+    }
+
+    /**
+     * Returns the paragraph end offset of the text.
+     */
+    public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
+        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
+        return mParagraphBreakPoints[paraIndex];
+    }
+
+    /** @hide */
+    public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) {
+        return mMeasuredParagraphs[paraIndex];
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Spanned overrides
     //
-    // This may be null if the passed text is not a Spanned.
-    private @Nullable Spanned mSpanned;
+    // Just proxy for underlying mText if appropriate.
 
-    // The start offset of the target range in the original text (mSpanned);
-    private @IntRange(from = 0) int mTextStart;
+    @Override
+    public <T> T[] getSpans(int start, int end, Class<T> type) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpans(start, end, type);
+        } else {
+            return ArrayUtils.emptyArray(type);
+        }
+    }
 
-    // The length of the target range in the original text.
-    private @IntRange(from = 0) int mTextLength;
+    @Override
+    public int getSpanStart(Object tag) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpanStart(tag);
+        } else {
+            return -1;
+        }
+    }
 
-    // The copied character buffer for measuring text.
+    @Override
+    public int getSpanEnd(Object tag) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpanEnd(tag);
+        } else {
+            return -1;
+        }
+    }
+
+    @Override
+    public int getSpanFlags(Object tag) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpanFlags(tag);
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public int nextSpanTransition(int start, int limit, Class type) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).nextSpanTransition(start, limit, type);
+        } else {
+            return mText.length();
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // CharSequence overrides.
     //
-    // The length of this array is mTextLength.
-    private @Nullable char[] mCopiedBuffer;
+    // Just proxy for underlying mText.
 
-    // The whole paragraph direction.
-    private @Layout.Direction int mParaDir;
-
-    // True if the text is LTR direction and doesn't contain any bidi characters.
-    private boolean mLtrWithoutBidi;
-
-    // The bidi level for individual characters.
-    //
-    // This is empty if mLtrWithoutBidi is true.
-    private @NonNull ByteArray mLevels = new ByteArray();
-
-    // The whole width of the text.
-    // See getWholeWidth comments.
-    private @FloatRange(from = 0.0f) float mWholeWidth;
-
-    // Individual characters' widths.
-    // See getWidths comments.
-    private @Nullable FloatArray mWidths = new FloatArray();
-
-    // The span end positions.
-    // See getSpanEndCache comments.
-    private @Nullable IntArray mSpanEndCache = new IntArray(4);
-
-    // The font metrics.
-    // See getFontMetrics comments.
-    private @Nullable IntArray mFontMetrics = new IntArray(4 * 4);
-
-    // The native MeasuredText.
-    // See getNativePtr comments.
-    // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead.
-    private /* Maybe Zero */ long mNativePtr = 0;
-    private @Nullable Runnable mNativeObjectCleaner;
-
-    // Associate the native object to this Java object.
-    private void bindNativeObject(/* Non Zero*/ long nativePtr) {
-        mNativePtr = nativePtr;
-        mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr);
+    @Override
+    public int length() {
+        return mText.length();
     }
 
-    // Decouple the native object from this Java object and release the native object.
-    private void unbindNativeObject() {
-        if (mNativePtr != 0) {
-            mNativeObjectCleaner.run();
-            mNativePtr = 0;
-        }
+    @Override
+    public char charAt(int index) {
+        // TODO: Should this be index + mStart ?
+        return mText.charAt(index);
     }
 
-    // Following two objects are for avoiding object allocation.
-    private @NonNull TextPaint mCachedPaint = new TextPaint();
-    private @Nullable Paint.FontMetricsInt mCachedFm;
-
-    /**
-     * Releases internal buffers.
-     */
-    public void release() {
-        reset();
-        mLevels.clearWithReleasingLargeArray();
-        mWidths.clearWithReleasingLargeArray();
-        mFontMetrics.clearWithReleasingLargeArray();
-        mSpanEndCache.clearWithReleasingLargeArray();
+    @Override
+    public CharSequence subSequence(int start, int end) {
+        // TODO: return MeasuredText.
+        // TODO: Should this be index + mStart, end + mStart ?
+        return mText.subSequence(start, end);
     }
 
-    /**
-     * Resets the internal state for starting new text.
-     */
-    private void reset() {
-        mSpanned = null;
-        mCopiedBuffer = null;
-        mWholeWidth = 0;
-        mLevels.clear();
-        mWidths.clear();
-        mFontMetrics.clear();
-        mSpanEndCache.clear();
-        unbindNativeObject();
+    @Override
+    public String toString() {
+        return mText.toString();
     }
-
-    /**
-     * Returns the characters to be measured.
-     *
-     * This is always available.
-     */
-    public @NonNull char[] getChars() {
-        return mCopiedBuffer;
-    }
-
-    /**
-     * Returns the paragraph direction.
-     *
-     * This is always available.
-     */
-    public @Layout.Direction int getParagraphDir() {
-        return mParaDir;
-    }
-
-    /**
-     * Returns the directions.
-     *
-     * This is always available.
-     */
-    public Directions getDirections(@IntRange(from = 0) int start,  // inclusive
-                                    @IntRange(from = 0) int end) {  // exclusive
-        if (mLtrWithoutBidi) {
-            return Layout.DIRS_ALL_LEFT_TO_RIGHT;
-        }
-
-        final int length = end - start;
-        return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start,
-                length);
-    }
-
-    /**
-     * Returns the whole text width.
-     *
-     * This is available only if the MeasureText is computed with computeForMeasurement.
-     * Returns 0 in other cases.
-     */
-    public @FloatRange(from = 0.0f) float getWholeWidth() {
-        return mWholeWidth;
-    }
-
-    /**
-     * Returns the individual character's width.
-     *
-     * This is available only if the MeasureText is computed with computeForMeasurement.
-     * Returns empty array in other cases.
-     */
-    public @NonNull FloatArray getWidths() {
-        return mWidths;
-    }
-
-    /**
-     * Returns the MetricsAffectingSpan end indices.
-     *
-     * If the input text is not a spanned string, this has one value that is the length of the text.
-     *
-     * This is available only if the MeasureText is computed with computeForStaticLayout.
-     * Returns empty array in other cases.
-     */
-    public @NonNull IntArray getSpanEndCache() {
-        return mSpanEndCache;
-    }
-
-    /**
-     * Returns the int array which holds FontMetrics.
-     *
-     * This array holds the repeat of top, bottom, ascent, descent of font metrics value.
-     *
-     * This is available only if the MeasureText is computed with computeForStaticLayout.
-     * Returns empty array in other cases.
-     */
-    public @NonNull IntArray getFontMetrics() {
-        return mFontMetrics;
-    }
-
-    /**
-     * Returns the native ptr of the MeasuredText.
-     *
-     * This is available only if the MeasureText is computed with computeForStaticLayout.
-     * Returns 0 in other cases.
-     */
-    public /* Maybe Zero */ long getNativePtr() {
-        return mNativePtr;
-    }
-
-    /**
-     * Generates new MeasuredText for Bidi computation.
-     *
-     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
-     * result to recycle and returns recycle.
-     *
-     * @param text the character sequence to be measured
-     * @param start the inclusive start offset of the target region in the text
-     * @param end the exclusive end offset of the target region in the text
-     * @param textDir the text direction
-     * @param recycle pass existing MeasuredText if you want to recycle it.
-     *
-     * @return measured text
-     */
-    public static @NonNull MeasuredText buildForBidi(@NonNull CharSequence text,
-                                                     @IntRange(from = 0) int start,
-                                                     @IntRange(from = 0) int end,
-                                                     @NonNull TextDirectionHeuristic textDir,
-                                                     @Nullable MeasuredText recycle) {
-        final MeasuredText mt = recycle == null ? obtain() : recycle;
-        mt.resetAndAnalyzeBidi(text, start, end, textDir);
-        return mt;
-    }
-
-    /**
-     * Generates new MeasuredText for measuring texts.
-     *
-     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
-     * result to recycle and returns recycle.
-     *
-     * @param paint the paint to be used for rendering the text.
-     * @param text the character sequence to be measured
-     * @param start the inclusive start offset of the target region in the text
-     * @param end the exclusive end offset of the target region in the text
-     * @param textDir the text direction
-     * @param recycle pass existing MeasuredText if you want to recycle it.
-     *
-     * @return measured text
-     */
-    public static @NonNull MeasuredText buildForMeasurement(@NonNull TextPaint paint,
-                                                            @NonNull CharSequence text,
-                                                            @IntRange(from = 0) int start,
-                                                            @IntRange(from = 0) int end,
-                                                            @NonNull TextDirectionHeuristic textDir,
-                                                            @Nullable MeasuredText recycle) {
-        final MeasuredText mt = recycle == null ? obtain() : recycle;
-        mt.resetAndAnalyzeBidi(text, start, end, textDir);
-
-        mt.mWidths.resize(mt.mTextLength);
-        if (mt.mTextLength == 0) {
-            return mt;
-        }
-
-        if (mt.mSpanned == null) {
-            // No style change by MetricsAffectingSpan. Just measure all text.
-            mt.applyMetricsAffectingSpan(
-                    paint, null /* spans */, start, end, 0 /* native static layout ptr */);
-        } else {
-            // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
-            int spanEnd;
-            for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
-                spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
-                MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
-                        MetricAffectingSpan.class);
-                spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
-                mt.applyMetricsAffectingSpan(
-                        paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */);
-            }
-        }
-        return mt;
-    }
-
-    /**
-     * Generates new MeasuredText for StaticLayout.
-     *
-     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
-     * result to recycle and returns recycle.
-     *
-     * @param paint the paint to be used for rendering the text.
-     * @param text the character sequence to be measured
-     * @param start the inclusive start offset of the target region in the text
-     * @param end the exclusive end offset of the target region in the text
-     * @param textDir the text direction
-     * @param recycle pass existing MeasuredText if you want to recycle it.
-     *
-     * @return measured text
-     */
-    public static @NonNull MeasuredText buildForStaticLayout(
-            @NonNull TextPaint paint,
-            @NonNull CharSequence text,
-            @IntRange(from = 0) int start,
-            @IntRange(from = 0) int end,
-            @NonNull TextDirectionHeuristic textDir,
-            @Nullable MeasuredText recycle) {
-        final MeasuredText mt = recycle == null ? obtain() : recycle;
-        mt.resetAndAnalyzeBidi(text, start, end, textDir);
-        if (mt.mTextLength == 0) {
-            // Need to build empty native measured text for StaticLayout.
-            // TODO: Stop creating empty measured text for empty lines.
-            long nativeBuilderPtr = nInitBuilder();
-            try {
-                mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer));
-            } finally {
-                nFreeBuilder(nativeBuilderPtr);
-            }
-            return mt;
-        }
-
-        long nativeBuilderPtr = nInitBuilder();
-        try {
-            if (mt.mSpanned == null) {
-                // No style change by MetricsAffectingSpan. Just measure all text.
-                mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr);
-                mt.mSpanEndCache.append(end);
-            } else {
-                // There may be a MetricsAffectingSpan. Split into span transitions and apply
-                // styles.
-                int spanEnd;
-                for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
-                    spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
-                                                             MetricAffectingSpan.class);
-                    MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
-                            MetricAffectingSpan.class);
-                    spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
-                                                       MetricAffectingSpan.class);
-                    mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
-                                                 nativeBuilderPtr);
-                    mt.mSpanEndCache.append(spanEnd);
-                }
-            }
-            mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer));
-        } finally {
-            nFreeBuilder(nativeBuilderPtr);
-        }
-
-        return mt;
-    }
-
-    /**
-     * Reset internal state and analyzes text for bidirectional runs.
-     *
-     * @param text the character sequence to be measured
-     * @param start the inclusive start offset of the target region in the text
-     * @param end the exclusive end offset of the target region in the text
-     * @param textDir the text direction
-     */
-    private void resetAndAnalyzeBidi(@NonNull CharSequence text,
-                                     @IntRange(from = 0) int start,  // inclusive
-                                     @IntRange(from = 0) int end,  // exclusive
-                                     @NonNull TextDirectionHeuristic textDir) {
-        reset();
-        mSpanned = text instanceof Spanned ? (Spanned) text : null;
-        mTextStart = start;
-        mTextLength = end - start;
-
-        if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) {
-            mCopiedBuffer = new char[mTextLength];
-        }
-        TextUtils.getChars(text, start, end, mCopiedBuffer, 0);
-
-        // Replace characters associated with ReplacementSpan to U+FFFC.
-        if (mSpanned != null) {
-            ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class);
-
-            for (int i = 0; i < spans.length; i++) {
-                int startInPara = mSpanned.getSpanStart(spans[i]) - start;
-                int endInPara = mSpanned.getSpanEnd(spans[i]) - start;
-                // The span interval may be larger and must be restricted to [start, end)
-                if (startInPara < 0) startInPara = 0;
-                if (endInPara > mTextLength) endInPara = mTextLength;
-                Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER);
-            }
-        }
-
-        if ((textDir == TextDirectionHeuristics.LTR ||
-                textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR ||
-                textDir == TextDirectionHeuristics.ANYRTL_LTR) &&
-                TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
-            mLevels.clear();
-            mParaDir = Layout.DIR_LEFT_TO_RIGHT;
-            mLtrWithoutBidi = true;
-        } else {
-            final int bidiRequest;
-            if (textDir == TextDirectionHeuristics.LTR) {
-                bidiRequest = Layout.DIR_REQUEST_LTR;
-            } else if (textDir == TextDirectionHeuristics.RTL) {
-                bidiRequest = Layout.DIR_REQUEST_RTL;
-            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
-                bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
-            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
-                bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
-            } else {
-                final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
-                bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
-            }
-            mLevels.resize(mTextLength);
-            mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray());
-            mLtrWithoutBidi = false;
-        }
-    }
-
-    private void applyReplacementRun(@NonNull ReplacementSpan replacement,
-                                     @IntRange(from = 0) int start,  // inclusive, in copied buffer
-                                     @IntRange(from = 0) int end,  // exclusive, in copied buffer
-                                     /* Maybe Zero */ long nativeBuilderPtr) {
-        // Use original text. Shouldn't matter.
-        // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for
-        //       backward compatibility? or Should we initialize them for getFontMetricsInt?
-        final float width = replacement.getSize(
-                mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm);
-        if (nativeBuilderPtr == 0) {
-            // Assigns all width to the first character. This is the same behavior as minikin.
-            mWidths.set(start, width);
-            if (end > start + 1) {
-                Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f);
-            }
-            mWholeWidth += width;
-        } else {
-            nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
-                               width);
-        }
-    }
-
-    private void applyStyleRun(@IntRange(from = 0) int start,  // inclusive, in copied buffer
-                               @IntRange(from = 0) int end,  // exclusive, in copied buffer
-                               /* Maybe Zero */ long nativeBuilderPtr) {
-        if (nativeBuilderPtr != 0) {
-            mCachedPaint.getFontMetricsInt(mCachedFm);
-        }
-
-        if (mLtrWithoutBidi) {
-            // If the whole text is LTR direction, just apply whole region.
-            if (nativeBuilderPtr == 0) {
-                mWholeWidth += mCachedPaint.getTextRunAdvances(
-                        mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
-                        mWidths.getRawArray(), start);
-            } else {
-                nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
-                        false /* isRtl */);
-            }
-        } else {
-            // If there is multiple bidi levels, split into individual bidi level and apply style.
-            byte level = mLevels.get(start);
-            // Note that the empty text or empty range won't reach this method.
-            // Safe to search from start + 1.
-            for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) {
-                if (levelEnd == end || mLevels.get(levelEnd) != level) {  // transition point
-                    final boolean isRtl = (level & 0x1) != 0;
-                    if (nativeBuilderPtr == 0) {
-                        final int levelLength = levelEnd - levelStart;
-                        mWholeWidth += mCachedPaint.getTextRunAdvances(
-                                mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
-                                isRtl, mWidths.getRawArray(), levelStart);
-                    } else {
-                        nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart,
-                                levelEnd, isRtl);
-                    }
-                    if (levelEnd == end) {
-                        break;
-                    }
-                    levelStart = levelEnd;
-                    level = mLevels.get(levelEnd);
-                }
-            }
-        }
-    }
-
-    private void applyMetricsAffectingSpan(
-            @NonNull TextPaint paint,
-            @Nullable MetricAffectingSpan[] spans,
-            @IntRange(from = 0) int start,  // inclusive, in original text buffer
-            @IntRange(from = 0) int end,  // exclusive, in original text buffer
-            /* Maybe Zero */ long nativeBuilderPtr) {
-        mCachedPaint.set(paint);
-        // XXX paint should not have a baseline shift, but...
-        mCachedPaint.baselineShift = 0;
-
-        final boolean needFontMetrics = nativeBuilderPtr != 0;
-
-        if (needFontMetrics && mCachedFm == null) {
-            mCachedFm = new Paint.FontMetricsInt();
-        }
-
-        ReplacementSpan replacement = null;
-        if (spans != null) {
-            for (int i = 0; i < spans.length; i++) {
-                MetricAffectingSpan span = spans[i];
-                if (span instanceof ReplacementSpan) {
-                    // The last ReplacementSpan is effective for backward compatibility reasons.
-                    replacement = (ReplacementSpan) span;
-                } else {
-                    // TODO: No need to call updateMeasureState for ReplacementSpan as well?
-                    span.updateMeasureState(mCachedPaint);
-                }
-            }
-        }
-
-        final int startInCopiedBuffer = start - mTextStart;
-        final int endInCopiedBuffer = end - mTextStart;
-
-        if (replacement != null) {
-            applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer,
-                                nativeBuilderPtr);
-        } else {
-            applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr);
-        }
-
-        if (needFontMetrics) {
-            if (mCachedPaint.baselineShift < 0) {
-                mCachedFm.ascent += mCachedPaint.baselineShift;
-                mCachedFm.top += mCachedPaint.baselineShift;
-            } else {
-                mCachedFm.descent += mCachedPaint.baselineShift;
-                mCachedFm.bottom += mCachedPaint.baselineShift;
-            }
-
-            mFontMetrics.append(mCachedFm.top);
-            mFontMetrics.append(mCachedFm.bottom);
-            mFontMetrics.append(mCachedFm.ascent);
-            mFontMetrics.append(mCachedFm.descent);
-        }
-    }
-
-    /**
-     * Returns the maximum index that the accumulated width not exceeds the width.
-     *
-     * If forward=false is passed, returns the minimum index from the end instead.
-     *
-     * This only works if the MeasuredText is computed with computeForMeasurement.
-     * Undefined behavior in other case.
-     */
-    @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) {
-        float[] w = mWidths.getRawArray();
-        if (forwards) {
-            int i = 0;
-            while (i < limit) {
-                width -= w[i];
-                if (width < 0.0f) break;
-                i++;
-            }
-            while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--;
-            return i;
-        } else {
-            int i = limit - 1;
-            while (i >= 0) {
-                width -= w[i];
-                if (width < 0.0f) break;
-                i--;
-            }
-            while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) {
-                i++;
-            }
-            return limit - i - 1;
-        }
-    }
-
-    /**
-     * Returns the length of the substring.
-     *
-     * This only works if the MeasuredText is computed with computeForMeasurement.
-     * Undefined behavior in other case.
-     */
-    @FloatRange(from = 0.0f) float measure(int start, int limit) {
-        float width = 0;
-        float[] w = mWidths.getRawArray();
-        for (int i = start; i < limit; ++i) {
-            width += w[i];
-        }
-        return width;
-    }
-
-    private static native /* Non Zero */ long nInitBuilder();
-
-    /**
-     * Apply style to make native measured text.
-     *
-     * @param nativeBuilderPtr The native MeasuredText builder pointer.
-     * @param paintPtr The native paint pointer to be applied.
-     * @param start The start offset in the copied buffer.
-     * @param end The end offset in the copied buffer.
-     * @param isRtl True if the text is RTL.
-     */
-    private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
-                                            /* Non Zero */ long paintPtr,
-                                            @IntRange(from = 0) int start,
-                                            @IntRange(from = 0) int end,
-                                            boolean isRtl);
-
-    /**
-     * Apply ReplacementRun to make native measured text.
-     *
-     * @param nativeBuilderPtr The native MeasuredText builder pointer.
-     * @param paintPtr The native paint pointer to be applied.
-     * @param start The start offset in the copied buffer.
-     * @param end The end offset in the copied buffer.
-     * @param width The width of the replacement.
-     */
-    private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr,
-                                                  /* Non Zero */ long paintPtr,
-                                                  @IntRange(from = 0) int start,
-                                                  @IntRange(from = 0) int end,
-                                                  @FloatRange(from = 0) float width);
-
-    private static native long nBuildNativeMeasuredText(/* Non Zero */ long nativeBuilderPtr,
-                                                 @NonNull char[] text);
-
-    private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
-
-    @CriticalNative
-    private static native /* Non Zero */ long nGetReleaseFunc();
 }
diff --git a/core/java/android/text/PremeasuredText.java b/core/java/android/text/PremeasuredText.java
deleted file mode 100644
index 465314d..0000000
--- a/core/java/android/text/PremeasuredText.java
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.util.IntArray;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-
-/**
- * A text which has already been measured.
- *
- * TODO: Rename to better name? e.g. MeasuredText, FrozenText etc.
- */
-public class PremeasuredText implements Spanned {
-    private static final char LINE_FEED = '\n';
-
-    // The original text.
-    private final @NonNull CharSequence mText;
-
-    // The inclusive start offset of the measuring target.
-    private final @IntRange(from = 0) int mStart;
-
-    // The exclusive end offset of the measuring target.
-    private final @IntRange(from = 0) int mEnd;
-
-    // The TextPaint used for measurement.
-    private final @NonNull TextPaint mPaint;
-
-    // The requested text direction.
-    private final @NonNull TextDirectionHeuristic mTextDir;
-
-    // The measured paragraph texts.
-    private final @NonNull MeasuredText[] mMeasuredTexts;
-
-    // The sorted paragraph end offsets.
-    private final @NonNull int[] mParagraphBreakPoints;
-
-    /**
-     * Build PremeasuredText from the text.
-     *
-     * @param text The text to be measured.
-     * @param paint The paint to be used for drawing.
-     * @param textDir The text direction.
-     * @return The measured text.
-     */
-    public static @NonNull PremeasuredText build(@NonNull CharSequence text,
-                                                 @NonNull TextPaint paint,
-                                                 @NonNull TextDirectionHeuristic textDir) {
-        return PremeasuredText.build(text, paint, textDir, 0, text.length());
-    }
-
-    /**
-     * Build PremeasuredText from the specific range of the text..
-     *
-     * @param text The text to be measured.
-     * @param paint The paint to be used for drawing.
-     * @param textDir The text direction.
-     * @param start The inclusive start offset of the text.
-     * @param end The exclusive start offset of the text.
-     * @return The measured text.
-     */
-    public static @NonNull PremeasuredText build(@NonNull CharSequence text,
-                                                 @NonNull TextPaint paint,
-                                                 @NonNull TextDirectionHeuristic textDir,
-                                                 @IntRange(from = 0) int start,
-                                                 @IntRange(from = 0) int end) {
-        Preconditions.checkNotNull(text);
-        Preconditions.checkNotNull(paint);
-        Preconditions.checkNotNull(textDir);
-        Preconditions.checkArgumentInRange(start, 0, text.length(), "start");
-        Preconditions.checkArgumentInRange(end, 0, text.length(), "end");
-
-        final IntArray paragraphEnds = new IntArray();
-        final ArrayList<MeasuredText> measuredTexts = new ArrayList<>();
-
-        int paraEnd = 0;
-        for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
-            paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
-            if (paraEnd < 0) {
-                // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end.
-                paraEnd = end;
-            } else {
-                paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
-            }
-
-            paragraphEnds.add(paraEnd);
-            measuredTexts.add(MeasuredText.buildForStaticLayout(
-                    paint, text, paraStart, paraEnd, textDir, null /* no recycle */));
-        }
-
-        return new PremeasuredText(text, start, end, paint, textDir,
-                                   measuredTexts.toArray(new MeasuredText[measuredTexts.size()]),
-                                   paragraphEnds.toArray());
-    }
-
-    // Use PremeasuredText.build instead.
-    private PremeasuredText(@NonNull CharSequence text,
-                            @IntRange(from = 0) int start,
-                            @IntRange(from = 0) int end,
-                            @NonNull TextPaint paint,
-                            @NonNull TextDirectionHeuristic textDir,
-                            @NonNull MeasuredText[] measuredTexts,
-                            @NonNull int[] paragraphBreakPoints) {
-        mText = text;
-        mStart = start;
-        mEnd = end;
-        mPaint = paint;
-        mMeasuredTexts = measuredTexts;
-        mParagraphBreakPoints = paragraphBreakPoints;
-        mTextDir = textDir;
-    }
-
-    /**
-     * Return the underlying text.
-     */
-    public @NonNull CharSequence getText() {
-        return mText;
-    }
-
-    /**
-     * Returns the inclusive start offset of measured region.
-     */
-    public @IntRange(from = 0) int getStart() {
-        return mStart;
-    }
-
-    /**
-     * Returns the exclusive end offset of measured region.
-     */
-    public @IntRange(from = 0) int getEnd() {
-        return mEnd;
-    }
-
-    /**
-     * Returns the text direction associated with char sequence.
-     */
-    public @NonNull TextDirectionHeuristic getTextDir() {
-        return mTextDir;
-    }
-
-    /**
-     * Returns the paint used to measure this text.
-     */
-    public @NonNull TextPaint getPaint() {
-        return mPaint;
-    }
-
-    /**
-     * Returns the length of the paragraph of this text.
-     */
-    public @IntRange(from = 0) int getParagraphCount() {
-        return mParagraphBreakPoints.length;
-    }
-
-    /**
-     * Returns the paragraph start offset of the text.
-     */
-    public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
-        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
-        return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1];
-    }
-
-    /**
-     * Returns the paragraph end offset of the text.
-     */
-    public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
-        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
-        return mParagraphBreakPoints[paraIndex];
-    }
-
-    /** @hide */
-    public @NonNull MeasuredText getMeasuredText(@IntRange(from = 0) int paraIndex) {
-        return mMeasuredTexts[paraIndex];
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    // Spanned overrides
-    //
-    // Just proxy for underlying mText if appropriate.
-
-    @Override
-    public <T> T[] getSpans(int start, int end, Class<T> type) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).getSpans(start, end, type);
-        } else {
-            return ArrayUtils.emptyArray(type);
-        }
-    }
-
-    @Override
-    public int getSpanStart(Object tag) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).getSpanStart(tag);
-        } else {
-            return -1;
-        }
-    }
-
-    @Override
-    public int getSpanEnd(Object tag) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).getSpanEnd(tag);
-        } else {
-            return -1;
-        }
-    }
-
-    @Override
-    public int getSpanFlags(Object tag) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).getSpanFlags(tag);
-        } else {
-            return 0;
-        }
-    }
-
-    @Override
-    public int nextSpanTransition(int start, int limit, Class type) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).nextSpanTransition(start, limit, type);
-        } else {
-            return mText.length();
-        }
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    // CharSequence overrides.
-    //
-    // Just proxy for underlying mText.
-
-    @Override
-    public int length() {
-        return mText.length();
-    }
-
-    @Override
-    public char charAt(int index) {
-        // TODO: Should this be index + mStart ?
-        return mText.charAt(index);
-    }
-
-    @Override
-    public CharSequence subSequence(int start, int end) {
-        // TODO: return PremeasuredText.
-        // TODO: Should this be index + mStart, end + mStart ?
-        return mText.subSequence(start, end);
-    }
-
-    @Override
-    public String toString() {
-        return mText.toString();
-    }
-}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index d69b119..36bec86 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -55,7 +55,8 @@
      * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
      * following:
      *
-     *   - Create MeasuredText by MeasuredText.buildForStaticLayout which measures in native.
+     *   - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
+     *     native.
      *   - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
      *
      * After all paragraphs, call finish() to release expensive buffers.
@@ -650,34 +651,34 @@
                 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
                 indents, mLeftPaddings, mRightPaddings);
 
-        PremeasuredText premeasured = null;
+        MeasuredText measured = null;
         final Spanned spanned;
-        if (source instanceof PremeasuredText) {
-            premeasured = (PremeasuredText) source;
+        if (source instanceof MeasuredText) {
+            measured = (MeasuredText) source;
 
-            final CharSequence original = premeasured.getText();
+            final CharSequence original = measured.getText();
             spanned = (original instanceof Spanned) ? (Spanned) original : null;
 
-            if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) {
+            if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) {
                 // The buffer position has changed. Re-measure here.
-                premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd);
+                measured = MeasuredText.build(original, paint, textDir, bufStart, bufEnd);
             } else {
-                // We can use premeasured information.
+                // We can use measured information.
 
-                // Overwrite with the one when premeasured.
+                // Overwrite with the one when emeasured.
                 // TODO: Give an option for developer not to overwrite and measure again here?
-                textDir = premeasured.getTextDir();
-                paint = premeasured.getPaint();
+                textDir = measured.getTextDir();
+                paint = measured.getPaint();
             }
         } else {
-            premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd);
+            measured = MeasuredText.build(source, paint, textDir, bufStart, bufEnd);
             spanned = (source instanceof Spanned) ? (Spanned) source : null;
         }
 
         try {
-            for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) {
-                final int paraStart = premeasured.getParagraphStart(paraIndex);
-                final int paraEnd = premeasured.getParagraphEnd(paraIndex);
+            for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) {
+                final int paraStart = measured.getParagraphStart(paraIndex);
+                final int paraEnd = measured.getParagraphEnd(paraIndex);
 
                 int firstWidthLineCount = 1;
                 int firstWidth = outerWidth;
@@ -743,10 +744,10 @@
                     }
                 }
 
-                final MeasuredText measured = premeasured.getMeasuredText(paraIndex);
-                final char[] chs = measured.getChars();
-                final int[] spanEndCache = measured.getSpanEndCache().getRawArray();
-                final int[] fmCache = measured.getFontMetrics().getRawArray();
+                final MeasuredParagraph measuredPara = measured.getMeasuredParagraph(paraIndex);
+                final char[] chs = measuredPara.getChars();
+                final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
+                final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
                 // TODO: Stop keeping duplicated width copy in native and Java.
                 widths.resize(chs.length);
 
@@ -759,7 +760,7 @@
 
                         // Inputs
                         chs,
-                        measured.getNativePtr(),
+                        measuredPara.getNativePtr(),
                         paraEnd - paraStart,
                         firstWidth,
                         firstWidthLineCount,
@@ -863,7 +864,7 @@
                         v = out(source, here, endPos,
                                 ascent, descent, fmTop, fmBottom,
                                 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
-                                flags[breakIndex], needMultiply, measured, bufEnd,
+                                flags[breakIndex], needMultiply, measuredPara, bufEnd,
                                 includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
                                 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
                                 paint, moreChars);
@@ -894,8 +895,8 @@
 
             if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
                     && mLineCount < mMaximumVisibleLineCount) {
-                final MeasuredText measured =
-                        MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null);
+                final MeasuredParagraph measuredPara =
+                        MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
                 paint.getFontMetricsInt(fm);
                 v = out(source,
                         bufEnd, bufEnd, fm.ascent, fm.descent,
@@ -903,7 +904,7 @@
                         v,
                         spacingmult, spacingadd, null,
                         null, fm, 0,
-                        needMultiply, measured, bufEnd,
+                        needMultiply, measuredPara, bufEnd,
                         includepad, trackpad, addLastLineSpacing, null,
                         null, bufStart, ellipsize,
                         ellipsizedWidth, 0, paint, false);
@@ -918,7 +919,7 @@
     private int out(final CharSequence text, final int start, final int end, int above, int below,
             int top, int bottom, int v, final float spacingmult, final float spacingadd,
             final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
-            final int flags, final boolean needMultiply, @NonNull final MeasuredText measured,
+            final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
             final int bufEnd, final boolean includePad, final boolean trackPad,
             final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
             final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 9c9fbf2..409e514 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1250,10 +1250,10 @@
             @NonNull String ellipsis) {
 
         final int len = text.length();
-        MeasuredText mt = null;
-        MeasuredText resultMt = null;
+        MeasuredParagraph mt = null;
+        MeasuredParagraph resultMt = null;
         try {
-            mt = MeasuredText.buildForMeasurement(paint, text, 0, text.length(), textDir, mt);
+            mt = MeasuredParagraph.buildForMeasurement(paint, text, 0, text.length(), textDir, mt);
             float width = mt.getWholeWidth();
 
             if (width <= avail) {
@@ -1332,7 +1332,7 @@
                 if (remaining == 0) { // All text is gone.
                     textFits = true;
                 } else {
-                    resultMt = MeasuredText.buildForMeasurement(
+                    resultMt = MeasuredParagraph.buildForMeasurement(
                             paint, result, 0, result.length(), textDir, resultMt);
                     width = resultMt.getWholeWidth();
                     if (width <= avail) {
@@ -1479,11 +1479,11 @@
     public static CharSequence commaEllipsize(CharSequence text, TextPaint p,
          float avail, String oneMore, String more, TextDirectionHeuristic textDir) {
 
-        MeasuredText mt = null;
-        MeasuredText tempMt = null;
+        MeasuredParagraph mt = null;
+        MeasuredParagraph tempMt = null;
         try {
             int len = text.length();
-            mt = MeasuredText.buildForMeasurement(p, text, 0, len, textDir, mt);
+            mt = MeasuredParagraph.buildForMeasurement(p, text, 0, len, textDir, mt);
             final float width = mt.getWholeWidth();
             if (width <= avail) {
                 return text;
@@ -1523,7 +1523,7 @@
                     }
 
                     // XXX this is probably ok, but need to look at it more
-                    tempMt = MeasuredText.buildForMeasurement(
+                    tempMt = MeasuredParagraph.buildForMeasurement(
                             p, format, 0, format.length(), textDir, tempMt);
                     float moreWid = tempMt.getWholeWidth();
 
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 62f9717..8242156 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -38,7 +38,6 @@
     static {
         DEFAULT_FLAGS = new HashMap<>();
         DEFAULT_FLAGS.put("device_info_v2", "true");
-        DEFAULT_FLAGS.put("settings_search_v2", "true");
         DEFAULT_FLAGS.put("settings_app_info_v2", "true");
         DEFAULT_FLAGS.put("settings_connected_device_v2", "true");
         DEFAULT_FLAGS.put("settings_battery_v2", "false");
diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java
index c25b272..e0d085c 100644
--- a/core/java/android/util/StatsManager.java
+++ b/core/java/android/util/StatsManager.java
@@ -42,6 +42,16 @@
     }
 
     /**
+     * Temporary to prevent build failures. Will be deleted.
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) {
+        // To prevent breakages of dependencies on old API.
+
+        return false;
+    }
+
+    /**
      * Clients can send a configuration and simultaneously registers the name of a broadcast
      * receiver that listens for when it should request data.
      *
@@ -70,6 +80,15 @@
     }
 
     /**
+     * Temporary to prevent build failures. Will be deleted.
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public boolean removeConfiguration(String configKey) {
+        // To prevent breakages of old dependencies.
+        return false;
+    }
+
+    /**
      * Remove a configuration from logging.
      *
      * @param configKey Configuration key to remove.
@@ -93,6 +112,16 @@
     }
 
     /**
+     * Temporary to prevent build failures. Will be deleted.
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public byte[] getData(String configKey) {
+        // TODO: remove this and all other methods with String-based config keys.
+        // To prevent build breakages of dependencies.
+        return null;
+    }
+
+    /**
      * Clients can request data with a binder call. This getter is destructive and also clears
      * the retrieved metrics from statsd memory.
      *
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index cc4a0b6..84ae20b 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -18,30 +18,18 @@
 
 import android.os.SystemClock;
 
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
 import libcore.util.TimeZoneFinder;
 import libcore.util.ZoneInfoDB;
 
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
 /**
  * A class containing utility methods related to time zones.
  */
 public class TimeUtils {
     /** @hide */ public TimeUtils() {}
-    private static final boolean DBG = false;
-    private static final String TAG = "TimeUtils";
-
-    /** Cached results of getTimeZonesWithUniqueOffsets */
-    private static final Object sLastUniqueLockObj = new Object();
-    private static List<String> sLastUniqueZoneOffsets = null;
-    private static String sLastUniqueCountry = null;
-
     /** {@hide} */
     private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
@@ -76,86 +64,6 @@
     }
 
     /**
-     * Returns an immutable list of unique time zone IDs for the country.
-     *
-     * @param country to find
-     * @return unmodifiable list of unique time zones, maybe empty but never null.
-     * @hide
-     */
-    public static List<String> getTimeZoneIdsWithUniqueOffsets(String country) {
-        synchronized(sLastUniqueLockObj) {
-            if ((country != null) && country.equals(sLastUniqueCountry)) {
-                if (DBG) {
-                    Log.d(TAG, "getTimeZonesWithUniqueOffsets(" +
-                            country + "): return cached version");
-                }
-                return sLastUniqueZoneOffsets;
-            }
-        }
-
-        Collection<android.icu.util.TimeZone> zones = getIcuTimeZones(country);
-        ArrayList<android.icu.util.TimeZone> uniqueTimeZones = new ArrayList<>();
-        for (android.icu.util.TimeZone zone : zones) {
-            // See if we already have this offset,
-            // Using slow but space efficient and these are small.
-            boolean found = false;
-            for (int i = 0; i < uniqueTimeZones.size(); i++) {
-                if (uniqueTimeZones.get(i).getRawOffset() == zone.getRawOffset()) {
-                    found = true;
-                    break;
-                }
-            }
-            if (!found) {
-                if (DBG) {
-                    Log.d(TAG, "getTimeZonesWithUniqueOffsets: add unique offset=" +
-                            zone.getRawOffset() + " zone.getID=" + zone.getID());
-                }
-                uniqueTimeZones.add(zone);
-            }
-        }
-
-        synchronized(sLastUniqueLockObj) {
-            // Cache the last result
-            sLastUniqueZoneOffsets = extractZoneIds(uniqueTimeZones);
-            sLastUniqueCountry = country;
-
-            return sLastUniqueZoneOffsets;
-        }
-    }
-
-    private static List<String> extractZoneIds(List<android.icu.util.TimeZone> timeZones) {
-        List<String> ids = new ArrayList<>(timeZones.size());
-        for (android.icu.util.TimeZone timeZone : timeZones) {
-            ids.add(timeZone.getID());
-        }
-        return Collections.unmodifiableList(ids);
-    }
-
-    /**
-     * Returns an immutable list of frozen ICU time zones for the country.
-     *
-     * @param countryIso is a two character country code.
-     * @return TimeZone list, maybe empty but never null.
-     * @hide
-     */
-    private static List<android.icu.util.TimeZone> getIcuTimeZones(String countryIso) {
-        if (countryIso == null) {
-            if (DBG) Log.d(TAG, "getIcuTimeZones(null): return empty list");
-            return Collections.emptyList();
-        }
-        List<android.icu.util.TimeZone> timeZones =
-                TimeZoneFinder.getInstance().lookupTimeZonesByCountry(countryIso);
-        if (timeZones == null) {
-            if (DBG) {
-                Log.d(TAG, "getIcuTimeZones(" + countryIso
-                        + "): returned null, converting to empty list");
-            }
-            return Collections.emptyList();
-        }
-        return timeZones;
-    }
-
-    /**
      * Returns a String indicating the version of the time zone database currently
      * in use.  The format of the string is dependent on the underlying time zone
      * database implementation, but will typically contain the year in which the database
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 8c70322..bfcf285 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -294,6 +294,11 @@
     boolean hasNavigationBar();
 
     /**
+    * Get the position of the nav bar
+    */
+    int getNavBarPosition();
+
+    /**
      * Lock the device immediately with the specified options (can be null).
      */
     void lockNow(in Bundle options);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ad71b58..bd3be1b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3226,6 +3226,11 @@
      */
     private static final int PFLAG3_SCREEN_READER_FOCUSABLE = 0x10000000;
 
+    /**
+     * The last aggregated visibility. Used to detect when it truly changes.
+     */
+    private static final int PFLAG3_AGGREGATED_VISIBLE = 0x20000000;
+
     /* End of masks for mPrivateFlags3 */
 
     /**
@@ -3387,6 +3392,18 @@
      * decorations when they are shown.  You can perform layout of your inner
      * UI elements to account for non-fullscreen system UI through the
      * {@link #fitSystemWindows(Rect)} method.
+     *
+     * <p>Note: on displays that have a {@link DisplayCutout}, the window may still be placed
+     *  differently than if {@link #SYSTEM_UI_FLAG_FULLSCREEN} was set, if the
+     *  window's {@link WindowManager.LayoutParams#layoutInDisplayCutoutMode
+     *  layoutInDisplayCutoutMode} is
+     *  {@link WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
+     *  LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}. To avoid this, use either of the other modes.
+     *
+     * @see WindowManager.LayoutParams#layoutInDisplayCutoutMode
+     * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
+     * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+     * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
      */
     public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400;
 
@@ -7354,7 +7371,12 @@
      * @hide
      */
     public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
-        if (!isShown()) {
+        // Panes disappearing are relevant even if though the view is no longer visible.
+        boolean isWindowStateChanged =
+                (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        boolean isWindowDisappearedEvent = isWindowStateChanged && ((event.getContentChangeTypes()
+                & AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED) != 0);
+        if (!isShown() && !isWindowDisappearedEvent) {
             return;
         }
         onInitializeAccessibilityEvent(event);
@@ -7462,6 +7484,10 @@
      * @hide
      */
     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+        if ((event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
+                && !TextUtils.isEmpty(getAccessibilityPaneTitle())) {
+            event.getText().add(getAccessibilityPaneTitle());
+        }
     }
 
     /**
@@ -11587,6 +11613,23 @@
         if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
             return;
         }
+        // Changes to views with a pane title count as window state changes, as the pane title
+        // marks them as significant parts of the UI.
+        if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) {
+            final AccessibilityEvent event = AccessibilityEvent.obtain();
+            event.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+            event.setContentChangeTypes(changeType);
+            onPopulateAccessibilityEvent(event);
+            if (mParent != null) {
+                try {
+                    mParent.requestSendAccessibilityEvent(this, event);
+                } catch (AbstractMethodError e) {
+                    Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName()
+                            + " does not fully implement ViewParent", e);
+                }
+            }
+        }
+
         if (mParent != null) {
             try {
                 mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType);
@@ -12520,6 +12563,10 @@
      */
     @CallSuper
     public void onVisibilityAggregated(boolean isVisible) {
+        // Update our internal visibility tracking so we can detect changes
+        boolean oldVisible = (mPrivateFlags3 & PFLAG3_AGGREGATED_VISIBLE) != 0;
+        mPrivateFlags3 = isVisible ? (mPrivateFlags3 | PFLAG3_AGGREGATED_VISIBLE)
+                : (mPrivateFlags3 & ~PFLAG3_AGGREGATED_VISIBLE);
         if (isVisible && mAttachInfo != null) {
             initialAwakenScrollBars();
         }
@@ -12560,6 +12607,13 @@
                 }
             }
         }
+        if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) {
+            if (isVisible != oldVisible) {
+                notifyAccessibilityStateChanged(isVisible
+                        ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
+                        : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f81a4c3..fe3b696 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -20,7 +20,7 @@
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
 import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
-import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
@@ -1596,9 +1596,9 @@
 
     void dispatchApplyInsets(View host) {
         WindowInsets insets = getWindowInsets(true /* forceConstruct */);
-        final boolean layoutInCutout =
-                (mWindowAttributes.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0;
-        if (!layoutInCutout) {
+        final boolean dispatchCutout = (mWindowAttributes.layoutInDisplayCutoutMode
+                == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS);
+        if (!dispatchCutout) {
             // Window is either not laid out in cutout or the status bar inset takes care of
             // clearing the cutout, so we don't need to dispatch the cutout to the hierarchy.
             insets = insets.consumeDisplayCutout();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index a65aba1..50d7118 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -21,7 +21,6 @@
 import static android.view.WindowLayoutParamsProto.BUTTON_BRIGHTNESS;
 import static android.view.WindowLayoutParamsProto.COLOR_MODE;
 import static android.view.WindowLayoutParamsProto.FLAGS;
-import static android.view.WindowLayoutParamsProto.FLAGS_EXTRA;
 import static android.view.WindowLayoutParamsProto.FORMAT;
 import static android.view.WindowLayoutParamsProto.GRAVITY;
 import static android.view.WindowLayoutParamsProto.HAS_SYSTEM_UI_LISTENERS;
@@ -46,7 +45,6 @@
 
 import android.Manifest.permission;
 import android.annotation.IntDef;
-import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -889,7 +887,12 @@
          *  decorations around the border (such as the status bar).  The
          *  window must correctly position its contents to take the screen
          *  decoration into account.  This flag is normally set for you
-         *  by Window as described in {@link Window#setFlags}. */
+         *  by Window as described in {@link Window#setFlags}.
+         *
+         *  <p>Note: on displays that have a {@link DisplayCutout}, the window may be placed
+         *  such that it avoids the {@link DisplayCutout} area if necessary according to the
+         *  {@link #layoutInDisplayCutoutMode}.
+         */
         public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
 
         /** Window flag: allow window to extend outside of the screen. */
@@ -1295,33 +1298,6 @@
         }, formatToHexString = true)
         public int flags;
 
-        /** @hide */
-        @Retention(RetentionPolicy.SOURCE)
-        @LongDef(
-            flag = true,
-            value = {
-                    LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA,
-            })
-        @interface Flags2 {}
-
-        /**
-         * Window flag: allow placing the window within the area that overlaps with the
-         * display cutout.
-         *
-         * <p>
-         * The window must correctly position its contents to take the display cutout into account.
-         *
-         * @see DisplayCutout
-         */
-        public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001;
-
-        /**
-         * Various behavioral options/flags.  Default is none.
-         *
-         * @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA
-         */
-        @Flags2 public long flags2;
-
         /**
          * If the window has requested hardware acceleration, but this is not
          * allowed in the process it is in, then still render it as if it is
@@ -2050,6 +2026,77 @@
          */
         public boolean hasSystemUiListeners;
 
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(
+                flag = true,
+                value = {LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
+                        LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS,
+                        LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER})
+        @interface LayoutInDisplayCutoutMode {}
+
+        /**
+         * Controls how the window is laid out if there is a {@link DisplayCutout}.
+         *
+         * <p>
+         * Defaults to {@link #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}.
+         *
+         * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
+         * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+         * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
+         * @see DisplayCutout
+         */
+        @LayoutInDisplayCutoutMode
+        public int layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+
+        /**
+         * The window is allowed to extend into the {@link DisplayCutout} area, only if the
+         * {@link DisplayCutout} is fully contained within the status bar. Otherwise, the window is
+         * laid out such that it does not overlap with the {@link DisplayCutout} area.
+         *
+         * <p>
+         * In practice, this means that if the window did not set FLAG_FULLSCREEN or
+         * SYSTEM_UI_FLAG_FULLSCREEN, it can extend into the cutout area in portrait.
+         * Otherwise (i.e. fullscreen or landscape) it is laid out such that it does overlap the
+         * cutout area.
+         *
+         * <p>
+         * The usual precautions for not overlapping with the status bar are sufficient for ensuring
+         * that no important content overlaps with the DisplayCutout.
+         *
+         * @see DisplayCutout
+         * @see WindowInsets
+         */
+        public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0;
+
+        /**
+         * The window is always allowed to extend into the {@link DisplayCutout} area,
+         * even if fullscreen or in landscape.
+         *
+         * <p>
+         * The window must make sure that no important content overlaps with the
+         * {@link DisplayCutout}.
+         *
+         * @see DisplayCutout
+         * @see WindowInsets#getDisplayCutout()
+         */
+        public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1;
+
+        /**
+         * The window is never allowed to overlap with the DisplayCutout area.
+         *
+         * <p>
+         * This should be used with windows that transiently set SYSTEM_UI_FLAG_FULLSCREEN to
+         * avoid a relayout of the window when the flag is set or cleared.
+         *
+         * @see DisplayCutout
+         * @see View#SYSTEM_UI_FLAG_FULLSCREEN SYSTEM_UI_FLAG_FULLSCREEN
+         * @see View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+         */
+        public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2;
+
+
         /**
          * When this window has focus, disable touch pad pointer gesture processing.
          * The window will receive raw position updates from the touch pad instead
@@ -2273,9 +2320,9 @@
             out.writeInt(y);
             out.writeInt(type);
             out.writeInt(flags);
-            out.writeLong(flags2);
             out.writeInt(privateFlags);
             out.writeInt(softInputMode);
+            out.writeInt(layoutInDisplayCutoutMode);
             out.writeInt(gravity);
             out.writeFloat(horizontalMargin);
             out.writeFloat(verticalMargin);
@@ -2329,9 +2376,9 @@
             y = in.readInt();
             type = in.readInt();
             flags = in.readInt();
-            flags2 = in.readLong();
             privateFlags = in.readInt();
             softInputMode = in.readInt();
+            layoutInDisplayCutoutMode = in.readInt();
             gravity = in.readInt();
             horizontalMargin = in.readFloat();
             verticalMargin = in.readFloat();
@@ -2462,10 +2509,6 @@
                 flags = o.flags;
                 changes |= FLAGS_CHANGED;
             }
-            if (flags2 != o.flags2) {
-                flags2 = o.flags2;
-                changes |= FLAGS_CHANGED;
-            }
             if (privateFlags != o.privateFlags) {
                 privateFlags = o.privateFlags;
                 changes |= PRIVATE_FLAGS_CHANGED;
@@ -2474,6 +2517,10 @@
                 softInputMode = o.softInputMode;
                 changes |= SOFT_INPUT_MODE_CHANGED;
             }
+            if (layoutInDisplayCutoutMode != o.layoutInDisplayCutoutMode) {
+                layoutInDisplayCutoutMode = o.layoutInDisplayCutoutMode;
+                changes |= LAYOUT_CHANGED;
+            }
             if (gravity != o.gravity) {
                 gravity = o.gravity;
                 changes |= LAYOUT_CHANGED;
@@ -2651,6 +2698,10 @@
                 sb.append(softInputModeToString(softInputMode));
                 sb.append('}');
             }
+            if (layoutInDisplayCutoutMode != 0) {
+                sb.append(" layoutInDisplayCutoutMode=");
+                sb.append(layoutInDisplayCutoutModeToString(layoutInDisplayCutoutMode));
+            }
             sb.append(" ty=");
             sb.append(ViewDebug.intToString(LayoutParams.class, "type", type));
             if (format != PixelFormat.OPAQUE) {
@@ -2719,11 +2770,6 @@
             sb.append(System.lineSeparator());
             sb.append(prefix).append("  fl=").append(
                     ViewDebug.flagsToString(LayoutParams.class, "flags", flags));
-            if (flags2 != 0) {
-                sb.append(System.lineSeparator());
-                // TODO(roosa): add a long overload for ViewDebug.flagsToString.
-                sb.append(prefix).append("  fl2=0x").append(Long.toHexString(flags2));
-            }
             if (privateFlags != 0) {
                 sb.append(System.lineSeparator());
                 sb.append(prefix).append("  pfl=").append(ViewDebug.flagsToString(
@@ -2771,7 +2817,6 @@
             proto.write(NEEDS_MENU_KEY, needsMenuKey);
             proto.write(COLOR_MODE, mColorMode);
             proto.write(FLAGS, flags);
-            proto.write(FLAGS_EXTRA, flags2);
             proto.write(PRIVATE_FLAGS, privateFlags);
             proto.write(SYSTEM_UI_VISIBILITY_FLAGS, systemUiVisibility);
             proto.write(SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS, subtreeSystemUiVisibility);
@@ -2849,6 +2894,20 @@
                     && height == WindowManager.LayoutParams.MATCH_PARENT;
         }
 
+        private static String layoutInDisplayCutoutModeToString(
+                @LayoutInDisplayCutoutMode int mode) {
+            switch (mode) {
+                case LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:
+                    return "default";
+                case LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS:
+                    return "always";
+                case LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:
+                    return "never";
+                default:
+                    return "unknown(" + mode + ")";
+            }
+        }
+
         private static String softInputModeToString(@SoftInputModeFlags int softInputMode) {
             final StringBuilder result = new StringBuilder();
             final int state = softInputMode & SOFT_INPUT_MASK_STATE;
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 9c1c9e3..a6f36bb 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -45,6 +45,11 @@
     int PRESENCE_INTERNAL = 1 << 0;
     int PRESENCE_EXTERNAL = 1 << 1;
 
+    // Navigation bar position values
+    int NAV_BAR_LEFT = 1 << 0;
+    int NAV_BAR_RIGHT = 1 << 1;
+    int NAV_BAR_BOTTOM = 1 << 2;
+
     /**
      * Sticky broadcast of the current HDMI plugged state.
      */
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index aa61926..e0f74a7 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -192,9 +192,11 @@
  * <b>TRANSITION TYPES</b></br>
  * </p>
  * <p>
- * <b>Window state changed</b> - represents the event of opening a
- * {@link android.widget.PopupWindow}, {@link android.view.Menu},
- * {@link android.app.Dialog}, etc.</br>
+ * <b>Window state changed</b> - represents the event of a change to a section of
+ * the user interface that is visually distinct. Should be sent from either the
+ * root view of a window or from a view that is marked as a pane
+ * {@link android.view.View#setAccessibilityPaneTitle(CharSequence)}. Not that changes
+ * to true windows are represented by {@link #TYPE_WINDOWS_CHANGED}.</br>
  * <em>Type:</em> {@link #TYPE_WINDOW_STATE_CHANGED}</br>
  * <em>Properties:</em></br>
  * <ul>
@@ -203,7 +205,7 @@
  *   <li>{@link #getClassName()} - The class name of the source.</li>
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
- *   <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ *   <li>{@link #getText()} - The text of the source's sub-tree, including the pane titles.</li>
  * </ul>
  * </p>
  * <p>
@@ -436,8 +438,10 @@
     public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010;
 
     /**
-     * Represents the event of opening a {@link android.widget.PopupWindow},
-     * {@link android.view.Menu}, {@link android.app.Dialog}, etc.
+     * Represents the event of a change to a visually distinct section of the user interface.
+     * These events should only be dispatched from {@link android.view.View}s that have
+     * accessibility pane titles, and replaces {@link #TYPE_WINDOW_CONTENT_CHANGED} for those
+     * sources. Details about the change are available from {@link #getContentChangeTypes()}.
      */
     public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020;
 
@@ -565,12 +569,30 @@
     public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
 
     /**
-     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
      * The node's pane title changed.
      */
     public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000008;
 
     /**
+     * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
+     * The node has a pane title, and either just appeared or just was assigned a title when it
+     * had none before.
+     */
+    public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 0x00000010;
+
+    /**
+     * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
+     * Can mean one of two slightly different things. The primary meaning is that the node has
+     * a pane title, and was removed from the node hierarchy. It will also be sent if the pane
+     * title is set to {@code null} after it contained a title.
+     * No source will be returned if the node is no longer on the screen. To make the change more
+     * clear for the user, the first entry in {@link #getText()} will return the value that would
+     * have been returned by {@code getSource().getPaneTitle()}.
+     */
+    public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00000020;
+
+    /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window was added.
      */
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 57f9895..e554540 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1,17 +1,17 @@
 /*
- * Copyright (C) 2007-2008 The Android Open Source Project
+ * Copyright (C) 2007 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
+ * 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
+ *      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.
+ * 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.inputmethod;
@@ -131,13 +131,13 @@
      * spans. <strong>Editor authors</strong>: you should strive to
      * send text with styles if possible, but it is not required.
      */
-    static final int GET_TEXT_WITH_STYLES = 0x0001;
+    int GET_TEXT_WITH_STYLES = 0x0001;
 
     /**
      * Flag for use with {@link #getExtractedText} to indicate you
      * would like to receive updates when the extracted text changes.
      */
-    public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001;
+    int GET_EXTRACTED_TEXT_MONITOR = 0x0001;
 
     /**
      * Get <var>n</var> characters of text before the current cursor
@@ -176,7 +176,7 @@
      * @return the text before the cursor position; the length of the
      * returned text might be less than <var>n</var>.
      */
-    public CharSequence getTextBeforeCursor(int n, int flags);
+    CharSequence getTextBeforeCursor(int n, int flags);
 
     /**
      * Get <var>n</var> characters of text after the current cursor
@@ -215,7 +215,7 @@
      * @return the text after the cursor position; the length of the
      * returned text might be less than <var>n</var>.
      */
-    public CharSequence getTextAfterCursor(int n, int flags);
+    CharSequence getTextAfterCursor(int n, int flags);
 
     /**
      * Gets the selected text, if any.
@@ -249,7 +249,7 @@
      * later, returns false when the target application does not implement
      * this method.
      */
-    public CharSequence getSelectedText(int flags);
+    CharSequence getSelectedText(int flags);
 
     /**
      * Retrieve the current capitalization mode in effect at the
@@ -279,7 +279,7 @@
      * @return the caps mode flags that are in effect at the current
      * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}.
      */
-    public int getCursorCapsMode(int reqModes);
+    int getCursorCapsMode(int reqModes);
 
     /**
      * Retrieve the current text in the input connection's editor, and
@@ -314,8 +314,7 @@
      * longer valid of the editor can't comply with the request for
      * some reason.
      */
-    public ExtractedText getExtractedText(ExtractedTextRequest request,
-            int flags);
+    ExtractedText getExtractedText(ExtractedTextRequest request, int flags);
 
     /**
      * Delete <var>beforeLength</var> characters of text before the
@@ -342,8 +341,8 @@
      * delete more characters than are in the editor, as that may have
      * ill effects on the application. Calling this method will cause
      * the editor to call
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * on your service after the batch input is over.</p>
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on your service after the batch input is over.</p>
      *
      * <p><strong>Editor authors:</strong> please be careful of race
      * conditions in implementing this call. An IME can make a change
@@ -369,7 +368,7 @@
      *        that range.
      * @return true on success, false if the input connection is no longer valid.
      */
-    public boolean deleteSurroundingText(int beforeLength, int afterLength);
+    boolean deleteSurroundingText(int beforeLength, int afterLength);
 
     /**
      * A variant of {@link #deleteSurroundingText(int, int)}. Major differences are:
@@ -397,7 +396,7 @@
      * @return true on success, false if the input connection is no longer valid.  Returns
      * {@code false} when the target application does not implement this method.
      */
-    public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
+    boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
 
     /**
      * Replace the currently composing text with the given text, and
@@ -416,8 +415,8 @@
      * <p>This is usually called by IMEs to add or remove or change
      * characters in the composing span. Calling this method will
      * cause the editor to call
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * on the current IME after the batch input is over.</p>
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.</p>
      *
      * <p><strong>Editor authors:</strong> please keep in mind the
      * text may be very similar or completely different than what was
@@ -455,7 +454,7 @@
      * @return true on success, false if the input connection is no longer
      * valid.
      */
-    public boolean setComposingText(CharSequence text, int newCursorPosition);
+    boolean setComposingText(CharSequence text, int newCursorPosition);
 
     /**
      * Mark a certain region of text as composing text. If there was a
@@ -474,8 +473,8 @@
      * <p>Since this does not change the contents of the text, editors should not call
      * {@link InputMethodManager#updateSelection(View, int, int, int, int)} and
      * IMEs should not receive
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}.
-     * </p>
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)}.</p>
      *
      * <p>This has no impact on the cursor/selection position. It may
      * result in the cursor being anywhere inside or outside the
@@ -488,7 +487,7 @@
      * valid. In {@link android.os.Build.VERSION_CODES#N} and later, false is returned when the
      * target application does not implement this method.
      */
-    public boolean setComposingRegion(int start, int end);
+    boolean setComposingRegion(int start, int end);
 
     /**
      * Have the text editor finish whatever composing text is
@@ -507,7 +506,7 @@
      * @return true on success, false if the input connection
      * is no longer valid.
      */
-    public boolean finishComposingText();
+    boolean finishComposingText();
 
     /**
      * Commit text to the text box and set the new cursor position.
@@ -522,8 +521,8 @@
      * then {@link #finishComposingText()}.</p>
      *
      * <p>Calling this method will cause the editor to call
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * on the current IME after the batch input is over.
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.
      * <strong>Editor authors</strong>, for this to happen you need to
      * make the changes known to the input method by calling
      * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
@@ -543,7 +542,7 @@
      * @return true on success, false if the input connection is no longer
      * valid.
      */
-    public boolean commitText(CharSequence text, int newCursorPosition);
+    boolean commitText(CharSequence text, int newCursorPosition);
 
     /**
      * Commit a completion the user has selected from the possible ones
@@ -569,8 +568,8 @@
      *
      * <p>Calling this method (with a valid {@link CompletionInfo} object)
      * will cause the editor to call
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * on the current IME after the batch input is over.
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.
      * <strong>Editor authors</strong>, for this to happen you need to
      * make the changes known to the input method by calling
      * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
@@ -581,15 +580,15 @@
      * @return true on success, false if the input connection is no longer
      * valid.
      */
-    public boolean commitCompletion(CompletionInfo text);
+    boolean commitCompletion(CompletionInfo text);
 
     /**
      * Commit a correction automatically performed on the raw user's input. A
      * typical example would be to correct typos using a dictionary.
      *
      * <p>Calling this method will cause the editor to call
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * on the current IME after the batch input is over.
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.
      * <strong>Editor authors</strong>, for this to happen you need to
      * make the changes known to the input method by calling
      * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
@@ -601,7 +600,7 @@
      * In {@link android.os.Build.VERSION_CODES#N} and later, returns false
      * when the target application does not implement this method.
      */
-    public boolean commitCorrection(CorrectionInfo correctionInfo);
+    boolean commitCorrection(CorrectionInfo correctionInfo);
 
     /**
      * Set the selection of the text editor. To set the cursor
@@ -609,8 +608,8 @@
      *
      * <p>Since this moves the cursor, calling this method will cause
      * the editor to call
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * on the current IME after the batch input is over.
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.
      * <strong>Editor authors</strong>, for this to happen you need to
      * make the changes known to the input method by calling
      * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
@@ -628,7 +627,7 @@
      * @return true on success, false if the input connection is no longer
      * valid.
      */
-    public boolean setSelection(int start, int end);
+    boolean setSelection(int start, int end);
 
     /**
      * Have the editor perform an action it has said it can do.
@@ -642,7 +641,7 @@
      * @return true on success, false if the input connection is no longer
      * valid.
      */
-    public boolean performEditorAction(int editorAction);
+    boolean performEditorAction(int editorAction);
 
     /**
      * Perform a context menu action on the field. The given id may be one of:
@@ -652,7 +651,7 @@
      * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
      * or {@link android.R.id#switchInputMethod}
      */
-    public boolean performContextMenuAction(int id);
+    boolean performContextMenuAction(int id);
 
     /**
      * Tell the editor that you are starting a batch of editor
@@ -662,8 +661,8 @@
      *
      * <p><strong>IME authors:</strong> use this to avoid getting
      * calls to
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * corresponding to intermediate state. Also, use this to avoid
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} corresponding to intermediate state. Also, use this to avoid
      * flickers that may arise from displaying intermediate state. Be
      * sure to call {@link #endBatchEdit} for each call to this, or
      * you may block updates in the editor.</p>
@@ -678,7 +677,7 @@
      * this method starts a batch edit, that means it will always return true
      * unless the input connection is no longer valid.
      */
-    public boolean beginBatchEdit();
+    boolean beginBatchEdit();
 
     /**
      * Tell the editor that you are done with a batch edit previously
@@ -696,7 +695,7 @@
      * the latest one (in other words, if the nesting count is > 0), false
      * otherwise or if the input connection is no longer valid.
      */
-    public boolean endBatchEdit();
+    boolean endBatchEdit();
 
     /**
      * Send a key event to the process that is currently attached
@@ -734,7 +733,7 @@
      * @see KeyCharacterMap#PREDICTIVE
      * @see KeyCharacterMap#ALPHA
      */
-    public boolean sendKeyEvent(KeyEvent event);
+    boolean sendKeyEvent(KeyEvent event);
 
     /**
      * Clear the given meta key pressed states in the given input
@@ -749,7 +748,7 @@
      * @return true on success, false if the input connection is no longer
      * valid.
      */
-    public boolean clearMetaKeyStates(int states);
+    boolean clearMetaKeyStates(int states);
 
     /**
      * Called back when the connected IME switches between fullscreen and normal modes.
@@ -766,7 +765,7 @@
      *         devices.
      * @see InputMethodManager#isFullscreenMode()
      */
-    public boolean reportFullscreenMode(boolean enabled);
+    boolean reportFullscreenMode(boolean enabled);
 
     /**
      * API to send private commands from an input method to its
@@ -786,7 +785,7 @@
      * associated editor understood it), false if the input connection is no longer
      * valid.
      */
-    public boolean performPrivateCommand(String action, Bundle data);
+    boolean performPrivateCommand(String action, Bundle data);
 
     /**
      * The editor is requested to call
@@ -794,7 +793,7 @@
      * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be
      * used together with {@link #CURSOR_UPDATE_MONITOR}.
      */
-    public static final int CURSOR_UPDATE_IMMEDIATE = 1 << 0;
+    int CURSOR_UPDATE_IMMEDIATE = 1 << 0;
 
     /**
      * The editor is requested to call
@@ -805,7 +804,7 @@
      * This flag can be used together with {@link #CURSOR_UPDATE_IMMEDIATE}.
      * </p>
      */
-    public static final int CURSOR_UPDATE_MONITOR = 1 << 1;
+    int CURSOR_UPDATE_MONITOR = 1 << 1;
 
     /**
      * Called by the input method to ask the editor for calling back
@@ -821,7 +820,7 @@
      * In {@link android.os.Build.VERSION_CODES#N} and later, returns {@code false} also when the
      * target application does not implement this method.
      */
-    public boolean requestCursorUpdates(int cursorUpdateMode);
+    boolean requestCursorUpdates(int cursorUpdateMode);
 
     /**
      * Called by the {@link InputMethodManager} to enable application developers to specify a
@@ -832,7 +831,7 @@
      *
      * @return {@code null} to use the default {@link Handler}.
      */
-    public Handler getHandler();
+    Handler getHandler();
 
     /**
      * Called by the system up to only once to notify that the system is about to invalidate
@@ -846,7 +845,7 @@
      *
      * <p>Note: This does nothing when called from input methods.</p>
      */
-    public void closeConnection();
+    void closeConnection();
 
     /**
      * When this flag is used, the editor will be able to request read access to the content URI
@@ -863,7 +862,7 @@
      * client is able to request a temporary read-only access even after the current IME is switched
      * to any other IME as long as the client keeps {@link InputContentInfo} object.</p>
      **/
-    public static int INPUT_CONTENT_GRANT_READ_URI_PERMISSION =
+    int INPUT_CONTENT_GRANT_READ_URI_PERMISSION =
             android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;  // 0x00000001
 
     /**
@@ -897,6 +896,6 @@
      * @return {@code true} if this request is accepted by the application, whether the request
      * is already handled or still being handled in background, {@code false} otherwise.
      */
-    public boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags,
+    boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags,
             @Nullable Bundle opts);
 }
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 317730c..f671e22 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -1,17 +1,17 @@
 /*
- * Copyright (C) 2007-2008 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
- * 
+ * Copyright (C) 2007 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.
+ * 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.inputmethod;
@@ -74,6 +74,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public CharSequence getTextBeforeCursor(int n, int flags) {
         return mTarget.getTextBeforeCursor(n, flags);
     }
@@ -82,6 +83,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public CharSequence getTextAfterCursor(int n, int flags) {
         return mTarget.getTextAfterCursor(n, flags);
     }
@@ -90,6 +92,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public CharSequence getSelectedText(int flags) {
         return mTarget.getSelectedText(flags);
     }
@@ -98,6 +101,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public int getCursorCapsMode(int reqModes) {
         return mTarget.getCursorCapsMode(reqModes);
     }
@@ -106,6 +110,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
         return mTarget.getExtractedText(request, flags);
     }
@@ -114,6 +119,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
         return mTarget.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
     }
@@ -122,6 +128,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
         return mTarget.deleteSurroundingText(beforeLength, afterLength);
     }
@@ -130,6 +137,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean setComposingText(CharSequence text, int newCursorPosition) {
         return mTarget.setComposingText(text, newCursorPosition);
     }
@@ -138,6 +146,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean setComposingRegion(int start, int end) {
         return mTarget.setComposingRegion(start, end);
     }
@@ -146,6 +155,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean finishComposingText() {
         return mTarget.finishComposingText();
     }
@@ -154,6 +164,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean commitText(CharSequence text, int newCursorPosition) {
         return mTarget.commitText(text, newCursorPosition);
     }
@@ -162,6 +173,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean commitCompletion(CompletionInfo text) {
         return mTarget.commitCompletion(text);
     }
@@ -170,6 +182,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean commitCorrection(CorrectionInfo correctionInfo) {
         return mTarget.commitCorrection(correctionInfo);
     }
@@ -178,6 +191,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean setSelection(int start, int end) {
         return mTarget.setSelection(start, end);
     }
@@ -186,6 +200,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean performEditorAction(int editorAction) {
         return mTarget.performEditorAction(editorAction);
     }
@@ -194,6 +209,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean performContextMenuAction(int id) {
         return mTarget.performContextMenuAction(id);
     }
@@ -202,6 +218,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean beginBatchEdit() {
         return mTarget.beginBatchEdit();
     }
@@ -210,6 +227,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean endBatchEdit() {
         return mTarget.endBatchEdit();
     }
@@ -218,6 +236,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean sendKeyEvent(KeyEvent event) {
         return mTarget.sendKeyEvent(event);
     }
@@ -226,6 +245,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean clearMetaKeyStates(int states) {
         return mTarget.clearMetaKeyStates(states);
     }
@@ -234,6 +254,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean reportFullscreenMode(boolean enabled) {
         return mTarget.reportFullscreenMode(enabled);
     }
@@ -242,6 +263,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean performPrivateCommand(String action, Bundle data) {
         return mTarget.performPrivateCommand(action, data);
     }
@@ -250,6 +272,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean requestCursorUpdates(int cursorUpdateMode) {
         return mTarget.requestCursorUpdates(cursorUpdateMode);
     }
@@ -258,6 +281,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public Handler getHandler() {
         return mTarget.getHandler();
     }
@@ -266,6 +290,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public void closeConnection() {
         mTarget.closeConnection();
     }
@@ -274,6 +299,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
         return mTarget.commitContent(inputContentInfo, flags, opts);
     }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 1e29120..7db5c32 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1082,15 +1082,15 @@
     }
 
     /**
-     * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
-     * input window should only be hidden if it was not explicitly shown
+     * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)}
+     * to indicate that the soft input window should only be hidden if it was not explicitly shown
      * by the user.
      */
     public static final int HIDE_IMPLICIT_ONLY = 0x0001;
 
     /**
-     * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
-     * input window should normally be hidden, unless it was originally
+     * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestShowSelf(int)}
+     * to indicate that the soft input window should normally be hidden, unless it was originally
      * shown with {@link #SHOW_FORCED}.
      */
     public static final int HIDE_NOT_ALWAYS = 0x0002;
@@ -1869,9 +1869,9 @@
      * @param flags Provides additional operating flags.  Currently may be
      * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
      * {@link #HIDE_NOT_ALWAYS} bit set.
-     * @deprecated Use {@link InputMethodService#hideSoftInputFromInputMethod(int)}
-     * instead. This method was intended for IME developers who should be accessing APIs through
-     * the service. APIs in this class are intended for app developers interacting with the IME.
+     * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was
+     * intended for IME developers who should be accessing APIs through the service. APIs in this
+     * class are intended for app developers interacting with the IME.
      */
     @Deprecated
     public void hideSoftInputFromInputMethod(IBinder token, int flags) {
@@ -1901,9 +1901,9 @@
      * @param flags Provides additional operating flags.  Currently may be
      * 0 or have the {@link #SHOW_IMPLICIT} or
      * {@link #SHOW_FORCED} bit set.
-     * @deprecated Use {@link InputMethodService#showSoftInputFromInputMethod(int)}
-     * instead. This method was intended for IME developers who should be accessing APIs through
-     * the service. APIs in this class are intended for app developers interacting with the IME.
+     * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was
+     * intended for IME developers who should be accessing APIs through the service. APIs in this
+     * class are intended for app developers interacting with the IME.
      */
     @Deprecated
     public void showSoftInputFromInputMethod(IBinder token, int flags) {
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index b3522ec..e9fe481 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -27,7 +27,6 @@
 import android.content.pm.Signature;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.StrictMode;
 import android.os.Trace;
 import android.util.AndroidRuntimeException;
 import android.util.ArraySet;
@@ -251,7 +250,6 @@
                         "WebView.disableWebView() was called: WebView is disabled");
             }
 
-            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
             try {
                 Class<WebViewFactoryProvider> providerClass = getProviderClass();
@@ -279,7 +277,6 @@
                 }
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
-                StrictMode.setThreadPolicy(oldPolicy);
             }
         }
     }
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b5ac330..247c806 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4627,7 +4627,7 @@
             return 0;
         }
 
-        protected final void showMagnifier() {
+        protected final void showMagnifier(@NonNull final MotionEvent event) {
             if (mMagnifier == null) {
                 return;
             }
@@ -4653,9 +4653,10 @@
 
             final Layout layout = mTextView.getLayout();
             final int lineNumber = layout.getLineForOffset(offset);
-            // Horizontally snap to character offset.
-            final float xPosInView = getHorizontal(mTextView.getLayout(), offset)
-                    + mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
+            // Horizontally move the magnifier smoothly.
+            final int[] textViewLocationOnScreen = new int[2];
+            mTextView.getLocationOnScreen(textViewLocationOnScreen);
+            final float xPosInView = event.getRawX() - textViewLocationOnScreen[0];
             // Vertically snap to middle of current line.
             final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber)
                     + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f
@@ -4850,11 +4851,11 @@
                 case MotionEvent.ACTION_DOWN:
                     mDownPositionX = ev.getRawX();
                     mDownPositionY = ev.getRawY();
-                    showMagnifier();
+                    showMagnifier(ev);
                     break;
 
                 case MotionEvent.ACTION_MOVE:
-                    showMagnifier();
+                    showMagnifier(ev);
                     break;
 
                 case MotionEvent.ACTION_UP:
@@ -5208,11 +5209,11 @@
                     // re-engages the handle.
                     mTouchWordDelta = 0.0f;
                     mPrevX = UNSET_X_VALUE;
-                    showMagnifier();
+                    showMagnifier(event);
                     break;
 
                 case MotionEvent.ACTION_MOVE:
-                    showMagnifier();
+                    showMagnifier(event);
                     break;
 
                 case MotionEvent.ACTION_UP:
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 26dfcc2..310b170 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -32,6 +32,7 @@
 import android.view.Surface;
 import android.view.SurfaceView;
 import android.view.View;
+import android.view.ViewParent;
 
 import com.android.internal.util.Preconditions;
 
@@ -44,6 +45,8 @@
     private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
     // The view to which this magnifier is attached.
     private final View mView;
+    // The coordinates of the view in the surface.
+    private final int[] mViewCoordinatesInSurface;
     // The window containing the magnifier.
     private final PopupWindow mWindow;
     // The center coordinates of the window containing the magnifier.
@@ -87,6 +90,8 @@
                 com.android.internal.R.dimen.magnifier_height);
         mZoomScale = context.getResources().getFloat(
                 com.android.internal.R.dimen.magnifier_zoom_scale);
+        // The view's surface coordinates will not be updated until the magnifier is first shown.
+        mViewCoordinatesInSurface = new int[2];
 
         mWindow = new PopupWindow(context);
         mWindow.setContentView(content);
@@ -120,9 +125,34 @@
         configureCoordinates(xPosInView, yPosInView);
 
         // Clamp startX value to avoid distorting the rendering of the magnifier content.
-        final int startX = Math.max(0, Math.min(
+        // For this, we compute:
+        // - zeroScrollXInSurface: this is the start x of mView, where this is not masked by a
+        //                         potential scrolling container. For example, if mView is a
+        //                         TextView contained in a HorizontalScrollView,
+        //                         mViewCoordinatesInSurface will reflect the surface position of
+        //                         the first text character, rather than the position of the first
+        //                         visible one. Therefore, we need to add back the amount of
+        //                         scrolling from the parent containers.
+        // - actualWidth: similarly, the width of a View will be larger than its actually visible
+        //                width when it is contained in a scrolling container. We need to use
+        //                the minimum width of a scrolling container which contains this view.
+        int zeroScrollXInSurface = mViewCoordinatesInSurface[0];
+        int actualWidth = mView.getWidth();
+        ViewParent viewParent = mView.getParent();
+        while (viewParent instanceof View) {
+            final View container = (View) viewParent;
+            if (container.canScrollHorizontally(-1 /* left scroll */)
+                    || container.canScrollHorizontally(1 /* right scroll */)) {
+                zeroScrollXInSurface += container.getScrollX();
+                actualWidth = Math.min(actualWidth, container.getWidth()
+                        - container.getPaddingLeft() - container.getPaddingRight());
+            }
+            viewParent = viewParent.getParent();
+        }
+
+        final int startX = Math.max(zeroScrollXInSurface, Math.min(
                 mCenterZoomCoords.x - mBitmap.getWidth() / 2,
-                mView.getWidth() - mBitmap.getWidth()));
+                zeroScrollXInSurface + actualWidth - mBitmap.getWidth()));
         final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
 
         if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) {
@@ -169,10 +199,9 @@
             posX = xPosInView;
             posY = yPosInView;
         } else {
-            final int[] coordinatesInSurface = new int[2];
-            mView.getLocationInSurface(coordinatesInSurface);
-            posX = xPosInView + coordinatesInSurface[0];
-            posY = yPosInView + coordinatesInSurface[1];
+            mView.getLocationInSurface(mViewCoordinatesInSurface);
+            posX = xPosInView + mViewCoordinatesInSurface[0];
+            posY = yPosInView + mViewCoordinatesInSurface[1];
         }
 
         mCenterZoomCoords.x = Math.round(posX);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 716b205..8c4e422 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -77,8 +77,8 @@
 import android.text.InputFilter;
 import android.text.InputType;
 import android.text.Layout;
+import android.text.MeasuredText;
 import android.text.ParcelableSpan;
-import android.text.PremeasuredText;
 import android.text.Selection;
 import android.text.SpanWatcher;
 import android.text.Spannable;
@@ -5393,7 +5393,7 @@
             if (imm != null) imm.restartInput(this);
         } else if (type == BufferType.SPANNABLE || mMovement != null) {
             text = mSpannableFactory.newSpannable(text);
-        } else if (!(text instanceof PremeasuredText || text instanceof CharWrapper)) {
+        } else if (!(text instanceof MeasuredText || text instanceof CharWrapper)) {
             text = TextUtils.stringOrSpannedString(text);
         }
 
diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java
new file mode 100644
index 0000000..310a7bb
--- /dev/null
+++ b/core/java/android/widget/VideoView2.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.media.AudioAttributes;
+import android.media.MediaPlayer;
+import android.media.update.ApiLoader;
+import android.media.update.VideoView2Provider;
+import android.media.update.ViewProvider;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+
+/**
+ * TODO PUBLIC API
+ * @hide
+ */
+public class VideoView2 extends FrameLayout {
+    @IntDef({
+            VIEW_TYPE_TEXTUREVIEW,
+            VIEW_TYPE_SURFACEVIEW
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ViewType {}
+    public static final int VIEW_TYPE_SURFACEVIEW = 1;
+    public static final int VIEW_TYPE_TEXTUREVIEW = 2;
+
+    private final VideoView2Provider mProvider;
+
+    /**
+     * @hide
+     */
+    public VideoView2(@NonNull Context context) {
+        this(context, null);
+    }
+
+    /**
+     * @hide
+     */
+    public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /**
+     * @hide
+     */
+    public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    /**
+     * @hide
+     */
+    public VideoView2(
+            @NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        mProvider = ApiLoader.getProvider(context).createVideoView2(this, new SuperProvider());
+    }
+
+    /**
+     * @hide
+     */
+    public VideoView2Provider getProvider() {
+        return mProvider;
+    }
+
+    /**
+     * @hide
+     */
+    public void start() {
+        mProvider.start_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void pause() {
+        mProvider.pause_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public int getDuration() {
+        return mProvider.getDuration_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public int getCurrentPosition() {
+        return mProvider.getCurrentPosition_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void seekTo(int msec) {
+        mProvider.seekTo_impl(msec);
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isPlaying() {
+        return mProvider.isPlaying_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public int getBufferPercentage() {
+        return mProvider.getBufferPercentage_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public int getAudioSessionId() {
+        return mProvider.getAudioSessionId_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void showSubtitle() {
+        mProvider.showSubtitle_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void hideSubtitle() {
+        mProvider.hideSubtitle_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void setAudioFocusRequest(int focusGain) {
+        mProvider.setAudioFocusRequest_impl(focusGain);
+    }
+
+    /**
+     * @hide
+     */
+    public void setAudioAttributes(@NonNull AudioAttributes attributes) {
+        mProvider.setAudioAttributes_impl(attributes);
+    }
+
+    /**
+     * @hide
+     */
+    public void setVideoPath(String path) {
+        mProvider.setVideoPath_impl(path);
+    }
+
+    /**
+     * @hide
+     */
+    public void setVideoURI(Uri uri) {
+        mProvider.setVideoURI_impl(uri);
+    }
+
+    /**
+     * @hide
+     */
+    public void setVideoURI(Uri uri, Map<String, String> headers) {
+        mProvider.setVideoURI_impl(uri, headers);
+    }
+
+    /**
+     * @hide
+     */
+    public void setMediaController2(MediaController2 controllerView) {
+        mProvider.setMediaController2_impl(controllerView);
+    }
+
+    /**
+     * @hide
+     */
+    public void setViewType(@ViewType int viewType) {
+        mProvider.setViewType_impl(viewType);
+    }
+
+    /**
+     * @hide
+     */
+    @ViewType
+    public int getViewType() {
+        return mProvider.getViewType_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void stopPlayback() {
+        mProvider.stopPlayback_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) {
+        mProvider.setOnPreparedListener_impl(l);
+    }
+
+    /**
+     * @hide
+     */
+    public void setOnCompletionListener(MediaPlayer.OnCompletionListener l) {
+        mProvider.setOnCompletionListener_impl(l);
+    }
+
+    /**
+     * @hide
+     */
+    public void setOnErrorListener(MediaPlayer.OnErrorListener l) {
+        mProvider.setOnErrorListener_impl(l);
+    }
+
+    /**
+     * @hide
+     */
+    public void setOnInfoListener(MediaPlayer.OnInfoListener l) {
+        mProvider.setOnInfoListener_impl(l);
+    }
+
+    /**
+     * @hide
+     */
+    public void setOnViewTypeChangedListener(OnViewTypeChangedListener l) {
+        mProvider.setOnViewTypeChangedListener_impl(l);
+    }
+
+    /**
+     * @hide
+     */
+    public interface OnViewTypeChangedListener {
+        /**
+         * @hide
+         */
+        void onViewTypeChanged(@ViewType int viewType);
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return mProvider.getAccessibilityClassName_impl();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return mProvider.onTouchEvent_impl(ev);
+    }
+
+    @Override
+    public boolean onTrackballEvent(MotionEvent ev) {
+        return mProvider.onTrackballEvent_impl(ev);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return mProvider.onKeyDown_impl(keyCode, event);
+    }
+
+    @Override
+    public void onFinishInflate() {
+        mProvider.onFinishInflate_impl();
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        return mProvider.dispatchKeyEvent_impl(event);
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        mProvider.setEnabled_impl(enabled);
+    }
+
+    private class SuperProvider implements ViewProvider {
+        @Override
+        public void onAttachedToWindow_impl() {
+            VideoView2.super.onAttachedToWindow();
+        }
+
+        @Override
+        public void onDetachedFromWindow_impl() {
+            VideoView2.super.onDetachedFromWindow();
+        }
+
+        @Override
+        public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
+            VideoView2.super.onLayout(changed, left, top, right, bottom);
+        }
+
+        @Override
+        public void draw_impl(Canvas canvas) {
+            VideoView2.super.draw(canvas);
+        }
+
+        @Override
+        public CharSequence getAccessibilityClassName_impl() {
+            return VideoView2.super.getAccessibilityClassName();
+        }
+
+        @Override
+        public boolean onTouchEvent_impl(MotionEvent ev) {
+            return VideoView2.super.onTouchEvent(ev);
+        }
+
+        @Override
+        public boolean onTrackballEvent_impl(MotionEvent ev) {
+            return VideoView2.super.onTrackballEvent(ev);
+        }
+
+        @Override
+        public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
+            return VideoView2.super.onKeyDown(keyCode, event);
+        }
+
+        @Override
+        public void onFinishInflate_impl() {
+            VideoView2.super.onFinishInflate();
+        }
+
+        @Override
+        public boolean dispatchKeyEvent_impl(KeyEvent event) {
+            return VideoView2.super.dispatchKeyEvent(event);
+        }
+
+        @Override
+        public void setEnabled_impl(boolean enabled) {
+            VideoView2.super.setEnabled(enabled);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index c5fe4cb..f814ba9 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -572,10 +572,12 @@
                 final String seInfo = null;
                 final String classLoaderContext =
                         getSystemServerClassLoaderContext(classPathForElement);
+                final int targetSdkVersion = 0;  // SystemServer targets the system's SDK version
                 try {
                     installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName,
                             instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter,
-                            uuid, classLoaderContext, seInfo, false /* downgrade */);
+                            uuid, classLoaderContext, seInfo, false /* downgrade */,
+                            targetSdkVersion);
                 } catch (RemoteException | ServiceSpecificException e) {
                     // Ignore (but log), we need this on the classpath for fallback mode.
                     Log.w(TAG, "Failed compiling classpath element for system server: "
diff --git a/core/java/com/android/internal/policy/KeyguardDismissCallback.java b/core/java/com/android/internal/policy/KeyguardDismissCallback.java
new file mode 100644
index 0000000..38337ec
--- /dev/null
+++ b/core/java/com/android/internal/policy/KeyguardDismissCallback.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy;
+
+import android.os.RemoteException;
+import com.android.internal.policy.IKeyguardDismissCallback;
+
+/**
+ * @hide
+ */
+public class KeyguardDismissCallback extends IKeyguardDismissCallback.Stub {
+
+    @Override
+    public void onDismissError() throws RemoteException {
+        // To be overidden
+    }
+
+    @Override
+    public void onDismissSucceeded() throws RemoteException {
+        // To be overidden
+    }
+
+    @Override
+    public void onDismissCancelled() throws RemoteException {
+        // To be overidden
+    }
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b3f66e9..96f3308 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -84,7 +84,7 @@
         "android_view_VelocityTracker.cpp",
         "android_text_AndroidCharacter.cpp",
         "android_text_Hyphenator.cpp",
-        "android_text_MeasuredText.cpp",
+        "android_text_MeasuredParagraph.cpp",
         "android_text_StaticLayout.cpp",
         "android_os_Debug.cpp",
         "android_os_GraphicsEnvironment.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 6d7fe05..6569b47 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -178,7 +178,7 @@
 extern int register_android_net_NetworkUtils(JNIEnv* env);
 extern int register_android_text_AndroidCharacter(JNIEnv *env);
 extern int register_android_text_Hyphenator(JNIEnv *env);
-extern int register_android_text_MeasuredText(JNIEnv* env);
+extern int register_android_text_MeasuredParagraph(JNIEnv* env);
 extern int register_android_text_StaticLayout(JNIEnv *env);
 extern int register_android_opengl_classes(JNIEnv *env);
 extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
@@ -1342,7 +1342,7 @@
     REG_JNI(register_android_content_XmlBlock),
     REG_JNI(register_android_text_AndroidCharacter),
     REG_JNI(register_android_text_Hyphenator),
-    REG_JNI(register_android_text_MeasuredText),
+    REG_JNI(register_android_text_MeasuredParagraph),
     REG_JNI(register_android_text_StaticLayout),
     REG_JNI(register_android_view_InputDevice),
     REG_JNI(register_android_view_KeyCharacterMap),
diff --git a/core/jni/android_text_MeasuredText.cpp b/core/jni/android_text_MeasuredParagraph.cpp
similarity index 83%
rename from core/jni/android_text_MeasuredText.cpp
rename to core/jni/android_text_MeasuredParagraph.cpp
index af9d131..bdae0b2 100644
--- a/core/jni/android_text_MeasuredText.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "MeasuredText"
+#define LOG_TAG "MeasuredParagraph"
 
 #include "ScopedIcuLocale.h"
 #include "unicode/locid.h"
@@ -49,7 +49,7 @@
     return reinterpret_cast<Paint*>(ptr);
 }
 
-static inline minikin::MeasuredText* toMeasuredText(jlong ptr) {
+static inline minikin::MeasuredText* toMeasuredParagraph(jlong ptr) {
     return reinterpret_cast<minikin::MeasuredText*>(ptr);
 }
 
@@ -57,8 +57,8 @@
     return reinterpret_cast<jlong>(ptr);
 }
 
-static void releaseMeasuredText(jlong measuredTextPtr) {
-    delete toMeasuredText(measuredTextPtr);
+static void releaseMeasuredParagraph(jlong measuredTextPtr) {
+    delete toMeasuredParagraph(measuredTextPtr);
 }
 
 // Regular JNI
@@ -84,7 +84,7 @@
 }
 
 // Regular JNI
-static jlong nBuildNativeMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr,
+static jlong nBuildNativeMeasuredParagraph(JNIEnv* env, jclass /* unused */, jlong builderPtr,
                                       jcharArray javaText) {
     ScopedCharArrayRO text(env, javaText);
     const minikin::U16StringPiece textBuffer(text.get(), text.size());
@@ -100,23 +100,23 @@
 
 // CriticalNative
 static jlong nGetReleaseFunc() {
-    return toJLong(&releaseMeasuredText);
+    return toJLong(&releaseMeasuredParagraph);
 }
 
 static const JNINativeMethod gMethods[] = {
-    // MeasuredTextBuilder native functions.
+    // MeasuredParagraphBuilder native functions.
     {"nInitBuilder", "()J", (void*) nInitBuilder},
     {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
     {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
-    {"nBuildNativeMeasuredText", "(J[C)J", (void*) nBuildNativeMeasuredText},
+    {"nBuildNativeMeasuredParagraph", "(J[C)J", (void*) nBuildNativeMeasuredParagraph},
     {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
 
-    // MeasuredText native functions.
+    // MeasuredParagraph native functions.
     {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc},  // Critical Natives
 };
 
-int register_android_text_MeasuredText(JNIEnv* env) {
-    return RegisterMethodsOrDie(env, "android/text/MeasuredText", gMethods, NELEM(gMethods));
+int register_android_text_MeasuredParagraph(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "android/text/MeasuredParagraph", gMethods, NELEM(gMethods));
 }
 
 }
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index b5c23df..682dc873 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -174,7 +174,7 @@
 
         // Inputs
         "[C"  // text
-        "J"  // MeasuredText ptr.
+        "J"  // MeasuredParagraph ptr.
         "I"  // length
         "F"  // firstWidth
         "I"  // firstWidthLineCount
diff --git a/core/proto/android/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto
index b81cd1f..f079e1e 100644
--- a/core/proto/android/view/windowlayoutparams.proto
+++ b/core/proto/android/view/windowlayoutparams.proto
@@ -58,7 +58,6 @@
   optional NeedsMenuState needs_menu_key = 22;
   optional .android.view.DisplayProto.ColorMode color_mode = 23;
   optional uint32 flags = 24;
-  optional uint64 flags_extra = 25;
   optional uint32 private_flags = 26;
   optional uint32 system_ui_visibility_flags = 27;
   optional uint32 subtree_system_ui_visibility_flags = 28;
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 353a1a5..445b19b 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -39,6 +39,10 @@
             android:layout_height="@dimen/notification_progress_bar_height"
             android:layout_marginTop="@dimen/notification_progress_margin_top"
             layout="@layout/notification_template_progress" />
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/notification_content_margin_bottom" />
     </LinearLayout>
     <include layout="@layout/notification_template_right_icon" />
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index 6b1049a..d47bff6 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -56,6 +56,12 @@
             android:id="@+id/notification_material_reply_container"
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginBottom="@dimen/notification_content_margin_bottom" />
     </LinearLayout>
     <include layout="@layout/notification_material_action_list" />
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_picture.xml b/core/res/res/layout/notification_template_material_big_picture.xml
index e94e646..76c0a67 100644
--- a/core/res/res/layout/notification_template_material_big_picture.xml
+++ b/core/res/res/layout/notification_template_material_big_picture.xml
@@ -64,6 +64,12 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 />
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginBottom="@dimen/notification_content_margin_bottom" />
     </LinearLayout>
     <include layout="@layout/notification_material_action_list" />
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index 3c87f92..ac4c052 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -67,6 +67,12 @@
                 android:id="@+id/notification_material_reply_container"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content" />
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginBottom="@dimen/notification_content_margin_bottom" />
     </LinearLayout>
     <include layout="@layout/notification_material_action_list" />
     <include layout="@layout/notification_template_right_icon" />
diff --git a/core/res/res/layout/notification_template_material_inbox.xml b/core/res/res/layout/notification_template_material_inbox.xml
index e4c91a4..718cf16 100644
--- a/core/res/res/layout/notification_template_material_inbox.xml
+++ b/core/res/res/layout/notification_template_material_inbox.xml
@@ -119,6 +119,12 @@
                 android:id="@+id/notification_material_reply_container"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content" />
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginBottom="@dimen/notification_content_margin_bottom" />
     </LinearLayout>
     <include layout="@layout/notification_material_action_list" />
     <include layout="@layout/notification_template_right_icon" />
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index a72ad53..34f5ae8 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -47,6 +47,10 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:spacing="@dimen/notification_messaging_spacing" />
+            <include layout="@layout/notification_template_smart_reply_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/notification_content_margin_bottom" />
         </LinearLayout>
     </LinearLayout>
     <include layout="@layout/notification_material_action_list" />
diff --git a/core/res/res/layout/notification_template_smart_reply_container.xml b/core/res/res/layout/notification_template_smart_reply_container.xml
new file mode 100644
index 0000000..637241e
--- /dev/null
+++ b/core/res/res/layout/notification_template_smart_reply_container.xml
@@ -0,0 +1,24 @@
+<?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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/smart_reply_container"
+              android:visibility="gone"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="horizontal">
+    <!-- SmartReplyView will be added here. -->
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index 0413100..f8a77f8 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -77,9 +77,9 @@
     <item name="secondary_content_alpha_material_dark" format="float" type="dimen">.7</item>
     <item name="secondary_content_alpha_material_light" format="float" type="dimen">0.54</item>
 
-    <item name="highlight_alpha_material_light" format="float" type="dimen">0.12</item>
-    <item name="highlight_alpha_material_dark" format="float" type="dimen">0.20</item>
-    <item name="highlight_alpha_material_colored" format="float" type="dimen">0.26</item>
+    <item name="highlight_alpha_material_light" format="float" type="dimen">0.16</item>
+    <item name="highlight_alpha_material_dark" format="float" type="dimen">0.32</item>
+    <item name="highlight_alpha_material_colored" format="float" type="dimen">0.48</item>
 
     <!-- Primary & accent colors -->
     <eat-comment />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f4ced58..50dc384 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2550,6 +2550,7 @@
   <java-symbol type="bool" name="config_mainBuiltInDisplayIsRound" />
 
   <java-symbol type="id" name="actions_container" />
+  <java-symbol type="id" name="smart_reply_container" />
   <java-symbol type="id" name="remote_input_tag" />
 
   <java-symbol type="attr" name="seekBarDialogPreferenceStyle" />
diff --git a/core/tests/coretests/src/android/text/MeasuredTextTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
similarity index 87%
rename from core/tests/coretests/src/android/text/MeasuredTextTest.java
rename to core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index ddef0c6..5d33397 100644
--- a/core/tests/coretests/src/android/text/MeasuredTextTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -31,7 +31,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class MeasuredTextTest {
+public class MeasuredParagraphTest {
     private static final TextDirectionHeuristic LTR = TextDirectionHeuristics.LTR;
     private static final TextDirectionHeuristic RTL = TextDirectionHeuristics.RTL;
 
@@ -60,9 +60,9 @@
 
     @Test
     public void buildForBidi() {
-        MeasuredText mt = null;
+        MeasuredParagraph mt = null;
 
-        mt = MeasuredText.buildForBidi("XXX", 0, 3, LTR, null);
+        mt = MeasuredParagraph.buildForBidi("XXX", 0, 3, LTR, null);
         assertNotNull(mt);
         assertNotNull(mt.getChars());
         assertEquals("XXX", charsToString(mt.getChars()));
@@ -75,7 +75,7 @@
         assertEquals(0, mt.getNativePtr());
 
         // Recycle it
-        MeasuredText mt2 = MeasuredText.buildForBidi("_VVV_", 1, 4, RTL, mt);
+        MeasuredParagraph mt2 = MeasuredParagraph.buildForBidi("_VVV_", 1, 4, RTL, mt);
         assertEquals(mt2, mt);
         assertNotNull(mt2.getChars());
         assertEquals("VVV", charsToString(mt.getChars()));
@@ -91,9 +91,9 @@
 
     @Test
     public void buildForMeasurement() {
-        MeasuredText mt = null;
+        MeasuredParagraph mt = null;
 
-        mt = MeasuredText.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null);
+        mt = MeasuredParagraph.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null);
         assertNotNull(mt);
         assertNotNull(mt.getChars());
         assertEquals("XXX", charsToString(mt.getChars()));
@@ -109,7 +109,8 @@
         assertEquals(0, mt.getNativePtr());
 
         // Recycle it
-        MeasuredText mt2 = MeasuredText.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt);
+        MeasuredParagraph mt2 =
+                MeasuredParagraph.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt);
         assertEquals(mt2, mt);
         assertNotNull(mt2.getChars());
         assertEquals("VVV", charsToString(mt.getChars()));
@@ -129,9 +130,9 @@
 
     @Test
     public void buildForStaticLayout() {
-        MeasuredText mt = null;
+        MeasuredParagraph mt = null;
 
-        mt = MeasuredText.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null);
+        mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null);
         assertNotNull(mt);
         assertNotNull(mt.getChars());
         assertEquals("XXX", charsToString(mt.getChars()));
@@ -145,7 +146,8 @@
         assertNotEquals(0, mt.getNativePtr());
 
         // Recycle it
-        MeasuredText mt2 = MeasuredText.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt);
+        MeasuredParagraph mt2 =
+                MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt);
         assertEquals(mt2, mt);
         assertNotNull(mt2.getChars());
         assertEquals("VVV", charsToString(mt.getChars()));
@@ -163,6 +165,6 @@
 
     @Test
     public void testFor70146381() {
-        MeasuredText.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null);
+        MeasuredParagraph.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null);
     }
 }
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index dea194e..4571553 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -16,17 +16,12 @@
 
 package android.graphics.drawable;
 
-import android.animation.Animator;
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.graphics.Canvas;
-import android.graphics.CanvasProperty;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.util.FloatProperty;
-import android.view.DisplayListCanvas;
-import android.view.RenderNodeAnimator;
 import android.view.animation.LinearInterpolator;
 
 /**
@@ -78,8 +73,8 @@
 
     private void onStateChanged(boolean animateChanged) {
         float newOpacity = 0.0f;
-        if (mHovered) newOpacity += 1.0f;
-        if (mFocused) newOpacity += 1.0f;
+        if (mHovered) newOpacity += .25f;
+        if (mFocused) newOpacity += .75f;
         if (mAnimator != null) {
             mAnimator.cancel();
             mAnimator = null;
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 734cff5..b883656 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -264,8 +264,8 @@
         }
 
         setRippleActive(enabled && pressed);
-
         setBackgroundActive(hovered, focused);
+
         return changed;
     }
 
@@ -879,22 +879,18 @@
         // Grab the color for the current state and cut the alpha channel in
         // half so that the ripple and background together yield full alpha.
         final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
-        final int halfAlpha = (Color.alpha(color) / 2) << 24;
         final Paint p = mRipplePaint;
 
         if (mMaskColorFilter != null) {
             // The ripple timing depends on the paint's alpha value, so we need
             // to push just the alpha channel into the paint and let the filter
             // handle the full-alpha color.
-            final int fullAlphaColor = color | (0xFF << 24);
-            mMaskColorFilter.setColor(fullAlphaColor);
-
-            p.setColor(halfAlpha);
+            mMaskColorFilter.setColor(color | 0xFF000000);
+            p.setColor(color & 0xFF000000);
             p.setColorFilter(mMaskColorFilter);
             p.setShader(mMaskShader);
         } else {
-            final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
-            p.setColor(halfAlphaColor);
+            p.setColor(color);
             p.setColorFilter(null);
             p.setShader(null);
         }
diff --git a/media/java/android/media/update/ApiLoader.java b/media/java/android/media/update/ApiLoader.java
index b57e02d..07483f6 100644
--- a/media/java/android/media/update/ApiLoader.java
+++ b/media/java/android/media/update/ApiLoader.java
@@ -49,8 +49,8 @@
                 Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
         sMediaLibrary = libContext.getClassLoader()
                 .loadClass(UPDATE_CLASS)
-                .getMethod(UPDATE_METHOD, Context.class)
-                .invoke(null, appContext);
+                .getMethod(UPDATE_METHOD, Context.class, Context.class)
+                .invoke(null, appContext, libContext);
         return sMediaLibrary;
     }
 }
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index 19f01c2..a1e2404 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SystemApi;
 import android.widget.MediaController2;
+import android.widget.VideoView2;
 
 /**
  * Interface for connecting the public API to an updatable implementation.
@@ -31,4 +32,5 @@
 public interface StaticProvider {
     MediaController2Provider createMediaController2(
             MediaController2 instance, ViewProvider superProvider);
+    VideoView2Provider createVideoView2(VideoView2 instance, ViewProvider superProvider);
 }
diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java
new file mode 100644
index 0000000..6fc9bdc
--- /dev/null
+++ b/media/java/android/media/update/VideoView2Provider.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.update;
+
+import android.media.AudioAttributes;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.widget.MediaController2;
+import android.widget.VideoView2;
+
+import java.util.Map;
+
+/**
+ * Interface for connecting the public API to an updatable implementation.
+ *
+ * Each instance object is connected to one corresponding updatable object which implements the
+ * runtime behavior of that class. There should a corresponding provider method for all public
+ * methods.
+ *
+ * All methods behave as per their namesake in the public API.
+ *
+ * @see android.widget.VideoView2
+ *
+ * @hide
+ */
+// TODO @SystemApi
+public interface VideoView2Provider extends ViewProvider {
+    void start_impl();
+    void pause_impl();
+    int getDuration_impl();
+    int getCurrentPosition_impl();
+    void seekTo_impl(int msec);
+    boolean isPlaying_impl();
+    int getBufferPercentage_impl();
+    int getAudioSessionId_impl();
+    void showSubtitle_impl();
+    void hideSubtitle_impl();
+    void setAudioFocusRequest_impl(int focusGain);
+    void setAudioAttributes_impl(AudioAttributes attributes);
+    void setVideoPath_impl(String path);
+    void setVideoURI_impl(Uri uri);
+    void setVideoURI_impl(Uri uri, Map<String, String> headers);
+    void setMediaController2_impl(MediaController2 controllerView);
+    void setViewType_impl(int viewType);
+    int getViewType_impl();
+    void stopPlayback_impl();
+    void setOnPreparedListener_impl(MediaPlayer.OnPreparedListener l);
+    void setOnCompletionListener_impl(MediaPlayer.OnCompletionListener l);
+    void setOnErrorListener_impl(MediaPlayer.OnErrorListener l);
+    void setOnInfoListener_impl(MediaPlayer.OnInfoListener l);
+    void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l);
+}
diff --git a/native/android/net.c b/native/android/net.c
index de4b90c..60296a7 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -27,7 +27,7 @@
     static const uint32_t k32BitMask = 0xffffffff;
     // This value MUST be kept in sync with the corresponding value in
     // the android.net.Network#getNetworkHandle() implementation.
-    static const uint32_t kHandleMagic = 0xfacade;
+    static const uint32_t kHandleMagic = 0xcafed00d;
 
     // Check for minimum acceptable version of the API in the low bits.
     if (handle != NETWORK_UNSPECIFIED &&
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
index adb4832..ae24c07 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
@@ -16,9 +16,9 @@
 package com.android.settingslib.core.lifecycle;
 
 import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-
 import static com.google.common.truth.Truth.assertThat;
 
+import android.arch.lifecycle.LifecycleOwner;
 import android.content.Context;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -48,6 +48,7 @@
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class LifecycleTest {
 
+    private LifecycleOwner mLifecycleOwner;
     private Lifecycle mLifecycle;
 
     public static class TestDialogFragment extends ObservableDialogFragment {
@@ -146,7 +147,8 @@
 
     @Before
     public void setUp() {
-        mLifecycle = new Lifecycle(() -> mLifecycle);
+        mLifecycleOwner = () -> mLifecycle;
+        mLifecycle = new Lifecycle(mLifecycleOwner);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java
index 5d5733e4..050877d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java
@@ -17,11 +17,11 @@
 package com.android.settingslib.development;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
+import android.arch.lifecycle.LifecycleOwner;
 import android.os.SystemProperties;
 import android.support.v7.preference.ListPreference;
 import android.support.v7.preference.Preference;
@@ -44,6 +44,7 @@
         shadows = SystemPropertiesTestImpl.class)
 public class LogpersistPreferenceControllerTest {
 
+    private LifecycleOwner mLifecycleOwner;
     private Lifecycle mLifecycle;
 
     @Mock
@@ -57,7 +58,8 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         SystemProperties.set("ro.debuggable", "1");
-        mLifecycle = new Lifecycle(() -> mLifecycle);
+        mLifecycleOwner = () -> mLifecycle;
+        mLifecycle = new Lifecycle(mLifecycleOwner);
         mController = new AbstractLogpersistPreferenceController(RuntimeEnvironment.application,
                 mLifecycle) {
             @Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
index 75b6c5f..88c57b5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
@@ -17,13 +17,13 @@
 package com.android.settingslib.widget;
 
 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.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.arch.lifecycle.LifecycleOwner;
 import android.support.v14.preference.PreferenceFragment;
 import android.support.v7.preference.PreferenceManager;
 import android.support.v7.preference.PreferenceScreen;
@@ -49,13 +49,15 @@
     @Mock
     private PreferenceScreen mScreen;
 
+    private LifecycleOwner mLifecycleOwner;
     private Lifecycle mLifecycle;
     private FooterPreferenceMixin mMixin;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mLifecycle = new Lifecycle(() -> mLifecycle);
+        mLifecycleOwner = () -> mLifecycle;
+        mLifecycle = new Lifecycle(mLifecycleOwner);
         when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class));
         when(mFragment.getPreferenceManager().getContext())
                 .thenReturn(ShadowApplication.getInstance().getApplicationContext());
diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml
index 287a888..fd4d296 100644
--- a/packages/SettingsProvider/AndroidManifest.xml
+++ b/packages/SettingsProvider/AndroidManifest.xml
@@ -8,6 +8,7 @@
                  android:process="system"
                  android:backupAgent="SettingsBackupAgent"
                  android:killAfterRestore="false"
+                 android:restoreAnyVersion="true"
                  android:icon="@mipmap/ic_launcher_settings"
                  android:defaultToDeviceProtectedStorage="true"
                  android:directBootAware="true">
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 1be0645..48a3a30 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -28,7 +28,7 @@
     <string name="def_bluetooth_disabled_profiles" translatable="false">0</string>
     <bool name="def_auto_time">true</bool>
     <bool name="def_auto_time_zone">true</bool>
-    <bool name="def_accelerometer_rotation">true</bool>
+    <bool name="def_accelerometer_rotation">false</bool>
     <!-- Default screen brightness, from 0 to 255.  102 is 40%. -->
     <integer name="def_screen_brightness">102</integer>
     <bool name="def_screen_brightness_automatic_mode">false</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index ae88227..c7ba4d6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -31,10 +31,12 @@
 import android.net.Uri;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.BackupUtils;
 import android.util.Log;
 
@@ -50,6 +52,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -147,6 +150,13 @@
     // stored in the full-backup tarfile as well, so should not be changed.
     private static final String STAGE_FILE = "flattened-data";
 
+    // List of keys that support restore to lower version of the SDK, introduced in Android P
+    private static final ArraySet<String> RESTORE_FROM_HIGHER_SDK_INT_SUPPORTED_KEYS =
+            new ArraySet<String>(Arrays.asList(new String[] {
+                KEY_NETWORK_POLICIES,
+                KEY_WIFI_NEW_CONFIG,
+            }));
+
     private SettingsHelper mSettingsHelper;
 
     private WifiManager mWifiManager;
@@ -209,6 +219,10 @@
     public void onRestore(BackupDataInput data, int appVersionCode,
             ParcelFileDescriptor newState) throws IOException {
 
+        if (DEBUG) {
+            Log.d(TAG, "onRestore(): appVersionCode: " + appVersionCode
+                    + "; Build.VERSION.SDK_INT: " + Build.VERSION.SDK_INT);
+        }
         // versionCode of com.android.providers.settings corresponds to SDK_INT
         mRestoredFromSdkInt = appVersionCode;
 
@@ -221,6 +235,15 @@
         while (data.readNextHeader()) {
             final String key = data.getKey();
             final int size = data.getDataSize();
+
+            // bail out of restoring from higher SDK_INT version for unsupported keys
+            if (appVersionCode > Build.VERSION.SDK_INT
+                    && !RESTORE_FROM_HIGHER_SDK_INT_SUPPORTED_KEYS.contains(key)) {
+                Log.w(TAG, "Not restoring unrecognized key '"
+                        + key + "' from future version " + appVersionCode);
+                continue;
+            }
+
             switch (key) {
                 case KEY_SYSTEM :
                     restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1167d69..175cff6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1584,6 +1584,11 @@
                 restriction = UserManager.DISALLOW_SAFE_BOOT;
                 break;
 
+            case Settings.Global.AIRPLANE_MODE_ON:
+                if ("0".equals(value)) return false;
+                restriction = UserManager.DISALLOW_AIRPLANE_MODE;
+                break;
+
             default:
                 if (setting != null && setting.startsWith(Settings.Global.DATA_ROAMING)) {
                     if ("0".equals(value)) return false;
@@ -2940,7 +2945,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 150;
+            private static final int SETTINGS_VERSION = 151;
 
             private final int mUserId;
 
@@ -3533,6 +3538,18 @@
                     currentVersion = 150;
                 }
 
+                if (currentVersion == 150) {
+                    // Version 151: Reset rotate locked setting for upgrading users
+                    final SettingsState systemSettings = getSystemSettingsLocked(userId);
+                    systemSettings.insertSettingLocked(
+                            Settings.System.ACCELEROMETER_ROTATION,
+                            getContext().getResources().getBoolean(
+                                    R.bool.def_accelerometer_rotation) ? "1" : "0",
+                            null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+
+                    currentVersion = 151;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
index 56a3ee3..e25930c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
@@ -50,5 +50,7 @@
         }
 
         void setDarkIntensity(float intensity);
+
+        void setDelayTouchFeedback(boolean shouldDelay);
     }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
index 674ed5a..6131acc 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
@@ -14,6 +14,7 @@
 
 package com.android.systemui.plugins.statusbar.phone;
 
+import android.graphics.Canvas;
 import android.view.MotionEvent;
 
 import com.android.systemui.plugins.Plugin;
@@ -35,6 +36,12 @@
 
         public void setBarState(boolean vertical, boolean isRtl);
 
+        public void onDraw(Canvas canvas);
+
+        public void onDarkIntensityChange(float intensity);
+
+        public void onLayout(boolean changed, int left, int top, int right, int bottom);
+
         public default void destroy() { }
     }
 
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index c97cfc4..9adb550 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -34,7 +34,6 @@
         android:orientation="vertical">
         <RelativeLayout
             android:id="@+id/keyguard_clock_container"
-            android:animateLayoutChanges="true"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="center_horizontal|top">
@@ -51,16 +50,6 @@
                 android:format12Hour="@string/keyguard_widget_12_hours_format"
                 android:format24Hour="@string/keyguard_widget_24_hours_format"
                 android:layout_marginBottom="@dimen/bottom_text_spacing_digital" />
-            <com.android.systemui.ChargingView
-                android:id="@+id/battery_doze"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignTop="@id/clock_view"
-                android:layout_alignBottom="@id/clock_view"
-                android:layout_toEndOf="@id/clock_view"
-                android:visibility="invisible"
-                android:src="@drawable/ic_aod_charging_24dp"
-                android:contentDescription="@string/accessibility_ambient_display_charging" />
             <View
                 android:id="@+id/clock_separator"
                 android:layout_width="16dp"
diff --git a/packages/SystemUI/res/drawable/ic_face_unlock.xml b/packages/SystemUI/res/drawable/ic_face_unlock.xml
new file mode 100644
index 0000000..29c2275
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_face_unlock.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportHeight="24.0"
+        android:viewportWidth="24.0">
+    <path android:fillColor="?attr/wallpaperTextColor"
+          android:strokeColor="?attr/wallpaperTextColor"
+          android:strokeWidth="1"
+          android:pathData="M9,11.75C8.31,11.75 7.75,12.31 7.75,13C7.75,13.69 8.31,14.25 9,14.25C9.69,14.25 10.25,13.69 10.25,13C10.25,12.31 9.69,11.75 9,11.75ZM15,11.75C14.31,11.75 13.75,12.31 13.75,13C13.75,13.69 14.31,14.25 15,14.25C15.69,14.25 16.25,13.69 16.25,13C16.25,12.31 15.69,11.75 15,11.75ZM12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2ZM12,20C7.59,20 4,16.41 4,12C4,11.71 4.02,11.42 4.05,11.14C6.41,10.09 8.28,8.16 9.26,5.77C11.07,8.33 14.05,10 17.42,10C18.2,10 18.95,9.91 19.67,9.74C19.88,10.45 20,11.21 20,12C20,16.41 16.41,20 12,20Z"
+    />
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_background_primary.xml b/packages/SystemUI/res/drawable/qs_background_primary.xml
index 03bba53..dd74cadd 100644
--- a/packages/SystemUI/res/drawable/qs_background_primary.xml
+++ b/packages/SystemUI/res/drawable/qs_background_primary.xml
@@ -16,5 +16,6 @@
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape>
         <solid android:color="@color/qs_background_dark"/>
+        <corners android:radius="?android:attr/dialogCornerRadius" />
     </shape>
 </inset>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 2fd4df4..d0d379c 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -40,6 +40,7 @@
             android:gravity="center_horizontal"
             android:textStyle="italic"
             android:textColor="?attr/wallpaperTextColorSecondary"
+            android:textSize="16sp"
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:visibility="gone" />
 
@@ -50,6 +51,7 @@
             android:gravity="center_horizontal"
             android:textStyle="italic"
             android:textColor="?attr/wallpaperTextColorSecondary"
+            android:textSize="16sp"
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:accessibilityLiveRegion="polite" />
 
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index bef0830..c5e5ee1 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -43,6 +43,8 @@
             android:layout_width="@dimen/qs_panel_width"
             android:layout_height="match_parent"
             android:layout_gravity="@integer/notification_panel_layout_gravity"
+            android:layout_marginStart="@dimen/notification_side_paddings"
+            android:layout_marginEnd="@dimen/notification_side_paddings"
             android:clipToPadding="false"
             android:clipChildren="false"
             systemui:viewType="com.android.systemui.plugins.qs.QS" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index f244d88..f5be337 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -143,6 +143,9 @@
 
     <color name="remote_input_accent">#eeeeee</color>
 
+    <color name="quick_step_track_background_dark">#61000000</color>
+    <color name="quick_step_track_background_light">#4DFFFFFF</color>
+
     <!-- Keyboard shortcuts colors -->
     <color name="ksh_application_group_color">#fff44336</color>
     <color name="ksh_keyword_color">#d9000000</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a510c4a..39ed08e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -579,6 +579,7 @@
     <dimen name="keyguard_affordance_icon_width">24dp</dimen>
 
     <dimen name="keyguard_indication_margin_bottom">65dp</dimen>
+    <dimen name="keyguard_indication_margin_bottom_ambient">30dp</dimen>
 
     <!-- The text size for battery level -->
     <dimen name="battery_level_text_size">12sp</dimen>
@@ -870,6 +871,8 @@
     <dimen name="rounded_corner_radius">0dp</dimen>
     <dimen name="rounded_corner_content_padding">0dp</dimen>
     <dimen name="nav_content_padding">0dp</dimen>
+    <dimen name="nav_quick_scrub_track_edge_padding">32dp</dimen>
+    <dimen name="nav_quick_scrub_track_thickness">2dp</dimen>
 
     <!-- Intended corner radius when drawing the mobile signal -->
     <dimen name="stat_sys_mobile_signal_corner_radius">0.75dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6ff239e..dde4dcf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -61,11 +61,26 @@
     <!-- When the battery is low, this is displayed to the user in a dialog.  The title of the low battery alert.  [CHAR LIMIT=NONE]-->
     <string name="battery_low_title">Battery is low</string>
 
+    <!-- When the battery is low and hybrid notifications are enabled, this is displayed to the user in a dialog.
+         The title of the low battery alert.  [CHAR LIMIT=NONE]-->
+    <string name="battery_low_title_hybrid">Battery is low. Turn on Battery Saver</string>
+
     <!-- A message that appears when the battery level is getting low in a dialog.  This is
-        appened to the subtitle of the low battery alert.  "percentage" is the percentage of battery
+        appended to the subtitle of the low battery alert.  "percentage" is the percentage of battery
         remaining [CHAR LIMIT=none]-->
     <string name="battery_low_percent_format"><xliff:g id="percentage">%s</xliff:g> remaining</string>
 
+    <!-- A message that appears when the battery remaining estimate is low in a dialog.  This is
+    appended to the subtitle of the low battery alert.  "percentage" is the percentage of battery
+    remaining. "time" is the amount of time remaining before the phone runs out of battery [CHAR LIMIT=none]-->
+    <string name="battery_low_percent_format_hybrid"><xliff:g id="percentage">%s</xliff:g> remaining, about <xliff:g id="time">%s</xliff:g> left based on your usage</string>
+
+    <!-- A message that appears when the battery remaining estimate is low in a dialog and insufficient
+    data was present to say it is customized to the user. This is appended to the subtitle of the
+    low battery alert.  "percentage" is the percentage of battery remaining. "time" is the amount
+     of time remaining before the phone runs out of battery [CHAR LIMIT=none]-->
+    <string name="battery_low_percent_format_hybrid_short"><xliff:g id="percentage">%s</xliff:g> remaining, about <xliff:g id="time">%s</xliff:g> left</string>
+
     <!-- Same as battery_low_percent_format, with a notice about battery saver if on. [CHAR LIMIT=none]-->
     <string name="battery_low_percent_format_saver_started"><xliff:g id="percentage">%s</xliff:g> remaining. Battery Saver is on.</string>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 173a90a..64fa9c6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -22,4 +22,8 @@
 oneway interface IOverviewProxy {
     void onBind(in ISystemUiProxy sysUiProxy);
     void onMotionEvent(in MotionEvent event);
+    void onQuickSwitch();
+    void onQuickScrubStart();
+    void onQuickScrubEnd();
+    void onQuickScrubProgress(float progress);
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 8135c61..d80a336 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -151,6 +151,7 @@
 
         mClickActions.clear();
         final int subItemsCount = subItems.size();
+        final int blendedColor = getTextColor();
 
         for (int i = 0; i < subItemsCount; i++) {
             SliceItem item = subItems.get(i);
@@ -159,7 +160,7 @@
             KeyguardSliceButton button = mRow.findViewWithTag(itemTag);
             if (button == null) {
                 button = new KeyguardSliceButton(mContext);
-                button.setTextColor(mTextColor);
+                button.setTextColor(blendedColor);
                 button.setTag(itemTag);
             } else {
                 mRow.removeView(button);
@@ -258,7 +259,7 @@
     }
 
     private void updateTextColors() {
-        final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+        final int blendedColor = getTextColor();
         mTitle.setTextColor(blendedColor);
         int childCount = mRow.getChildCount();
         for (int i = 0; i < childCount; i++) {
@@ -322,6 +323,10 @@
         }
     }
 
+    public int getTextColor() {
+        return ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+    }
+
     /**
      * Representation of an item that appears under the clock on main keyguard message.
      * Shows optional separator.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 4b9a874..2873afb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -16,7 +16,6 @@
 
 package com.android.keyguard;
 
-import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -40,7 +39,6 @@
 import android.widget.TextView;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.ChargingView;
 
 import com.google.android.collect.Sets;
 
@@ -60,7 +58,6 @@
     private View mClockSeparator;
     private TextView mOwnerInfo;
     private ViewGroup mClockContainer;
-    private ChargingView mBatteryDoze;
     private KeyguardSliceView mKeyguardSlice;
     private Runnable mPendingMarqueeStart;
     private Handler mHandler;
@@ -155,11 +152,9 @@
             mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext));
         }
         mOwnerInfo = findViewById(R.id.owner_info);
-        mBatteryDoze = findViewById(R.id.battery_doze);
         mKeyguardSlice = findViewById(R.id.keyguard_status_area);
         mClockSeparator = findViewById(R.id.clock_separator);
-        mVisibleInDoze = Sets.newArraySet(mBatteryDoze, mClockView, mKeyguardSlice,
-                mClockSeparator);
+        mVisibleInDoze = Sets.newArraySet(mClockView, mKeyguardSlice, mClockSeparator);
         mTextColor = mClockView.getCurrentTextColor();
 
         mKeyguardSlice.setListener(this::onSliceContentChanged);
@@ -184,10 +179,6 @@
         mClockView.setTranslationY(translation);
         mClockView.setScaleX(clockScale);
         mClockView.setScaleY(clockScale);
-        final float batteryTranslation =
-                -(mClockView.getWidth() - (mClockView.getWidth() * clockScale)) / 2;
-        mBatteryDoze.setTranslationX(batteryTranslation);
-        mBatteryDoze.setTranslationY(translation);
         mClockSeparator.setVisibility(hasHeader ? VISIBLE : GONE);
     }
 
@@ -310,7 +301,7 @@
         }
     }
 
-    public void setDark(float darkAmount) {
+    public void setDarkAmount(float darkAmount) {
         if (mDarkAmount == darkAmount) {
             return;
         }
@@ -331,7 +322,6 @@
 
         final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount);
         updateDozeVisibleViews();
-        mBatteryDoze.setDark(dark);
         mKeyguardSlice.setDark(darkAmount);
         mClockView.setTextColor(blendedTextColor);
         mClockSeparator.setBackgroundColor(blendedTextColor);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index ab2bce8..e58ad05 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1613,11 +1613,10 @@
         }
     }
 
-    private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
+    private boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
         final boolean nowPluggedIn = current.isPluggedIn();
         final boolean wasPluggedIn = old.isPluggedIn();
-        final boolean stateChangedWhilePluggedIn =
-            wasPluggedIn == true && nowPluggedIn == true
+        final boolean stateChangedWhilePluggedIn = wasPluggedIn && nowPluggedIn
             && (old.status != current.status);
 
         // change in plug state is always interesting
@@ -1630,6 +1629,11 @@
             return true;
         }
 
+        // change in battery level while keyguard visible
+        if (mKeyguardIsVisible && old.level != current.level) {
+            return true;
+        }
+
         // change where battery needs charging
         if (!nowPluggedIn && current.isBatteryLow() && current.level != old.level) {
             return true;
diff --git a/packages/SystemUI/src/com/android/systemui/ChargingView.java b/packages/SystemUI/src/com/android/systemui/ChargingView.java
deleted file mode 100644
index 33f8b06..0000000
--- a/packages/SystemUI/src/com/android/systemui/ChargingView.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.os.UserHandle;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-
-import com.android.internal.hardware.AmbientDisplayConfiguration;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-
-/**
- * A view that only shows its drawable while the phone is charging.
- *
- * Also reloads its drawable upon density changes.
- */
-public class ChargingView extends ImageView implements
-        BatteryController.BatteryStateChangeCallback,
-        ConfigurationController.ConfigurationListener {
-
-    private static final long CHARGING_INDICATION_DELAY_MS = 1000;
-
-    private final AmbientDisplayConfiguration mConfig;
-    private final Runnable mClearSuppressCharging = this::clearSuppressCharging;
-    private BatteryController mBatteryController;
-    private int mImageResource;
-    private boolean mCharging;
-    private boolean mDark;
-    private boolean mSuppressCharging;
-
-
-    private void clearSuppressCharging() {
-        mSuppressCharging = false;
-        removeCallbacks(mClearSuppressCharging);
-        updateVisibility();
-    }
-
-    public ChargingView(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-
-        mConfig = new AmbientDisplayConfiguration(context);
-
-        TypedArray a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.src});
-        int srcResId = a.getResourceId(0, 0);
-
-        if (srcResId != 0) {
-            mImageResource = srcResId;
-        }
-
-        a.recycle();
-
-        updateVisibility();
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mBatteryController = Dependency.get(BatteryController.class);
-        mBatteryController.addCallback(this);
-        Dependency.get(ConfigurationController.class).addCallback(this);
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mBatteryController.removeCallback(this);
-        Dependency.get(ConfigurationController.class).removeCallback(this);
-        removeCallbacks(mClearSuppressCharging);
-    }
-
-    @Override
-    public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
-        boolean startCharging = charging && !mCharging;
-        if (startCharging && deviceWillWakeUpWhenPluggedIn() && mDark) {
-            // We're about to wake up, and thus don't want to show the indicator just for it to be
-            // hidden again.
-            clearSuppressCharging();
-            mSuppressCharging = true;
-            postDelayed(mClearSuppressCharging, CHARGING_INDICATION_DELAY_MS);
-        }
-        mCharging = charging;
-        updateVisibility();
-    }
-
-    private boolean deviceWillWakeUpWhenPluggedIn() {
-        boolean plugTurnsOnScreen = getResources().getBoolean(
-                com.android.internal.R.bool.config_unplugTurnsOnScreen);
-        boolean aod = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
-        return !aod && plugTurnsOnScreen;
-    }
-
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        setImageResource(mImageResource);
-    }
-
-    public void setDark(boolean dark) {
-        mDark = dark;
-        if (!dark) {
-            clearSuppressCharging();
-        }
-        updateVisibility();
-    }
-
-    private void updateVisibility() {
-        setVisibility(mCharging && !mSuppressCharging && mDark ? VISIBLE : INVISIBLE);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index e7e70af..7403ddc 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -40,6 +40,8 @@
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.PluginManagerImpl;
 import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.power.EnhancedEstimatesImpl;
 import com.android.systemui.power.PowerNotificationWarnings;
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -310,6 +312,8 @@
 
         mProviders.put(OverviewProxyService.class, () -> new OverviewProxyService(mContext));
 
+        mProviders.put(EnhancedEstimates.class, () -> new EnhancedEstimatesImpl());
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
index f41425a..5d2e4d0 100644
--- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
+++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
@@ -16,20 +16,14 @@
 
 package com.android.systemui;
 
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
 import android.content.Context;
-import android.database.ContentObserver;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.PorterDuff;
-import android.graphics.Region;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
 import android.view.DisplayCutout;
 import android.view.Gravity;
 import android.view.View;
@@ -41,9 +35,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 
-import java.util.Collections;
-import java.util.List;
-
 /**
  * Emulates a display cutout by drawing its shape in an overlay as supplied by
  * {@link DisplayCutout}.
@@ -101,7 +92,7 @@
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
                 | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
-        lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         lp.setTitle("EmulatedDisplayCutout");
         lp.gravity = Gravity.TOP;
         return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
index 6f7a270..c960fa1 100644
--- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
+++ b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui;
 
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
 import static com.android.systemui.tuner.TunablePadding.FLAG_START;
 import static com.android.systemui.tuner.TunablePadding.FLAG_END;
 
@@ -163,7 +165,7 @@
                 | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
         lp.setTitle("RoundedOverlay");
         lp.gravity = Gravity.TOP;
-        lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         return lp;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index bfe07a9..0486a9d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -373,7 +373,7 @@
             if (menuState == MENU_STATE_FULL) {
                 mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
             } else {
-                mMenuContainerAnimator.playTogether(settingsAnim, dismissAnim);
+                mMenuContainerAnimator.playTogether(dismissAnim);
             }
             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
             mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
new file mode 100644
index 0000000..8f41a60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
@@ -0,0 +1,8 @@
+package com.android.systemui.power;
+
+public interface EnhancedEstimates {
+
+    boolean isHybridNotificationEnabled();
+
+    Estimate getEstimate();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
new file mode 100644
index 0000000..d447542
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
@@ -0,0 +1,16 @@
+package com.android.systemui.power;
+
+import android.util.Log;
+
+public class EnhancedEstimatesImpl implements EnhancedEstimates {
+
+    @Override
+    public boolean isHybridNotificationEnabled() {
+        return false;
+    }
+
+    @Override
+    public Estimate getEstimate() {
+        return null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.java b/packages/SystemUI/src/com/android/systemui/power/Estimate.java
new file mode 100644
index 0000000..12a8f0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/Estimate.java
@@ -0,0 +1,11 @@
+package com.android.systemui.power;
+
+public class Estimate {
+    public final long estimateMillis;
+    public final boolean isBasedOnUsage;
+
+    public Estimate(long estimateMillis, boolean isBasedOnUsage) {
+        this.estimateMillis = estimateMillis;
+        this.isBasedOnUsage = isBasedOnUsage;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c29b362..736286f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -17,40 +17,40 @@
 package com.android.systemui.power;
 
 import android.app.Notification;
-import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
 import android.content.DialogInterface.OnDismissListener;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.icu.text.MeasureFormat;
+import android.icu.text.MeasureFormat.FormatWidth;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
 import android.media.AudioAttributes;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
-import android.os.SystemClock;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.support.annotation.VisibleForTesting;
+import android.text.format.DateUtils;
 import android.util.Slog;
 
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
-import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.NotificationChannels;
 
 import java.io.PrintWriter;
 import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
 
 public class PowerNotificationWarnings implements PowerUI.WarningsUI {
     private static final String TAG = PowerUI.TAG + ".Notification";
@@ -96,8 +96,9 @@
     private long mScreenOffTime;
     private int mShowing;
 
-    private long mBucketDroppedNegativeTimeMs;
+    private long mWarningTriggerTimeMs;
 
+    private Estimate mEstimate;
     private boolean mWarning;
     private boolean mPlaySound;
     private boolean mInvalidCharger;
@@ -130,14 +131,22 @@
     public void update(int batteryLevel, int bucket, long screenOffTime) {
         mBatteryLevel = batteryLevel;
         if (bucket >= 0) {
-            mBucketDroppedNegativeTimeMs = 0;
+            mWarningTriggerTimeMs = 0;
         } else if (bucket < mBucket) {
-            mBucketDroppedNegativeTimeMs = System.currentTimeMillis();
+            mWarningTriggerTimeMs = System.currentTimeMillis();
         }
         mBucket = bucket;
         mScreenOffTime = screenOffTime;
     }
 
+    @Override
+    public void updateEstimate(Estimate estimate) {
+        mEstimate = estimate;
+        if (estimate.estimateMillis <= PowerUI.THREE_HOURS_IN_MILLIS) {
+            mWarningTriggerTimeMs = System.currentTimeMillis();
+        }
+    }
+
     private void updateNotification() {
         if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound="
                 + mPlaySound + " mInvalidCharger=" + mInvalidCharger);
@@ -171,25 +180,43 @@
         mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, n, UserHandle.ALL);
     }
 
-    private void showWarningNotification() {
-        final int textRes = R.string.battery_low_percent_format;
+    protected void showWarningNotification() {
         final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0);
 
+        // get standard notification copy
+        String title = mContext.getString(R.string.battery_low_title);
+        String contentText = mContext.getString(R.string.battery_low_percent_format, percentage);
+
+        // override notification copy if hybrid notification enabled
+        if (mEstimate != null) {
+            title = mContext.getString(R.string.battery_low_title_hybrid);
+            contentText = mContext.getString(
+                    mEstimate.isBasedOnUsage
+                            ? R.string.battery_low_percent_format_hybrid
+                            : R.string.battery_low_percent_format_hybrid_short,
+                    percentage,
+                    getTimeRemainingFormatted());
+        }
+
         final Notification.Builder nb =
                 new Notification.Builder(mContext, NotificationChannels.BATTERY)
                         .setSmallIcon(R.drawable.ic_power_low)
                         // Bump the notification when the bucket dropped.
-                        .setWhen(mBucketDroppedNegativeTimeMs)
+                        .setWhen(mWarningTriggerTimeMs)
                         .setShowWhen(false)
-                        .setContentTitle(mContext.getString(R.string.battery_low_title))
-                        .setContentText(mContext.getString(textRes, percentage))
+                        .setContentTitle(title)
+                        .setContentText(contentText)
                         .setOnlyAlertOnce(true)
                         .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING))
-                        .setVisibility(Notification.VISIBILITY_PUBLIC)
-                        .setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
+                        .setVisibility(Notification.VISIBILITY_PUBLIC);
         if (hasBatterySettings()) {
             nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
         }
+        // Make the notification red if the percentage goes below a certain amount or the time
+        // remaining estimate is disabled
+        if (mEstimate == null || mBucket < 0) {
+            nb.setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
+        }
         nb.addAction(0,
                 mContext.getString(R.string.battery_saver_start_action),
                 pendingBroadcast(ACTION_START_SAVER));
@@ -201,6 +228,23 @@
         mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL);
     }
 
+    @VisibleForTesting
+    String getTimeRemainingFormatted() {
+        final Locale currentLocale = mContext.getResources().getConfiguration().getLocales().get(0);
+        MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.NARROW);
+
+        final long remainder = mEstimate.estimateMillis % DateUtils.HOUR_IN_MILLIS;
+        final long hours = TimeUnit.MILLISECONDS.toHours(
+                mEstimate.estimateMillis - remainder);
+        // round down to the nearest 15 min for now to not appear overly precise
+        final long minutes = TimeUnit.MILLISECONDS.toMinutes(
+                remainder - (remainder % TimeUnit.MINUTES.toMillis(15)));
+        final Measure hoursMeasure = new Measure(hours, MeasureUnit.HOUR);
+        final Measure minutesMeasure = new Measure(minutes, MeasureUnit.MINUTE);
+
+        return frmt.formatMeasures(hoursMeasure, minutesMeasure);
+    }
+
     private PendingIntent pendingBroadcast(String action) {
         return PendingIntent.getBroadcastAsUser(mContext,
                 0, new Intent(action), 0, UserHandle.CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index a351c09..c5aab60 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -52,6 +52,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
 
 public class PowerUI extends SystemUI {
     static final String TAG = "PowerUI";
@@ -59,6 +60,7 @@
     private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
     private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS;
     private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer
+    static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3;
 
     private final Handler mHandler = new Handler();
     private final Receiver mReceiver = new Receiver();
@@ -68,9 +70,11 @@
     private WarningsUI mWarnings;
     private final Configuration mLastConfiguration = new Configuration();
     private int mBatteryLevel = 100;
+    private long mTimeRemaining = Long.MAX_VALUE;
     private int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
     private int mPlugType = 0;
     private int mInvalidCharger = 0;
+    private EnhancedEstimates mEnhancedEstimates;
 
     private int mLowBatteryAlertCloseLevel;
     private final int[] mLowBatteryReminderLevels = new int[2];
@@ -83,8 +87,8 @@
     private long mNextLogTime;
     private IThermalService mThermalService;
 
-    // We create a method reference here so that we are guaranteed that we can remove a callback
     // by using the same instance (method references are not guaranteed to be the same object
+    // We create a method reference here so that we are guaranteed that we can remove a callback
     // each time they are created).
     private final Runnable mUpdateTempCallback = this::updateTemperatureWarning;
 
@@ -94,6 +98,7 @@
                 mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE);
         mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
         mWarnings = Dependency.get(WarningsUI.class);
+        mEnhancedEstimates = Dependency.get(EnhancedEstimates.class);
         mLastConfiguration.setTo(mContext.getResources().getConfiguration());
 
         ContentObserver obs = new ContentObserver(mHandler) {
@@ -236,21 +241,9 @@
                     return;
                 }
 
-                boolean isPowerSaver = mPowerManager.isPowerSaveMode();
-                if (!plugged
-                        && !isPowerSaver
-                        && (bucket < oldBucket || oldPlugged)
-                        && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
-                        && bucket < 0) {
+                // Show the correct version of low battery warning if needed
+                maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket);
 
-                    // only play SFX when the dialog comes up or the bucket changes
-                    final boolean playSound = bucket != oldBucket || oldPlugged;
-                    mWarnings.showLowBatteryWarning(playSound);
-                } else if (isPowerSaver || plugged || (bucket > oldBucket && bucket > 0)) {
-                    mWarnings.dismissLowBatteryWarning();
-                } else {
-                    mWarnings.updateLowBatteryWarning();
-                }
             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                 mScreenOffTime = SystemClock.elapsedRealtime();
             } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
@@ -261,7 +254,65 @@
                 Slog.w(TAG, "unknown intent: " + intent);
             }
         }
-    };
+    }
+
+    protected void maybeShowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
+        int bucket) {
+        boolean isPowerSaver = mPowerManager.isPowerSaveMode();
+        // only play SFX when the dialog comes up or the bucket changes
+        final boolean playSound = bucket != oldBucket || oldPlugged;
+        long oldTimeRemaining = mTimeRemaining;
+        if (mEnhancedEstimates.isHybridNotificationEnabled()) {
+            final Estimate estimate = mEnhancedEstimates.getEstimate();
+            // Turbo is not always booted once SysUI is running so we have ot make sure we actually
+            // get data back
+            if (estimate != null) {
+                mTimeRemaining = estimate.estimateMillis;
+                mWarnings.updateEstimate(estimate);
+            }
+        }
+
+        if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket, oldTimeRemaining,
+                mTimeRemaining,
+                isPowerSaver, mBatteryStatus)) {
+            mWarnings.showLowBatteryWarning(playSound);
+        } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining,
+                isPowerSaver)) {
+            mWarnings.dismissLowBatteryWarning();
+        } else {
+            mWarnings.updateLowBatteryWarning();
+        }
+    }
+
+    @VisibleForTesting
+    boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
+            int bucket, long oldTimeRemaining, long timeRemaining,
+            boolean isPowerSaver, int mBatteryStatus) {
+        return !plugged
+                && !isPowerSaver
+                && (((bucket < oldBucket || oldPlugged) && bucket < 0)
+                        || (mEnhancedEstimates.isHybridNotificationEnabled()
+                                && timeRemaining < THREE_HOURS_IN_MILLIS
+                                && isHourLess(oldTimeRemaining, timeRemaining)))
+                && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
+    }
+
+    private boolean isHourLess(long oldTimeRemaining, long timeRemaining) {
+        final long dif = oldTimeRemaining - timeRemaining;
+        return dif >= TimeUnit.HOURS.toMillis(1);
+    }
+
+    @VisibleForTesting
+    boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
+            long timeRemaining, boolean isPowerSaver) {
+        final boolean hybridWouldDismiss = mEnhancedEstimates.isHybridNotificationEnabled()
+                && timeRemaining > THREE_HOURS_IN_MILLIS;
+        final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0);
+        return isPowerSaver
+                || plugged
+                || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled()
+                        || hybridWouldDismiss));
+    }
 
     private void initTemperatureWarning() {
         ContentResolver resolver = mContext.getContentResolver();
@@ -433,6 +484,7 @@
 
     public interface WarningsUI {
         void update(int batteryLevel, int bucket, long screenOffTime);
+        void updateEstimate(Estimate estimate);
         void dismissLowBatteryWarning();
         void showLowBatteryWarning(boolean playSound);
         void dismissInvalidChargerWarning();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 33b5268..7f0acc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -17,13 +17,18 @@
 package com.android.systemui.qs;
 
 import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.widget.FrameLayout;
 
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.statusbar.ExpandableOutlineView;
 
 /**
  * Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader}
@@ -31,6 +36,7 @@
 public class QSContainerImpl extends FrameLayout {
 
     private final Point mSizePoint = new Point();
+    private final Path mClipPath = new Path();
 
     private int mHeightOverride = -1;
     protected View mQSPanel;
@@ -40,6 +46,7 @@
     private QSCustomizer mQSCustomizer;
     private View mQSFooter;
     private float mFullElevation;
+    private float mRadius;
 
     public QSContainerImpl(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -54,6 +61,8 @@
         mQSCustomizer = findViewById(R.id.qs_customize);
         mQSFooter = findViewById(R.id.qs_footer);
         mFullElevation = mQSPanel.getElevation();
+        mRadius = getResources().getDimensionPixelSize(
+                Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
 
         setClickable(true);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -93,6 +102,18 @@
         updateExpansion();
     }
 
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        boolean ret;
+        canvas.save();
+        if (child != mQSCustomizer) {
+            canvas.clipPath(mClipPath);
+        }
+        ret = super.drawChild(canvas, child, drawingTime);
+        canvas.restore();
+        return ret;
+    }
+
     /**
      * Overrides the height of this view (post-layout), so that the content is clipped to that
      * height and the background is set to that height.
@@ -110,6 +131,10 @@
         mQSDetail.setBottom(getTop() + height);
         // Pin QS Footer to the bottom of the panel.
         mQSFooter.setTranslationY(height - mQSFooter.getHeight());
+
+        ExpandableOutlineView.getRoundedRectPath(0, 0, getWidth(), height, mRadius,
+                mRadius,
+                mClipPath);
     }
 
     protected int calculateContainerHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index c249e37..0f83078 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -103,7 +103,7 @@
 
             if (iv instanceof SlashImageView) {
                 ((SlashImageView) iv).setAnimationEnabled(shouldAnimate);
-                ((SlashImageView) iv).setState(state.slash, d);
+                ((SlashImageView) iv).setState(null, d);
             } else {
                 iv.setImageDrawable(d);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 4d0e60d..acd327b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -13,7 +13,12 @@
  */
 package com.android.systemui.qs.tileimpl;
 
+import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
@@ -22,16 +27,21 @@
 import android.os.Message;
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
 import android.widget.Switch;
 
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.*;
+import com.android.systemui.plugins.qs.QSIconView;
+import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 
 public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
@@ -47,6 +57,12 @@
     private boolean mCollapsedView;
     private boolean mClicked;
 
+    private final ImageView mBg;
+    private final int mColorActive;
+    private final int mColorInactive;
+    private final int mColorDisabled;
+    private int mCircleColor;
+
     public QSTileBaseView(Context context, QSIconView icon) {
         this(context, icon, false);
     }
@@ -60,6 +76,10 @@
         mIconFrame.setForegroundGravity(Gravity.CENTER);
         int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
         addView(mIconFrame, new LayoutParams(size, size));
+        mBg = new ImageView(getContext());
+        mBg.setScaleType(ScaleType.FIT_CENTER);
+        mBg.setImageResource(R.drawable.ic_qs_circle);
+        mIconFrame.addView(mBg);
         mIcon = icon;
         FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -73,6 +93,11 @@
         setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
         setBackground(mTileBackground);
 
+        mColorActive = Utils.getColorAttr(context, android.R.attr.colorAccent);
+        mColorDisabled = Utils.getDisabled(context,
+                Utils.getColorAttr(context, android.R.attr.textColorTertiary));
+        mColorInactive = Utils.getColorAttr(context, android.R.attr.textColorSecondary);
+
         setPadding(0, 0, 0, 0);
         setClipChildren(false);
         setClipToPadding(false);
@@ -80,6 +105,10 @@
         setFocusable(true);
     }
 
+    public View getBgCicle() {
+        return mBg;
+    }
+
     protected Drawable newTileBackground() {
         final int[] attrs = new int[]{android.R.attr.selectableItemBackgroundBorderless};
         final TypedArray ta = getContext().obtainStyledAttributes(attrs);
@@ -150,6 +179,20 @@
     }
 
     protected void handleStateChanged(QSTile.State state) {
+        int circleColor = getCircleColor(state.state);
+        if (circleColor != mCircleColor) {
+            if (mBg.isShown()) {
+                ValueAnimator animator = ValueAnimator.ofArgb(mCircleColor, circleColor)
+                        .setDuration(QS_ANIM_LENGTH);
+                animator.addUpdateListener(animation -> mBg.setImageTintList(ColorStateList.valueOf(
+                        (Integer) animation.getAnimatedValue())));
+                animator.start();
+            } else {
+                QSIconViewImpl.setTint(mBg, circleColor);
+            }
+            mCircleColor = circleColor;
+        }
+
         setClickable(state.state != Tile.STATE_UNAVAILABLE);
         mIcon.setIcon(state);
         setContentDescription(state.contentDescription);
@@ -163,6 +206,19 @@
         }
     }
 
+    private int getCircleColor(int state) {
+        switch (state) {
+            case Tile.STATE_ACTIVE:
+                return mColorActive;
+            case Tile.STATE_INACTIVE:
+            case Tile.STATE_UNAVAILABLE:
+                return mColorDisabled;
+            default:
+                Log.e(TAG, "Invalid state " + state);
+                return 0;
+        }
+    }
+
     @Override
     public void setClickable(boolean clickable) {
         super.setClickable(clickable);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 576a447..72592829 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -373,11 +373,11 @@
         switch (state) {
             case Tile.STATE_UNAVAILABLE:
                 return Utils.getDisabled(context,
-                        Utils.getColorAttr(context, android.R.attr.colorForeground));
+                        Utils.getColorAttr(context, android.R.attr.textColorSecondary));
             case Tile.STATE_INACTIVE:
-                return Utils.getColorAttr(context, android.R.attr.textColorHint);
+                return Utils.getColorAttr(context, android.R.attr.textColorSecondary);
             case Tile.STATE_ACTIVE:
-                return Utils.getColorAttr(context, android.R.attr.textColorPrimary);
+                return Utils.getColorAttr(context, android.R.attr.colorPrimary);
             default:
                 Log.e("QSTile", "Invalid state " + state);
                 return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 85400a1..43047ed6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -52,6 +52,8 @@
 import com.android.systemui.util.wakelock.SettableWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
+import java.text.NumberFormat;
+
 /**
  * Controls the indications and error messages shown on the Keyguard
  */
@@ -87,6 +89,7 @@
     private boolean mPowerCharged;
     private int mChargingSpeed;
     private int mChargingWattage;
+    private int mBatteryLevel;
     private String mMessageToShowOnScreenOn;
 
     private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
@@ -285,14 +288,18 @@
             // Walk down a precedence-ordered list of what indication
             // should be shown based on user or device state
             if (mDozing) {
-                // If we're dozing, never show a persistent indication.
+                mTextView.setTextColor(Color.WHITE);
                 if (!TextUtils.isEmpty(mTransientIndication)) {
                     // When dozing we ignore any text color and use white instead, because
                     // colors can be hard to read in low brightness.
-                    mTextView.setTextColor(Color.WHITE);
                     mTextView.switchIndication(mTransientIndication);
+                } else if (mPowerPluggedIn) {
+                    String indication = computePowerIndication();
+                    mTextView.switchIndication(indication);
                 } else {
-                    mTextView.switchIndication(null);
+                    String percentage = NumberFormat.getPercentInstance()
+                            .format(mBatteryLevel / 100f);
+                    mTextView.switchIndication(percentage);
                 }
                 return;
             }
@@ -422,6 +429,7 @@
             mPowerCharged = status.isCharged();
             mChargingWattage = status.maxChargingWattage;
             mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold);
+            mBatteryLevel = status.level;
             updateIndication();
             if (mDozing) {
                 if (!wasPluggedIn && mPowerPluggedIn) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
index 78ee040..9b123cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -14,7 +14,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.graphics.drawable.Drawable;
 import android.view.View;
 
 import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
@@ -39,6 +38,7 @@
     private Integer mAlpha;
     private Float mDarkIntensity;
     private Integer mVisibility = -1;
+    private Boolean mDelayTouchFeedback;
     private KeyButtonDrawable mImageDrawable;
     private View mCurrentView;
     private boolean mVertical;
@@ -71,10 +71,10 @@
         if (mImageDrawable != null) {
             ((ButtonInterface) view).setImageDrawable(mImageDrawable);
         }
-
-        if (view instanceof  ButtonInterface) {
-            ((ButtonInterface) view).setVertical(mVertical);
+        if (mDelayTouchFeedback != null) {
+            ((ButtonInterface) view).setDelayTouchFeedback(mDelayTouchFeedback);
         }
+        ((ButtonInterface) view).setVertical(mVertical);
     }
 
     public int getId() {
@@ -134,6 +134,14 @@
         }
     }
 
+    public void setDelayTouchFeedback(boolean delay) {
+        mDelayTouchFeedback = delay;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay);
+        }
+    }
+
     public void setOnClickListener(View.OnClickListener clickListener) {
         mClickListener = clickListener;
         final int N = mViews.size();
@@ -166,6 +174,14 @@
         }
     }
 
+    public void setClickable(boolean clickable) {
+        abortCurrentGesture();
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setClickable(clickable);
+        }
+    }
+
     public ArrayList<View> getViews() {
         return mViews;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 01b3b44..ca66e98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -51,6 +51,7 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
@@ -166,6 +167,10 @@
     private String mLeftButtonStr;
     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
     private boolean mDozing;
+    private int mIndicationBottomMargin;
+    private int mIndicationBottomMarginAmbient;
+    private float mDarkAmount;
+    private int mBurnInXOffset;
 
     public KeyguardBottomAreaView(Context context) {
         this(context, null);
@@ -235,6 +240,10 @@
         mEnterpriseDisclosure = findViewById(
                 R.id.keyguard_indication_enterprise_disclosure);
         mIndicationText = findViewById(R.id.keyguard_indication_text);
+        mIndicationBottomMargin = getResources().getDimensionPixelSize(
+                R.dimen.keyguard_indication_margin_bottom);
+        mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize(
+                R.dimen.keyguard_indication_margin_bottom_ambient);
         updateCameraVisibility();
         mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
         mUnlockMethodCache.addListener(this);
@@ -303,11 +312,13 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        int indicationBottomMargin = getResources().getDimensionPixelSize(
+        mIndicationBottomMargin = getResources().getDimensionPixelSize(
                 R.dimen.keyguard_indication_margin_bottom);
+        mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize(
+                R.dimen.keyguard_indication_margin_bottom_ambient);
         MarginLayoutParams mlp = (MarginLayoutParams) mIndicationArea.getLayoutParams();
-        if (mlp.bottomMargin != indicationBottomMargin) {
-            mlp.bottomMargin = indicationBottomMargin;
+        if (mlp.bottomMargin != mIndicationBottomMargin) {
+            mlp.bottomMargin = mIndicationBottomMargin;
             mIndicationArea.setLayoutParams(mlp);
         }
 
@@ -543,6 +554,22 @@
         }
     }
 
+    public void setDarkAmount(float darkAmount) {
+        if (darkAmount == mDarkAmount) {
+            return;
+        }
+        mDarkAmount = darkAmount;
+        // Let's randomize the bottom margin every time we wake up to avoid burn-in.
+        if (darkAmount == 0) {
+            mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize(
+                    R.dimen.keyguard_indication_margin_bottom_ambient)
+                    + (int) (Math.random() * mIndicationText.getTextSize());
+        }
+        mIndicationArea.setAlpha(MathUtils.lerp(1f, 0.7f, darkAmount));
+        mIndicationArea.setTranslationY(MathUtils.lerp(0,
+                mIndicationBottomMargin - mIndicationBottomMarginAmbient, darkAmount));
+    }
+
     private static boolean isSuccessfulLaunch(int result) {
         return result == ActivityManager.START_SUCCESS
                 || result == ActivityManager.START_DELIVERED_TO_TOP
@@ -687,11 +714,6 @@
         if (mRightAffordanceView.getVisibility() == View.VISIBLE) {
             startFinishDozeAnimationElement(mRightAffordanceView, delay);
         }
-        mIndicationArea.setAlpha(0f);
-        mIndicationArea.animate()
-                .alpha(1f)
-                .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
-                .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
     }
 
     private void startFinishDozeAnimationElement(View element, long delay) {
@@ -815,6 +837,22 @@
         }
     }
 
+    public void dozeTimeTick() {
+        if (mDarkAmount == 1) {
+            // Move indication every minute to avoid burn-in
+            final int dozeTranslation = mIndicationBottomMargin - mIndicationBottomMarginAmbient;
+            mIndicationArea.setTranslationY(dozeTranslation + (float) Math.random() * 5);
+        }
+    }
+
+    public void setBurnInXOffset(int burnInXOffset) {
+        if (mBurnInXOffset == burnInXOffset) {
+            return;
+        }
+        mBurnInXOffset = burnInXOffset;
+        mIndicationArea.setTranslationX(burnInXOffset);
+    }
+
     private class DefaultLeftButton implements IntentButton {
 
         private IconState mIconState = new IconState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 34486db..264f574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -250,7 +250,7 @@
                 }
                 break;
             case STATE_FACE_UNLOCK:
-                iconRes = R.drawable.ic_account_circle;
+                iconRes = R.drawable.ic_face_unlock;
                 break;
             case STATE_FINGERPRINT:
                 // If screen is off and device asleep, use the draw on animation so the first frame
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 6f636aa..4faa84a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -19,15 +19,14 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
-import android.view.ViewConfiguration;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
@@ -37,6 +36,7 @@
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
 import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.tuner.TunerService;
 
@@ -72,6 +72,7 @@
     private NavigationBarView mNavigationBarView;
     private boolean mIsVertical;
 
+    private final QuickScrubController mQuickScrubController;
     private final int mScrollTouchSlop;
     private final Matrix mTransformGlobalMatrix = new Matrix();
     private final Matrix mTransformLocalMatrix = new Matrix();
@@ -89,6 +90,7 @@
         mContext = context;
         Resources r = context.getResources();
         mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
+        mQuickScrubController = new QuickScrubController(context);
         Dependency.get(TunerService.class).addTunable(this, KEY_DOCK_WINDOW_GESTURE);
     }
 
@@ -101,10 +103,12 @@
         mRecentsComponent = recentsComponent;
         mDivider = divider;
         mNavigationBarView = navigationBarView;
+        mQuickScrubController.setComponents(mNavigationBarView);
     }
 
     public void setBarState(boolean isVertical, boolean isRTL) {
         mIsVertical = isVertical;
+        mQuickScrubController.setBarState(isVertical, isRTL);
     }
 
     private boolean proxyMotionEvents(MotionEvent event) {
@@ -126,7 +130,6 @@
 
     public boolean onInterceptTouchEvent(MotionEvent event) {
         int action = event.getAction();
-        boolean result = false;
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                 mTouchDownX = (int) event.getX();
@@ -137,24 +140,26 @@
                 mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
                 break;
             }
-            case MotionEvent.ACTION_MOVE: {
-                int x = (int) event.getX();
-                int y = (int) event.getY();
-                int xDiff = Math.abs(x - mTouchDownX);
-                int yDiff = Math.abs(y - mTouchDownY);
-                boolean exceededTouchSlop = xDiff > mScrollTouchSlop && xDiff > yDiff
-                        || yDiff > mScrollTouchSlop && yDiff > xDiff;
-                if (exceededTouchSlop) {
-                    result = true;
-                }
-                break;
-            }
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                break;
         }
-        proxyMotionEvents(event);
-        return result || (mDockWindowEnabled && interceptDockWindowEvent(event));
+        if (!mQuickScrubController.onInterceptTouchEvent(event)) {
+            proxyMotionEvents(event);
+            return false;
+        }
+        return (mDockWindowEnabled && interceptDockWindowEvent(event));
+    }
+
+    public void onDraw(Canvas canvas) {
+        if (mOverviewEventSender.getProxy() != null) {
+            mQuickScrubController.onDraw(canvas);
+        }
+    }
+
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        mQuickScrubController.onLayout(changed, left, top, right, bottom);
+    }
+
+    public void onDarkIntensityChange(float intensity) {
+        mQuickScrubController.onDarkIntensityChange(intensity);
     }
 
     private boolean interceptDockWindowEvent(MotionEvent event) {
@@ -294,7 +299,7 @@
     }
 
     public boolean onTouchEvent(MotionEvent event) {
-        boolean result = proxyMotionEvents(event);
+        boolean result = mQuickScrubController.onTouchEvent(event) || proxyMotionEvents(event);
         if (mDockWindowEnabled) {
             result |= handleDockWindowEvent(event);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index bd6421c..b113675 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -138,6 +138,7 @@
         if (mAutoDim) {
             applyLightsOut(false, true);
         }
+        mView.onDarkIntensityChange(darkIntensity);
     }
 
     private final View.OnTouchListener mLightsOutListener = new View.OnTouchListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 006a85b..059ce92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -26,6 +26,7 @@
 import android.app.StatusBarManager;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -625,6 +626,24 @@
         updateRotatedViews();
     }
 
+    public void onDarkIntensityChange(float intensity) {
+        if (mGestureHelper != null) {
+            mGestureHelper.onDarkIntensityChange(intensity);
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        mGestureHelper.onDraw(canvas);
+        super.onDraw(canvas);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        mGestureHelper.onLayout(changed, left, top, right, bottom);
+    }
+
     private void updateRotatedViews() {
         mRotatedViews[Surface.ROTATION_0] =
                 mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 32675d3..0cc7f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -478,6 +478,7 @@
         }
         mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
         mNotificationStackScroller.setDarkShelfOffsetX(mClockPositionResult.clockX);
+        mKeyguardBottomArea.setBurnInXOffset(mClockPositionResult.clockX);
         requestScrollerTopPaddingUpdate(animate);
     }
 
@@ -2608,7 +2609,8 @@
 
     private void setDarkAmount(float amount) {
         mDarkAmount = amount;
-        mKeyguardStatusView.setDark(mDarkAmount);
+        mKeyguardStatusView.setDarkAmount(mDarkAmount);
+        mKeyguardBottomArea.setDarkAmount(mDarkAmount);
         positionClockAndNotifications();
     }
 
@@ -2630,8 +2632,9 @@
         }
     }
 
-    public void refreshTime() {
+    public void dozeTimeTick() {
         mKeyguardStatusView.refreshTime();
+        mKeyguardBottomArea.dozeTimeTick();
         if (mDarkAmount > 0) {
             positionClockAndNotifications();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
new file mode 100644
index 0000000..9f8a7ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.Display;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.support.annotation.DimenRes;
+import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
+import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.utilities.Utilities;
+
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
+
+/**
+ * Class to detect gestures on the navigation bar and implement quick scrub and switch.
+ */
+public class QuickScrubController extends GestureDetector.SimpleOnGestureListener implements
+        GestureHelper {
+
+    private static final String TAG = "QuickScrubController";
+    private static final int QUICK_SWITCH_FLING_VELOCITY = 0;
+    private static final int ANIM_DURATION_MS = 200;
+    private static final long LONG_PRESS_DELAY_MS = 150;
+
+    /**
+     * For quick step, set a damping value to allow the button to stick closer its origin position
+     * when dragging before quick scrub is active.
+     */
+    private static final int SWITCH_STICKINESS = 4;
+
+    private NavigationBarView mNavigationBarView;
+    private GestureDetector mGestureDetector;
+
+    private boolean mDraggingActive;
+    private boolean mQuickScrubActive;
+    private float mDownOffset;
+    private float mTranslation;
+    private int mTouchDownX;
+    private int mTouchDownY;
+    private boolean mDragPositive;
+    private boolean mIsVertical;
+    private boolean mIsRTL;
+    private float mMaxTrackPaintAlpha;
+
+    private final Handler mHandler = new Handler();
+    private final Interpolator mQuickScrubEndInterpolator = new DecelerateInterpolator();
+    private final Rect mTrackRect = new Rect();
+    private final Rect mHomeButtonRect = new Rect();
+    private final Paint mTrackPaint = new Paint();
+    private final int mScrollTouchSlop;
+    private final OverviewProxyService mOverviewEventSender;
+    private final Display mDisplay;
+    private final int mTrackThickness;
+    private final int mTrackPadding;
+    private final ValueAnimator mTrackAnimator;
+    private final ValueAnimator mButtonAnimator;
+    private final AnimatorSet mQuickScrubEndAnimator;
+    private final Context mContext;
+
+    private final AnimatorUpdateListener mTrackAnimatorListener = valueAnimator -> {
+        mTrackPaint.setAlpha(Math.round((float) valueAnimator.getAnimatedValue() * 255));
+        mNavigationBarView.invalidate();
+    };
+
+    private final AnimatorUpdateListener mButtonTranslationListener = animator -> {
+        int pos = (int) animator.getAnimatedValue();
+        if (!mQuickScrubActive) {
+            pos = mDragPositive ? Math.min((int) mTranslation, pos) : Math.max((int) mTranslation, pos);
+        }
+        final View homeView = mNavigationBarView.getHomeButton().getCurrentView();
+        if (mIsVertical) {
+            homeView.setTranslationY(pos);
+        } else {
+            homeView.setTranslationX(pos);
+        }
+    };
+
+    private AnimatorListenerAdapter mQuickScrubEndListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mNavigationBarView.getHomeButton().setClickable(true);
+            mQuickScrubActive = false;
+            mTranslation = 0;
+        }
+    };
+
+    private Runnable mLongPressRunnable = this::startQuickScrub;
+
+    private final GestureDetector.SimpleOnGestureListener mGestureListener =
+        new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) {
+                if (mQuickScrubActive) {
+                    return false;
+                }
+                float velocityX = mIsRTL ? -velX : velX;
+                float absVelY = Math.abs(velY);
+                final boolean isValidFling = velocityX > QUICK_SWITCH_FLING_VELOCITY &&
+                        mIsVertical ? (absVelY > velocityX) : (velocityX > absVelY);
+                if (isValidFling) {
+                    mDraggingActive = false;
+                    mButtonAnimator.setIntValues((int) mTranslation, 0);
+                    mButtonAnimator.start();
+                    mHandler.removeCallbacks(mLongPressRunnable);
+                    try {
+                        final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
+                        overviewProxy.onQuickSwitch();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Failed to send start of quick switch.", e);
+                    }
+                }
+                return true;
+            }
+        };
+
+    public QuickScrubController(Context context) {
+        mContext = context;
+        mScrollTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mDisplay = ((WindowManager) context.getSystemService(
+                Context.WINDOW_SERVICE)).getDefaultDisplay();
+        mOverviewEventSender = Dependency.get(OverviewProxyService.class);
+        mGestureDetector = new GestureDetector(mContext, mGestureListener);
+        mTrackThickness = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_thickness);
+        mTrackPadding = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_edge_padding);
+
+        mTrackAnimator = ObjectAnimator.ofFloat();
+        mTrackAnimator.addUpdateListener(mTrackAnimatorListener);
+        mButtonAnimator = ObjectAnimator.ofInt();
+        mButtonAnimator.addUpdateListener(mButtonTranslationListener);
+        mQuickScrubEndAnimator = new AnimatorSet();
+        mQuickScrubEndAnimator.playTogether(mTrackAnimator, mButtonAnimator);
+        mQuickScrubEndAnimator.setDuration(ANIM_DURATION_MS);
+        mQuickScrubEndAnimator.addListener(mQuickScrubEndListener);
+        mQuickScrubEndAnimator.setInterpolator(mQuickScrubEndInterpolator);
+    }
+
+    public void setComponents(NavigationBarView navigationBarView) {
+        mNavigationBarView = navigationBarView;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
+        final ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
+        if (overviewProxy == null) {
+            homeButton.setDelayTouchFeedback(false);
+            return false;
+        }
+        mGestureDetector.onTouchEvent(event);
+        int action = event.getAction();
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN: {
+                int x = (int) event.getX();
+                int y = (int) event.getY();
+                if (mHomeButtonRect.contains(x, y)) {
+                    mTouchDownX = x;
+                    mTouchDownY = y;
+                    homeButton.setDelayTouchFeedback(true);
+                    mHandler.postDelayed(mLongPressRunnable, LONG_PRESS_DELAY_MS);
+                } else {
+                    mTouchDownX = mTouchDownY = -1;
+                }
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                if (mTouchDownX != -1) {
+                    int x = (int) event.getX();
+                    int y = (int) event.getY();
+                    int xDiff = Math.abs(x - mTouchDownX);
+                    int yDiff = Math.abs(y - mTouchDownY);
+                    boolean exceededTouchSlop;
+                    int pos, touchDown, offset, trackSize;
+                    if (mIsVertical) {
+                        exceededTouchSlop = yDiff > mScrollTouchSlop && yDiff > xDiff;
+                        pos = y;
+                        touchDown = mTouchDownY;
+                        offset = pos - mTrackRect.top;
+                        trackSize = mTrackRect.height();
+                    } else {
+                        exceededTouchSlop = xDiff > mScrollTouchSlop && xDiff > yDiff;
+                        pos = x;
+                        touchDown = mTouchDownX;
+                        offset = pos - mTrackRect.left;
+                        trackSize = mTrackRect.width();
+                    }
+                    if (!mDragPositive) {
+                        offset -= mIsVertical ? mTrackRect.height() : mTrackRect.width();
+                    }
+
+                    // Control the button movement
+                    if (!mDraggingActive && exceededTouchSlop) {
+                        boolean allowDrag = !mDragPositive
+                                ? offset < 0 && pos < touchDown : offset >= 0 && pos > touchDown;
+                        if (allowDrag) {
+                            mDownOffset = offset;
+                            homeButton.setClickable(false);
+                            mDraggingActive = true;
+                        }
+                    }
+                    if (mDraggingActive && (mDragPositive && offset >= 0
+                            || !mDragPositive && offset <= 0)) {
+                        float scrubFraction =
+                                Utilities.clamp(Math.abs(offset) * 1f / trackSize, 0, 1);
+                        mTranslation = !mDragPositive
+                            ? Utilities.clamp(offset - mDownOffset, -trackSize, 0)
+                            : Utilities.clamp(offset - mDownOffset, 0, trackSize);
+                        if (mQuickScrubActive) {
+                            try {
+                                overviewProxy.onQuickScrubProgress(scrubFraction);
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "Failed to send progress of quick scrub.", e);
+                            }
+                        } else {
+                            mTranslation /= SWITCH_STICKINESS;
+                        }
+                        if (mIsVertical) {
+                            homeButton.getCurrentView().setTranslationY(mTranslation);
+                        } else {
+                            homeButton.getCurrentView().setTranslationX(mTranslation);
+                        }
+                    }
+                }
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                endQuickScrub();
+                break;
+        }
+        return mDraggingActive || mQuickScrubActive;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        canvas.drawRect(mTrackRect, mTrackPaint);
+    }
+
+    @Override
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int width = right - left;
+        final int height = bottom - top;
+        final int x1, x2, y1, y2;
+        if (mIsVertical) {
+            x1 = (width - mTrackThickness) / 2;
+            x2 = x1 + mTrackThickness;
+            y1 = mDragPositive ? height / 2 : mTrackPadding;
+            y2 = y1 + height / 2 - mTrackPadding;
+        } else {
+            y1 = (height - mTrackThickness) / 2;
+            y2 = y1 + mTrackThickness;
+            x1 = mDragPositive ? width / 2 : mTrackPadding;
+            x2 = x1 + width / 2 - mTrackPadding;
+        }
+        mTrackRect.set(x1, y1, x2, y2);
+
+        // Get the touch rect of the home button location
+        View homeView = mNavigationBarView.getHomeButton().getCurrentView();
+        int[] globalHomePos = homeView.getLocationOnScreen();
+        int[] globalNavBarPos = mNavigationBarView.getLocationOnScreen();
+        int homeX = globalHomePos[0] - globalNavBarPos[0];
+        int homeY = globalHomePos[1] - globalNavBarPos[1];
+        mHomeButtonRect.set(homeX, homeY, homeX + homeView.getMeasuredWidth(),
+                homeY + homeView.getMeasuredHeight());
+    }
+
+    @Override
+    public void onDarkIntensityChange(float intensity) {
+        if (intensity == 0) {
+            mTrackPaint.setColor(mContext.getColor(R.color.quick_step_track_background_light));
+        } else if (intensity == 1) {
+            mTrackPaint.setColor(mContext.getColor(R.color.quick_step_track_background_dark));
+        }
+        mMaxTrackPaintAlpha = mTrackPaint.getAlpha() * 1f / 255;
+        mTrackPaint.setAlpha(0);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            endQuickScrub();
+        }
+        return false;
+    }
+
+    @Override
+    public void setBarState(boolean isVertical, boolean isRTL) {
+        mIsVertical = isVertical;
+        mIsRTL = isRTL;
+        try {
+            int navbarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition();
+            mDragPositive = navbarPos == NAV_BAR_LEFT || navbarPos == NAV_BAR_BOTTOM;
+            if (isRTL) {
+                mDragPositive = !mDragPositive;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get nav bar position.", e);
+        }
+    }
+
+    private void startQuickScrub() {
+        if (!mQuickScrubActive) {
+            mQuickScrubActive = true;
+            mTrackAnimator.setFloatValues(0, mMaxTrackPaintAlpha);
+            mTrackAnimator.start();
+            try {
+                mOverviewEventSender.getProxy().onQuickScrubStart();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to send start of quick scrub.", e);
+            }
+        }
+    }
+
+    private void endQuickScrub() {
+        mHandler.removeCallbacks(mLongPressRunnable);
+        if (mDraggingActive || mQuickScrubActive) {
+            mButtonAnimator.setIntValues((int) mTranslation, 0);
+            mTrackAnimator.setFloatValues(mTrackPaint.getAlpha() * 1f / 255, 0);
+            mQuickScrubEndAnimator.start();
+            try {
+                mOverviewEventSender.getProxy().onQuickScrubEnd();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to send end of quick scrub.", e);
+            }
+        }
+        mDraggingActive = false;
+    }
+
+    private int getDimensionPixelSize(Context context, @DimenRes int resId) {
+        return context.getResources().getDimensionPixelSize(resId);
+    }
+}
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 2da1e4d..af65a86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1596,7 +1596,7 @@
 
         final boolean hasArtwork = artworkDrawable != null;
 
-        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
+        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) && !mDozing
                 && (mState != StatusBarState.SHADE || allowWhenShade)
                 && mFingerprintUnlockController.getMode()
                         != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
@@ -1657,15 +1657,16 @@
                 }
             }
         } else {
-            // need to hide the album art, either because we are unlocked or because
-            // the metadata isn't there to support it
+            // need to hide the album art, either because we are unlocked, on AOD
+            // or because the metadata isn't there to support it
             if (mBackdrop.getVisibility() != View.GONE) {
                 if (DEBUG_MEDIA) {
                     Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
                 }
+                boolean cannotAnimateDoze = mDozing && !ScrimState.AOD.getAnimateChange();
                 if (mFingerprintUnlockController.getMode()
                         == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
-                        || hideBecauseOccluded) {
+                        || hideBecauseOccluded || cannotAnimateDoze) {
 
                     // We are unlocking directly - no animation!
                     mBackdrop.setVisibility(View.GONE);
@@ -4644,7 +4645,7 @@
 
         @Override
         public void dozeTimeTick() {
-            mNotificationPanel.refreshTime();
+            mNotificationPanel.dozeTimeTick();
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
index cc7943b..a2bec98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
@@ -26,9 +26,11 @@
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
 import android.view.DisplayListCanvas;
 import android.view.RenderNodeAnimator;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.animation.Interpolator;
 
 import com.android.systemui.Interpolators;
@@ -56,14 +58,17 @@
     private float mGlowAlpha = 0f;
     private float mGlowScale = 1f;
     private boolean mPressed;
+    private boolean mVisible;
     private boolean mDrawingHardwareGlow;
     private int mMaxWidth;
     private boolean mLastDark;
     private boolean mDark;
+    private boolean mDelayTouchFeedback;
 
     private final Interpolator mInterpolator = new LogInterpolator();
     private boolean mSupportHardware;
     private final View mTargetView;
+    private final Handler mHandler = new Handler();
 
     private final HashSet<Animator> mRunningAnimations = new HashSet<>();
     private final ArrayList<Animator> mTmpArray = new ArrayList<>();
@@ -77,6 +82,10 @@
         mDark = darkIntensity >= 0.5f;
     }
 
+    public void setDelayTouchFeedback(boolean delay) {
+        mDelayTouchFeedback = delay;
+    }
+
     private Paint getRipplePaint() {
         if (mRipplePaint == null) {
             mRipplePaint = new Paint();
@@ -211,7 +220,16 @@
         }
     }
 
+    /**
+     * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch
+     * is enabled.
+     */
+    public void abortDelayedRipple() {
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
     private void cancelAnimations() {
+        mVisible = false;
         mTmpArray.addAll(mRunningAnimations);
         int size = mTmpArray.size();
         for (int i = 0; i < size; i++) {
@@ -220,11 +238,21 @@
         }
         mTmpArray.clear();
         mRunningAnimations.clear();
+        mHandler.removeCallbacksAndMessages(null);
     }
 
     private void setPressedSoftware(boolean pressed) {
         if (pressed) {
-            enterSoftware();
+            if (mDelayTouchFeedback) {
+                if (mRunningAnimations.isEmpty()) {
+                    mHandler.removeCallbacksAndMessages(null);
+                    mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout());
+                } else if (mVisible) {
+                    enterSoftware();
+                }
+            } else {
+                enterSoftware();
+            }
         } else {
             exitSoftware();
         }
@@ -232,6 +260,7 @@
 
     private void enterSoftware() {
         cancelAnimations();
+        mVisible = true;
         mGlowAlpha = getMaxGlowAlpha();
         ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale",
                 0f, GLOW_MAX_SCALE_FACTOR);
@@ -240,6 +269,12 @@
         scaleAnimator.addListener(mAnimatorListener);
         scaleAnimator.start();
         mRunningAnimations.add(scaleAnimator);
+
+        // With the delay, it could eventually animate the enter animation with no pressed state,
+        // then immediately show the exit animation. If this is skipped there will be no ripple.
+        if (mDelayTouchFeedback && !mPressed) {
+            exitSoftware();
+        }
     }
 
     private void exitSoftware() {
@@ -253,7 +288,16 @@
 
     private void setPressedHardware(boolean pressed) {
         if (pressed) {
-            enterHardware();
+            if (mDelayTouchFeedback) {
+                if (mRunningAnimations.isEmpty()) {
+                    mHandler.removeCallbacksAndMessages(null);
+                    mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout());
+                } else if (mVisible) {
+                    enterHardware();
+                }
+            } else {
+                enterHardware();
+            }
         } else {
             exitHardware();
         }
@@ -302,6 +346,7 @@
 
     private void enterHardware() {
         cancelAnimations();
+        mVisible = true;
         mDrawingHardwareGlow = true;
         setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2));
         final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(),
@@ -343,6 +388,12 @@
         mRunningAnimations.add(endAnim);
 
         invalidateSelf();
+
+        // With the delay, it could eventually animate the enter animation with no pressed state,
+        // then immediately show the exit animation. If this is skipped there will be no ripple.
+        if (mDelayTouchFeedback && !mPressed) {
+            exitHardware();
+        }
     }
 
     private void exitHardware() {
@@ -366,6 +417,7 @@
         public void onAnimationEnd(Animator animation) {
             mRunningAnimations.remove(animation);
             if (mRunningAnimations.isEmpty() && !mPressed) {
+                mVisible = false;
                 mDrawingHardwareGlow = false;
                 invalidateSelf();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 0501771..077c6c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -284,6 +284,7 @@
     @Override
     public void abortCurrentGesture() {
         setPressed(false);
+        mRipple.abortDelayedRipple();
         mGestureAborted = true;
     }
 
@@ -301,6 +302,11 @@
     }
 
     @Override
+    public void setDelayTouchFeedback(boolean shouldDelay) {
+        mRipple.setDelayTouchFeedback(shouldDelay);
+    }
+
+    @Override
     public void setVertical(boolean vertical) {
         //no op
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 7f07e0c..3e37cfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.NotificationChannels;
 
+import java.util.concurrent.TimeUnit;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,6 +47,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class PowerNotificationWarningsTest extends SysuiTestCase {
+
+    public static final String FORMATTED_45M = "0h 45m";
+    public static final String FORMATTED_HOUR = "1h 0m";
     private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
     private PowerNotificationWarnings mPowerNotificationWarnings;
 
@@ -147,4 +151,22 @@
         verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(),
                 eq(SystemMessage.NOTE_THERMAL_SHUTDOWN), any());
     }
+
+    @Test
+    public void testGetTimeRemainingFormatted_roundsDownTo15() {
+        mPowerNotificationWarnings.updateEstimate(
+                new Estimate(TimeUnit.MINUTES.toMillis(57), true));
+        String time = mPowerNotificationWarnings.getTimeRemainingFormatted();
+
+        assertTrue("time:" + time + ", expected: " + FORMATTED_45M, time.equals(FORMATTED_45M));
+    }
+
+    @Test
+    public void testGetTimeRemainingFormatted_keepsMinutesWhenZero() {
+        mPowerNotificationWarnings.updateEstimate(
+                new Estimate(TimeUnit.MINUTES.toMillis(65), true));
+        String time = mPowerNotificationWarnings.getTimeRemainingFormatted();
+
+        assertTrue("time:" + time + ", expected: " + FORMATTED_HOUR, time.equals(FORMATTED_HOUR));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index e4734a4..fdb7f8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -19,12 +19,15 @@
 import static android.os.HardwarePropertiesManager.TEMPERATURE_SHUTDOWN;
 import static android.provider.Settings.Global.SHOW_TEMPERATURE_WARNING;
 
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
 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.content.Context;
+import android.os.BatteryManager;
 import android.os.HardwarePropertiesManager;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
@@ -37,6 +40,7 @@
 import com.android.systemui.power.PowerUI.WarningsUI;
 import com.android.systemui.statusbar.phone.StatusBar;
 
+import java.util.concurrent.TimeUnit;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,14 +50,23 @@
 @SmallTest
 public class PowerUITest extends SysuiTestCase {
 
+    private static final boolean UNPLUGGED = false;
+    private static final boolean POWER_SAVER_OFF = false;
+    private static final int ABOVE_WARNING_BUCKET = 1;
+    public static final int BELOW_WARNING_BUCKET = -1;
+    public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2);
+    public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4);
     private HardwarePropertiesManager mHardProps;
     private WarningsUI mMockWarnings;
     private PowerUI mPowerUI;
+    private EnhancedEstimates mEnhacedEstimates;
 
     @Before
     public void setup() {
         mMockWarnings = mDependency.injectMockDependency(WarningsUI.class);
+        mEnhacedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class);
         mHardProps = mock(HardwarePropertiesManager.class);
+
         mContext.putComponent(StatusBar.class, mock(StatusBar.class));
         mContext.addMockSystemService(Context.HARDWARE_PROPERTIES_SERVICE, mHardProps);
 
@@ -128,6 +141,180 @@
         verify(mMockWarnings).showHighTemperatureWarning();
     }
 
+    @Test
+    public void testShouldShowLowBatteryWarning_showHybridOnly_returnsShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // unplugged device that would not show the non-hybrid notification but would show the
+        // hybrid
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        ABOVE_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD,
+                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertTrue(shouldShow);
+    }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_showHybrid_showStandard_returnsShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // unplugged device that would show the non-hybrid notification and the hybrid
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD,
+                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertTrue(shouldShow);
+    }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_showStandardOnly_returnsShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // unplugged device that would show the non-hybrid but not the hybrid
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, Long.MAX_VALUE, ABOVE_HYBRID_THRESHOLD,
+                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertTrue(shouldShow);
+    }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_deviceHighBattery_returnsNoShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // unplugged device that would show the neither due to battery level being good
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, ABOVE_HYBRID_THRESHOLD,
+                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertFalse(shouldShow);
+    }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_devicePlugged_returnsNoShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // plugged device that would show the neither due to being plugged
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(!UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD,
+                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertFalse(shouldShow);
+   }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnkown_returnsNoShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // Unknown battery status device that would show the neither due
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD,
+                        !POWER_SAVER_OFF, BatteryManager.BATTERY_STATUS_UNKNOWN);
+        assertFalse(shouldShow);
+    }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_batterySaverEnabled_returnsNoShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // BatterySaverEnabled device that would show the neither due to battery saver
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD,
+                        !POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertFalse(shouldShow);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabled() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        // device that gets power saver turned on should dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, !POWER_SAVER_OFF);
+        assertTrue(shouldDismiss);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_dismissWhenPlugged() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+
+        // device that gets plugged in should dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(!UNPLUGGED, BELOW_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+        assertTrue(shouldDismiss);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_dismissHybridSignal_showStandardSignal_shouldShow() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        // would dismiss hybrid but not non-hybrid should not dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+        assertFalse(shouldDismiss);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_showHybridSignal_dismissStandardSignal_shouldShow() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+
+        // would dismiss non-hybrid but not hybrid should not dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+                        ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+        assertFalse(shouldDismiss);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_showBothSignal_shouldShow() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+
+        // should not dismiss when both would not dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+        assertFalse(shouldDismiss);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_dismissBothSignal_shouldDismiss() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+
+        //should dismiss if both would dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+                        ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+        assertTrue(shouldDismiss);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_dismissStandardSignal_hybridDisabled_shouldDismiss() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(false);
+
+        // would dismiss non-hybrid with hybrid disabled should dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+                        ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+        assertTrue(shouldDismiss);
+    }
+
     private void setCurrentTemp(float temp) {
         when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_CURRENT))
                 .thenReturn(new float[] { temp });
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 10a809a..04cee67 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5134,6 +5134,15 @@
     // OS: P
     ACTION_SCREENSHOT_POWER_MENU = 1282;
 
+    // OPEN: Settings > Apps & Notifications -> Special app access -> Storage Access
+    // CATEGORY: SETTINGS
+    // OS: P
+    STORAGE_ACCESS = 1283;
+
+    // OPEN: Settings > Apps & Notifications -> Special app access -> Storage Access -> Package
+    // CATEGORY: SETTINGS
+    // OS: P
+    APPLICATIONS_STORAGE_DETAIL = 1284;
 
     // ---- End P Constants, all P constants go above this line ----
     // Add new aosp constants above this line.
diff --git a/services/backup/java/com/android/server/backup/BackupPolicyEnforcer.java b/services/backup/java/com/android/server/backup/BackupPolicyEnforcer.java
new file mode 100644
index 0000000..158084a
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/BackupPolicyEnforcer.java
@@ -0,0 +1,25 @@
+package com.android.server.backup;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A helper class to decouple this service from {@link DevicePolicyManager} in order to improve
+ * testability.
+ */
+@VisibleForTesting
+public class BackupPolicyEnforcer {
+    private DevicePolicyManager mDevicePolicyManager;
+
+    public BackupPolicyEnforcer(Context context) {
+        mDevicePolicyManager =
+                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+    }
+
+    public ComponentName getMandatoryBackupTransport() {
+        return mDevicePolicyManager.getMandatoryBackupTransport();
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 350d7af..5188910 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -38,6 +38,7 @@
 import android.app.IActivityManager;
 import android.app.IBackupAgent;
 import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupManagerMonitor;
 import android.app.backup.FullBackup;
@@ -681,6 +682,8 @@
     @GuardedBy("mQueueLock")
     private ArrayList<FullBackupEntry> mFullBackupQueue;
 
+    private BackupPolicyEnforcer mBackupPolicyEnforcer;
+
     // Utility: build a new random integer token. The low bits are the ordinal of the
     // operation for near-time uniqueness, and the upper bits are random for app-
     // side unpredictability.
@@ -872,6 +875,8 @@
 
         // Power management
         mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
+
+        mBackupPolicyEnforcer = new BackupPolicyEnforcer(context);
     }
 
     private void initPackageTracking() {
@@ -2774,6 +2779,10 @@
     public void setBackupEnabled(boolean enable) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "setBackupEnabled");
+        if (!enable && mBackupPolicyEnforcer.getMandatoryBackupTransport() != null) {
+            Slog.w(TAG, "Cannot disable backups when the mandatory backups policy is active.");
+            return;
+        }
 
         Slog.i(TAG, "Backup enabled => " + enable);
 
@@ -3009,6 +3018,12 @@
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BACKUP, "selectBackupTransport");
 
+        if (!isAllowedByMandatoryBackupTransportPolicy(transportName)) {
+            // Don't change the transport if it is not allowed.
+            Slog.w(TAG, "Failed to select transport - disallowed by device owner policy.");
+            return mTransportManager.getCurrentTransportName();
+        }
+
         final long oldId = Binder.clearCallingIdentity();
         try {
             String previousTransportName = mTransportManager.selectTransport(transportName);
@@ -3023,10 +3038,20 @@
 
     @Override
     public void selectBackupTransportAsync(
-            ComponentName transportComponent, ISelectBackupTransportCallback listener) {
+            ComponentName transportComponent, @Nullable ISelectBackupTransportCallback listener) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BACKUP, "selectBackupTransportAsync");
-
+        if (!isAllowedByMandatoryBackupTransportPolicy(transportComponent)) {
+            try {
+                if (listener != null) {
+                    Slog.w(TAG, "Failed to select transport - disallowed by device owner policy.");
+                    listener.onFailure(BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "ISelectBackupTransportCallback listener not available");
+            }
+            return;
+        }
         final long oldId = Binder.clearCallingIdentity();
         try {
             String transportString = transportComponent.flattenToShortString();
@@ -3048,10 +3073,12 @@
                         }
 
                         try {
-                            if (transportName != null) {
-                                listener.onSuccess(transportName);
-                            } else {
-                                listener.onFailure(result);
+                            if (listener != null) {
+                                if (transportName != null) {
+                                    listener.onSuccess(transportName);
+                                } else {
+                                    listener.onFailure(result);
+                                }
                             }
                         } catch (RemoteException e) {
                             Slog.e(TAG, "ISelectBackupTransportCallback listener not available");
@@ -3062,6 +3089,38 @@
         }
     }
 
+    /**
+     * Returns if the specified transport can be set as the current transport without violating the
+     * mandatory backup transport policy.
+     */
+    private boolean isAllowedByMandatoryBackupTransportPolicy(String transportName) {
+        ComponentName mandatoryBackupTransport = mBackupPolicyEnforcer.getMandatoryBackupTransport();
+        if (mandatoryBackupTransport == null) {
+            return true;
+        }
+        final String mandatoryBackupTransportName;
+        try {
+            mandatoryBackupTransportName =
+                    mTransportManager.getTransportName(mandatoryBackupTransport);
+        } catch (TransportNotRegisteredException e) {
+            Slog.e(TAG, "mandatory backup transport not registered!");
+            return false;
+        }
+        return TextUtils.equals(mandatoryBackupTransportName, transportName);
+    }
+
+    /**
+     * Returns if the specified transport can be set as the current transport without violating the
+     * mandatory backup transport policy.
+     */
+    private boolean isAllowedByMandatoryBackupTransportPolicy(ComponentName transport) {
+        ComponentName mandatoryBackupTransport = mBackupPolicyEnforcer.getMandatoryBackupTransport();
+        if (mandatoryBackupTransport == null) {
+            return true;
+        }
+        return mandatoryBackupTransport.equals(transport);
+    }
+
     private void updateStateForTransport(String newTransportName) {
         // Publish the name change
         Settings.Secure.putString(mContext.getContentResolver(),
@@ -3081,6 +3140,7 @@
                 mCurrentToken = 0;
                 Slog.w(TAG, "Transport " + newTransportName + " not available: current token = 0");
             }
+            mTransportManager.disposeOfTransportClient(transportClient, callerLogString);
         } else {
             Slog.w(TAG, "Transport " + newTransportName + " not registered: current token = 0");
             // The named transport isn't registered, so we can't know what its current dataset token
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index c232241..cc3af8c 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -907,7 +907,15 @@
                         backupData = ParcelFileDescriptor.open(mBackupDataName,
                                 ParcelFileDescriptor.MODE_READ_ONLY);
                         backupManagerService.addBackupTrace("sending data to transport");
-                        int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+
+                        int userInitiatedFlag =
+                                mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+                        int incrementalFlag =
+                                mSavedStateName.length() == 0
+                                    ? BackupTransport.FLAG_NON_INCREMENTAL
+                                    : BackupTransport.FLAG_INCREMENTAL;
+                        int flags = userInitiatedFlag | incrementalFlag;
+
                         mStatus = transport.performBackup(mCurrentPackage, backupData, flags);
                     }
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9fb2681..3369458 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -16,6 +16,7 @@
         ":installd_aidl",
         ":storaged_aidl",
         ":vold_aidl",
+        ":mediaupdateservice_aidl",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
     ],
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index af0b66d..04d292f 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -293,15 +293,10 @@
 
     private void updateBatteryWarningLevelLocked() {
         final ContentResolver resolver = mContext.getContentResolver();
-        final int defWarnLevel = mContext.getResources().getInteger(
+        int defWarnLevel = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
-        final int lowPowerModeTriggerLevel = Settings.Global.getInt(resolver,
+        mLowBatteryWarningLevel = Settings.Global.getInt(resolver,
                 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel);
-
-        // NOTE: Keep the logic in sync with PowerUI.java in systemUI.
-        // TODO: Propagate this value from BatteryService to system UI, really.
-        mLowBatteryWarningLevel = Math.min(defWarnLevel, lowPowerModeTriggerLevel);
-
         if (mLowBatteryWarningLevel == 0) {
             mLowBatteryWarningLevel = defWarnLevel;
         }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index bbba878..77521df 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -226,7 +226,11 @@
     @GuardedBy("mVpns")
     private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>();
 
+    // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
+    // a direct call to LockdownVpnTracker.isEnabled().
+    @GuardedBy("mVpns")
     private boolean mLockdownEnabled;
+    @GuardedBy("mVpns")
     private LockdownVpnTracker mLockdownTracker;
 
     final private Context mContext;
@@ -997,9 +1001,9 @@
     }
 
     private Network[] getVpnUnderlyingNetworks(int uid) {
-        if (!mLockdownEnabled) {
-            int user = UserHandle.getUserId(uid);
-            synchronized (mVpns) {
+        synchronized (mVpns) {
+            if (!mLockdownEnabled) {
+                int user = UserHandle.getUserId(uid);
                 Vpn vpn = mVpns.get(user);
                 if (vpn != null && vpn.appliesToUid(uid)) {
                     return vpn.getUnderlyingNetworks();
@@ -1087,8 +1091,10 @@
         if (isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, ignoreBlocked)) {
             state.networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
         }
-        if (mLockdownTracker != null) {
-            mLockdownTracker.augmentNetworkInfo(state.networkInfo);
+        synchronized (mVpns) {
+            if (mLockdownTracker != null) {
+                mLockdownTracker.augmentNetworkInfo(state.networkInfo);
+            }
         }
     }
 
@@ -1253,8 +1259,8 @@
             result.put(nai.network, nc);
         }
 
-        if (!mLockdownEnabled) {
-            synchronized (mVpns) {
+        synchronized (mVpns) {
+            if (!mLockdownEnabled) {
                 Vpn vpn = mVpns.get(userId);
                 if (vpn != null) {
                     Network[] networks = vpn.getUnderlyingNetworks();
@@ -1580,9 +1586,11 @@
     }
 
     private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
-        if (mLockdownTracker != null) {
-            info = new NetworkInfo(info);
-            mLockdownTracker.augmentNetworkInfo(info);
+        synchronized (mVpns) {
+            if (mLockdownTracker != null) {
+                info = new NetworkInfo(info);
+                mLockdownTracker.augmentNetworkInfo(info);
+            }
         }
 
         Intent intent = new Intent(bcastType);
@@ -3436,9 +3444,9 @@
     public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
             int userId) {
         enforceCrossUserPermission(userId);
-        throwIfLockdownEnabled();
 
         synchronized (mVpns) {
+            throwIfLockdownEnabled();
             Vpn vpn = mVpns.get(userId);
             if (vpn != null) {
                 return vpn.prepare(oldPackage, newPackage);
@@ -3482,9 +3490,9 @@
      */
     @Override
     public ParcelFileDescriptor establishVpn(VpnConfig config) {
-        throwIfLockdownEnabled();
         int user = UserHandle.getUserId(Binder.getCallingUid());
         synchronized (mVpns) {
+            throwIfLockdownEnabled();
             return mVpns.get(user).establish(config);
         }
     }
@@ -3495,13 +3503,13 @@
      */
     @Override
     public void startLegacyVpn(VpnProfile profile) {
-        throwIfLockdownEnabled();
+        int user = UserHandle.getUserId(Binder.getCallingUid());
         final LinkProperties egress = getActiveLinkProperties();
         if (egress == null) {
             throw new IllegalStateException("Missing active network connection");
         }
-        int user = UserHandle.getUserId(Binder.getCallingUid());
         synchronized (mVpns) {
+            throwIfLockdownEnabled();
             mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
         }
     }
@@ -3527,11 +3535,11 @@
     @Override
     public VpnInfo[] getAllVpnInfo() {
         enforceConnectivityInternalPermission();
-        if (mLockdownEnabled) {
-            return new VpnInfo[0];
-        }
-
         synchronized (mVpns) {
+            if (mLockdownEnabled) {
+                return new VpnInfo[0];
+            }
+
             List<VpnInfo> infoList = new ArrayList<>();
             for (int i = 0; i < mVpns.size(); i++) {
                 VpnInfo info = createVpnInfo(mVpns.valueAt(i));
@@ -3596,33 +3604,33 @@
             return false;
         }
 
-        // Tear down existing lockdown if profile was removed
-        mLockdownEnabled = LockdownVpnTracker.isEnabled();
-        if (mLockdownEnabled) {
-            byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
-            if (profileTag == null) {
-                Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore");
-                return false;
-            }
-            String profileName = new String(profileTag);
-            final VpnProfile profile = VpnProfile.decode(
-                    profileName, mKeyStore.get(Credentials.VPN + profileName));
-            if (profile == null) {
-                Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName);
-                setLockdownTracker(null);
-                return true;
-            }
-            int user = UserHandle.getUserId(Binder.getCallingUid());
-            synchronized (mVpns) {
+        synchronized (mVpns) {
+            // Tear down existing lockdown if profile was removed
+            mLockdownEnabled = LockdownVpnTracker.isEnabled();
+            if (mLockdownEnabled) {
+                byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
+                if (profileTag == null) {
+                    Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore");
+                    return false;
+                }
+                String profileName = new String(profileTag);
+                final VpnProfile profile = VpnProfile.decode(
+                        profileName, mKeyStore.get(Credentials.VPN + profileName));
+                if (profile == null) {
+                    Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName);
+                    setLockdownTracker(null);
+                    return true;
+                }
+                int user = UserHandle.getUserId(Binder.getCallingUid());
                 Vpn vpn = mVpns.get(user);
                 if (vpn == null) {
                     Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
                     return false;
                 }
                 setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, vpn, profile));
+            } else {
+                setLockdownTracker(null);
             }
-        } else {
-            setLockdownTracker(null);
         }
 
         return true;
@@ -3632,6 +3640,7 @@
      * Internally set new {@link LockdownVpnTracker}, shutting down any existing
      * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
      */
+    @GuardedBy("mVpns")
     private void setLockdownTracker(LockdownVpnTracker tracker) {
         // Shutdown any existing tracker
         final LockdownVpnTracker existing = mLockdownTracker;
@@ -3646,6 +3655,7 @@
         }
     }
 
+    @GuardedBy("mVpns")
     private void throwIfLockdownEnabled() {
         if (mLockdownEnabled) {
             throw new IllegalStateException("Unavailable in lockdown mode");
@@ -3693,12 +3703,12 @@
         enforceConnectivityInternalPermission();
         enforceCrossUserPermission(userId);
 
-        // Can't set always-on VPN if legacy VPN is already in lockdown mode.
-        if (LockdownVpnTracker.isEnabled()) {
-            return false;
-        }
-
         synchronized (mVpns) {
+            // Can't set always-on VPN if legacy VPN is already in lockdown mode.
+            if (LockdownVpnTracker.isEnabled()) {
+                return false;
+            }
+
             Vpn vpn = mVpns.get(userId);
             if (vpn == null) {
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
@@ -3874,9 +3884,9 @@
             }
             userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId);
             mVpns.put(userId, userVpn);
-        }
-        if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
-            updateLockdownVpn();
+            if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
+                updateLockdownVpn();
+            }
         }
     }
 
@@ -3913,11 +3923,13 @@
     }
 
     private void onUserUnlocked(int userId) {
-        // User present may be sent because of an unlock, which might mean an unlocked keystore.
-        if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
-            updateLockdownVpn();
-        } else {
-            startAlwaysOnVpn(userId);
+        synchronized (mVpns) {
+            // User present may be sent because of an unlock, which might mean an unlocked keystore.
+            if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
+                updateLockdownVpn();
+            } else {
+                startAlwaysOnVpn(userId);
+            }
         }
     }
 
@@ -4597,51 +4609,67 @@
     }
 
     /**
-     * Update the NetworkCapabilities for {@code networkAgent} to {@code networkCapabilities}
-     * augmented with any stateful capabilities implied from {@code networkAgent}
-     * (e.g., validated status and captive portal status).
-     *
-     * @param oldScore score of the network before any of the changes that prompted us
-     *                 to call this function.
-     * @param nai the network having its capabilities updated.
-     * @param networkCapabilities the new network capabilities.
+     * Augments the NetworkCapabilities passed in by a NetworkAgent with capabilities that are
+     * maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal,
+     * and foreground status).
      */
-    private void updateCapabilities(
-            int oldScore, NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) {
+    private NetworkCapabilities mixInCapabilities(NetworkAgentInfo nai, NetworkCapabilities nc) {
         // Once a NetworkAgent is connected, complain if some immutable capabilities are removed.
-        if (nai.everConnected && !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(
-                networkCapabilities)) {
-            // TODO: consider not complaining when a network agent degrade its capabilities if this
+        if (nai.everConnected &&
+                !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(nc)) {
+            // TODO: consider not complaining when a network agent degrades its capabilities if this
             // does not cause any request (that is not a listen) currently matching that agent to
             // stop being matched by the updated agent.
-            String diff = nai.networkCapabilities.describeImmutableDifferences(networkCapabilities);
+            String diff = nai.networkCapabilities.describeImmutableDifferences(nc);
             if (!TextUtils.isEmpty(diff)) {
                 Slog.wtf(TAG, "BUG: " + nai + " lost immutable capabilities:" + diff);
             }
         }
 
         // Don't modify caller's NetworkCapabilities.
-        networkCapabilities = new NetworkCapabilities(networkCapabilities);
+        NetworkCapabilities newNc = new NetworkCapabilities(nc);
         if (nai.lastValidated) {
-            networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
+            newNc.addCapability(NET_CAPABILITY_VALIDATED);
         } else {
-            networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED);
+            newNc.removeCapability(NET_CAPABILITY_VALIDATED);
         }
         if (nai.lastCaptivePortalDetected) {
-            networkCapabilities.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+            newNc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
         } else {
-            networkCapabilities.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+            newNc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
         }
         if (nai.isBackgroundNetwork()) {
-            networkCapabilities.removeCapability(NET_CAPABILITY_FOREGROUND);
+            newNc.removeCapability(NET_CAPABILITY_FOREGROUND);
         } else {
-            networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
+            newNc.addCapability(NET_CAPABILITY_FOREGROUND);
         }
 
-        if (Objects.equals(nai.networkCapabilities, networkCapabilities)) return;
+        return newNc;
+    }
+
+    /**
+     * Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically:
+     *
+     * 1. Calls mixInCapabilities to merge the passed-in NetworkCapabilities {@code nc} with the
+     *    capabilities we manage and store in {@code nai}, such as validated status and captive
+     *    portal status)
+     * 2. Takes action on the result: changes network permissions, sends CAP_CHANGED callbacks, and
+     *    potentially triggers rematches.
+     * 3. Directly informs other network stack components (NetworkStatsService, VPNs, etc. of the
+     *    change.)
+     *
+     * @param oldScore score of the network before any of the changes that prompted us
+     *                 to call this function.
+     * @param nai the network having its capabilities updated.
+     * @param nc the new network capabilities.
+     */
+    private void updateCapabilities(int oldScore, NetworkAgentInfo nai, NetworkCapabilities nc) {
+        NetworkCapabilities newNc = mixInCapabilities(nai, nc);
+
+        if (Objects.equals(nai.networkCapabilities, newNc)) return;
 
         final String oldPermission = getNetworkPermission(nai.networkCapabilities);
-        final String newPermission = getNetworkPermission(networkCapabilities);
+        final String newPermission = getNetworkPermission(newNc);
         if (!Objects.equals(oldPermission, newPermission) && nai.created && !nai.isVPN()) {
             try {
                 mNetd.setNetworkPermission(nai.network.netId, newPermission);
@@ -4653,11 +4681,10 @@
         final NetworkCapabilities prevNc;
         synchronized (nai) {
             prevNc = nai.networkCapabilities;
-            nai.networkCapabilities = networkCapabilities;
+            nai.networkCapabilities = newNc;
         }
 
-        if (nai.getCurrentScore() == oldScore &&
-                networkCapabilities.equalRequestableCapabilities(prevNc)) {
+        if (nai.getCurrentScore() == oldScore && newNc.equalRequestableCapabilities(prevNc)) {
             // If the requestable capabilities haven't changed, and the score hasn't changed, then
             // the change we're processing can't affect any requests, it can only affect the listens
             // on this network. We might have been called by rematchNetworkAndRequests when a
@@ -4673,15 +4700,15 @@
         // Report changes that are interesting for network statistics tracking.
         if (prevNc != null) {
             final boolean meteredChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_METERED) !=
-                    networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED);
+                    newNc.hasCapability(NET_CAPABILITY_NOT_METERED);
             final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) !=
-                    networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+                    newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
             if (meteredChanged || roamingChanged) {
                 notifyIfacesChangedForNetworkStats();
             }
         }
 
-        if (!networkCapabilities.hasTransport(TRANSPORT_VPN)) {
+        if (!newNc.hasTransport(TRANSPORT_VPN)) {
             // Tell VPNs about updated capabilities, since they may need to
             // bubble those changes through.
             synchronized (mVpns) {
@@ -5205,11 +5232,13 @@
     }
 
     private void notifyLockdownVpn(NetworkAgentInfo nai) {
-        if (mLockdownTracker != null) {
-            if (nai != null && nai.isVPN()) {
-                mLockdownTracker.onVpnStateChanged(nai.networkInfo);
-            } else {
-                mLockdownTracker.onNetworkInfoChanged();
+        synchronized (mVpns) {
+            if (mLockdownTracker != null) {
+                if (nai != null && nai.isVPN()) {
+                    mLockdownTracker.onVpnStateChanged(nai.networkInfo);
+                } else {
+                    mLockdownTracker.onNetworkInfoChanged();
+                }
             }
         }
     }
@@ -5439,28 +5468,28 @@
 
     @Override
     public boolean addVpnAddress(String address, int prefixLength) {
-        throwIfLockdownEnabled();
         int user = UserHandle.getUserId(Binder.getCallingUid());
         synchronized (mVpns) {
+            throwIfLockdownEnabled();
             return mVpns.get(user).addAddress(address, prefixLength);
         }
     }
 
     @Override
     public boolean removeVpnAddress(String address, int prefixLength) {
-        throwIfLockdownEnabled();
         int user = UserHandle.getUserId(Binder.getCallingUid());
         synchronized (mVpns) {
+            throwIfLockdownEnabled();
             return mVpns.get(user).removeAddress(address, prefixLength);
         }
     }
 
     @Override
     public boolean setUnderlyingNetworksForVpn(Network[] networks) {
-        throwIfLockdownEnabled();
         int user = UserHandle.getUserId(Binder.getCallingUid());
-        boolean success;
+        final boolean success;
         synchronized (mVpns) {
+            throwIfLockdownEnabled();
             success = mVpns.get(user).setUnderlyingNetworks(networks);
         }
         if (success) {
@@ -5520,31 +5549,31 @@
                     setAlwaysOnVpnPackage(userId, null, false);
                     setVpnPackageAuthorization(alwaysOnPackage, userId, false);
                 }
-            }
 
-            // Turn Always-on VPN off
-            if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    mKeyStore.delete(Credentials.LOCKDOWN_VPN);
-                    mLockdownEnabled = false;
-                    setLockdownTracker(null);
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
+                // Turn Always-on VPN off
+                if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        mKeyStore.delete(Credentials.LOCKDOWN_VPN);
+                        mLockdownEnabled = false;
+                        setLockdownTracker(null);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
                 }
-            }
 
-            // Turn VPN off
-            VpnConfig vpnConfig = getVpnConfig(userId);
-            if (vpnConfig != null) {
-                if (vpnConfig.legacy) {
-                    prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
-                } else {
-                    // Prevent this app (packagename = vpnConfig.user) from initiating VPN connections
-                    // in the future without user intervention.
-                    setVpnPackageAuthorization(vpnConfig.user, userId, false);
+                // Turn VPN off
+                VpnConfig vpnConfig = getVpnConfig(userId);
+                if (vpnConfig != null) {
+                    if (vpnConfig.legacy) {
+                        prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
+                    } else {
+                        // Prevent this app (packagename = vpnConfig.user) from initiating
+                        // VPN connections in the future without user intervention.
+                        setVpnPackageAuthorization(vpnConfig.user, userId, false);
 
-                    prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
+                        prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 02cfe3d..9d228c3 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -25,6 +25,7 @@
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.IIpSecService;
 import android.net.INetd;
 import android.net.IpSecAlgorithm;
@@ -62,7 +63,6 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import libcore.io.IoUtils;
 
@@ -83,7 +83,7 @@
 
     private static final String NETD_SERVICE_NAME = "netd";
     private static final int[] DIRECTIONS =
-            new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
+            new int[] {IpSecManager.DIRECTION_OUT, IpSecManager.DIRECTION_IN};
 
     private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
     private static final int MAX_PORT_BIND_ATTEMPTS = 10;
@@ -104,10 +104,10 @@
     private final Context mContext;
 
     /**
-     * The next non-repeating global ID for tracking resources between users, this service,
-     * and kernel data structures. Accessing this variable is not thread safe, so it is
-     * only read or modified within blocks synchronized on IpSecService.this. We want to
-     * avoid -1 (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it).
+     * The next non-repeating global ID for tracking resources between users, this service, and
+     * kernel data structures. Accessing this variable is not thread safe, so it is only read or
+     * modified within blocks synchronized on IpSecService.this. We want to avoid -1
+     * (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it).
      */
     @GuardedBy("IpSecService.this")
     private int mNextResourceId = 1;
@@ -536,14 +536,14 @@
 
     private final class TransformRecord extends KernelResourceRecord {
         private final IpSecConfig mConfig;
-        private final SpiRecord[] mSpis;
+        private final SpiRecord mSpi;
         private final EncapSocketRecord mSocket;
 
         TransformRecord(
-                int resourceId, IpSecConfig config, SpiRecord[] spis, EncapSocketRecord socket) {
+                int resourceId, IpSecConfig config, SpiRecord spi, EncapSocketRecord socket) {
             super(resourceId);
             mConfig = config;
-            mSpis = spis;
+            mSpi = spi;
             mSocket = socket;
         }
 
@@ -551,29 +551,26 @@
             return mConfig;
         }
 
-        public SpiRecord getSpiRecord(int direction) {
-            return mSpis[direction];
+        public SpiRecord getSpiRecord() {
+            return mSpi;
         }
 
         /** always guarded by IpSecService#this */
         @Override
         public void freeUnderlyingResources() {
-            for (int direction : DIRECTIONS) {
-                int spi = mSpis[direction].getSpi();
-                try {
-                    mSrvConfig
-                            .getNetdInstance()
-                            .ipSecDeleteSecurityAssociation(
-                                    mResourceId,
-                                    direction,
-                                    mConfig.getLocalAddress(),
-                                    mConfig.getRemoteAddress(),
-                                    spi);
-                } catch (ServiceSpecificException e) {
-                    // FIXME: get the error code and throw is at an IOException from Errno Exception
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
-                }
+            int spi = mSpi.getSpi();
+            try {
+                mSrvConfig
+                        .getNetdInstance()
+                        .ipSecDeleteSecurityAssociation(
+                                mResourceId,
+                                mConfig.getSourceAddress(),
+                                mConfig.getDestinationAddress(),
+                                spi);
+            } catch (ServiceSpecificException e) {
+                // FIXME: get the error code and throw is at an IOException from Errno Exception
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
             }
 
             getResourceTracker().give();
@@ -597,10 +594,8 @@
                     .append(super.toString())
                     .append(", mSocket=")
                     .append(mSocket)
-                    .append(", mSpis[OUT].mResourceId=")
-                    .append(mSpis[IpSecTransform.DIRECTION_OUT].mResourceId)
-                    .append(", mSpis[IN].mResourceId=")
-                    .append(mSpis[IpSecTransform.DIRECTION_IN].mResourceId)
+                    .append(", mSpi.mResourceId=")
+                    .append(mSpi.mResourceId)
                     .append(", mConfig=")
                     .append(mConfig)
                     .append("}");
@@ -609,23 +604,16 @@
     }
 
     private final class SpiRecord extends KernelResourceRecord {
-        private final int mDirection;
-        private final String mLocalAddress;
-        private final String mRemoteAddress;
+        private final String mSourceAddress;
+        private final String mDestinationAddress;
         private int mSpi;
 
         private boolean mOwnedByTransform = false;
 
-        SpiRecord(
-                int resourceId,
-                int direction,
-                String localAddress,
-                String remoteAddress,
-                int spi) {
+        SpiRecord(int resourceId, String sourceAddress, String destinationAddress, int spi) {
             super(resourceId);
-            mDirection = direction;
-            mLocalAddress = localAddress;
-            mRemoteAddress = remoteAddress;
+            mSourceAddress = sourceAddress;
+            mDestinationAddress = destinationAddress;
             mSpi = spi;
         }
 
@@ -646,7 +634,7 @@
                 mSrvConfig
                         .getNetdInstance()
                         .ipSecDeleteSecurityAssociation(
-                                mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
+                                mResourceId, mSourceAddress, mDestinationAddress, mSpi);
             } catch (ServiceSpecificException e) {
                 // FIXME: get the error code and throw is at an IOException from Errno Exception
             } catch (RemoteException e) {
@@ -662,6 +650,10 @@
             return mSpi;
         }
 
+        public String getDestinationAddress() {
+            return mDestinationAddress;
+        }
+
         public void setOwnedByTransform() {
             if (mOwnedByTransform) {
                 // Programming error
@@ -689,12 +681,10 @@
                     .append(super.toString())
                     .append(", mSpi=")
                     .append(mSpi)
-                    .append(", mDirection=")
-                    .append(mDirection)
-                    .append(", mLocalAddress=")
-                    .append(mLocalAddress)
-                    .append(", mRemoteAddress=")
-                    .append(mRemoteAddress)
+                    .append(", mSourceAddress=")
+                    .append(mSourceAddress)
+                    .append(", mDestinationAddress=")
+                    .append(mDestinationAddress)
                     .append(", mOwnedByTransform=")
                     .append(mOwnedByTransform)
                     .append("}");
@@ -772,14 +762,17 @@
     /** @hide */
     @VisibleForTesting
     public IpSecService(Context context, IpSecServiceConfiguration config) {
-        this(context, config, (fd, uid) ->  {
-            try{
-                TrafficStats.setThreadStatsUid(uid);
-                TrafficStats.tagFileDescriptor(fd);
-            } finally {
-                TrafficStats.clearThreadStatsUid();
-            }
-        });
+        this(
+                context,
+                config,
+                (fd, uid) -> {
+                    try {
+                        TrafficStats.setThreadStatsUid(uid);
+                        TrafficStats.tagFileDescriptor(fd);
+                    } finally {
+                        TrafficStats.clearThreadStatsUid();
+                    }
+                });
     }
 
     /** @hide */
@@ -845,8 +838,8 @@
      */
     private static void checkDirection(int direction) {
         switch (direction) {
-            case IpSecTransform.DIRECTION_OUT:
-            case IpSecTransform.DIRECTION_IN:
+            case IpSecManager.DIRECTION_OUT:
+            case IpSecManager.DIRECTION_IN:
                 return;
         }
         throw new IllegalArgumentException("Invalid Direction: " + direction);
@@ -855,10 +848,8 @@
     /** Get a new SPI and maintain the reservation in the system server */
     @Override
     public synchronized IpSecSpiResponse allocateSecurityParameterIndex(
-            int direction, String remoteAddress, int requestedSpi, IBinder binder)
-            throws RemoteException {
-        checkDirection(direction);
-        checkInetAddress(remoteAddress);
+            String destinationAddress, int requestedSpi, IBinder binder) throws RemoteException {
+        checkInetAddress(destinationAddress);
         /* requestedSpi can be anything in the int range, so no check is needed. */
         checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex");
 
@@ -866,28 +857,21 @@
         final int resourceId = mNextResourceId++;
 
         int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
-        String localAddress = "";
-
         try {
             if (!userRecord.mSpiQuotaTracker.isAvailable()) {
                 return new IpSecSpiResponse(
                         IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
             }
+
             spi =
                     mSrvConfig
                             .getNetdInstance()
-                            .ipSecAllocateSpi(
-                                    resourceId,
-                                    direction,
-                                    localAddress,
-                                    remoteAddress,
-                                    requestedSpi);
+                            .ipSecAllocateSpi(resourceId, "", destinationAddress, requestedSpi);
             Log.d(TAG, "Allocated SPI " + spi);
             userRecord.mSpiRecords.put(
                     resourceId,
                     new RefcountedResource<SpiRecord>(
-                            new SpiRecord(resourceId, direction, localAddress, remoteAddress, spi),
-                            binder));
+                            new SpiRecord(resourceId, "", destinationAddress, spi), binder));
         } catch (ServiceSpecificException e) {
             // TODO: Add appropriate checks when other ServiceSpecificException types are supported
             return new IpSecSpiResponse(
@@ -1032,27 +1016,27 @@
     }
 
     @VisibleForTesting
-    void validateAlgorithms(IpSecConfig config, int direction) throws IllegalArgumentException {
-            IpSecAlgorithm auth = config.getAuthentication(direction);
-            IpSecAlgorithm crypt = config.getEncryption(direction);
-            IpSecAlgorithm aead = config.getAuthenticatedEncryption(direction);
+    void validateAlgorithms(IpSecConfig config) throws IllegalArgumentException {
+        IpSecAlgorithm auth = config.getAuthentication();
+        IpSecAlgorithm crypt = config.getEncryption();
+        IpSecAlgorithm aead = config.getAuthenticatedEncryption();
 
-            // Validate the algorithm set
-            Preconditions.checkArgument(
-                    aead != null || crypt != null || auth != null,
-                    "No Encryption or Authentication algorithms specified");
-            Preconditions.checkArgument(
-                    auth == null || auth.isAuthentication(),
-                    "Unsupported algorithm for Authentication");
-            Preconditions.checkArgument(
+        // Validate the algorithm set
+        Preconditions.checkArgument(
+                aead != null || crypt != null || auth != null,
+                "No Encryption or Authentication algorithms specified");
+        Preconditions.checkArgument(
+                auth == null || auth.isAuthentication(),
+                "Unsupported algorithm for Authentication");
+        Preconditions.checkArgument(
                 crypt == null || crypt.isEncryption(), "Unsupported algorithm for Encryption");
-            Preconditions.checkArgument(
-                    aead == null || aead.isAead(),
-                    "Unsupported algorithm for Authenticated Encryption");
-            Preconditions.checkArgument(
-                    aead == null || (auth == null && crypt == null),
-                    "Authenticated Encryption is mutually exclusive with other Authentication "
-                                    + "or Encryption algorithms");
+        Preconditions.checkArgument(
+                aead == null || aead.isAead(),
+                "Unsupported algorithm for Authenticated Encryption");
+        Preconditions.checkArgument(
+                aead == null || (auth == null && crypt == null),
+                "Authenticated Encryption is mutually exclusive with other Authentication "
+                        + "or Encryption algorithms");
     }
 
     /**
@@ -1062,29 +1046,6 @@
     private void checkIpSecConfig(IpSecConfig config) {
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
 
-        if (config.getLocalAddress() == null) {
-            throw new IllegalArgumentException("Invalid null Local InetAddress");
-        }
-
-        if (config.getRemoteAddress() == null) {
-            throw new IllegalArgumentException("Invalid null Remote InetAddress");
-        }
-
-        switch (config.getMode()) {
-            case IpSecTransform.MODE_TRANSPORT:
-                if (!config.getLocalAddress().isEmpty()) {
-                    throw new IllegalArgumentException("Non-empty Local Address");
-                }
-                // Must be valid, and not a wildcard
-                checkInetAddress(config.getRemoteAddress());
-                break;
-            case IpSecTransform.MODE_TUNNEL:
-                break;
-            default:
-                throw new IllegalArgumentException(
-                        "Invalid IpSecTransform.mode: " + config.getMode());
-        }
-
         switch (config.getEncapType()) {
             case IpSecTransform.ENCAP_NONE:
                 break;
@@ -1103,11 +1064,36 @@
                 throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
         }
 
-        for (int direction : DIRECTIONS) {
-            validateAlgorithms(config, direction);
+        validateAlgorithms(config);
 
-            // Retrieve SPI record; will throw IllegalArgumentException if not found
-            userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId(direction));
+        // Retrieve SPI record; will throw IllegalArgumentException if not found
+        SpiRecord s = userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId());
+
+        // If no remote address is supplied, then use one from the SPI.
+        if (TextUtils.isEmpty(config.getDestinationAddress())) {
+            config.setDestinationAddress(s.getDestinationAddress());
+        }
+
+        // All remote addresses must match
+        if (!config.getDestinationAddress().equals(s.getDestinationAddress())) {
+            throw new IllegalArgumentException("Mismatched remote addresseses.");
+        }
+
+        // This check is technically redundant due to the chain of custody between the SPI and
+        // the IpSecConfig, but in the future if the dest is allowed to be set explicitly in
+        // the transform, this will prevent us from messing up.
+        checkInetAddress(config.getDestinationAddress());
+
+        // Require a valid source address for all transforms.
+        checkInetAddress(config.getSourceAddress());
+
+        switch (config.getMode()) {
+            case IpSecTransform.MODE_TRANSPORT:
+            case IpSecTransform.MODE_TUNNEL:
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid IpSecTransform.mode: " + config.getMode());
         }
     }
 
@@ -1127,13 +1113,12 @@
 
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
 
-        // Avoid resizing by creating a dependency array of min-size 3 (1 UDP encap + 2 SPIs)
-        List<RefcountedResource> dependencies = new ArrayList<>(3);
+        // Avoid resizing by creating a dependency array of min-size 2 (1 UDP encap + 1 SPI)
+        List<RefcountedResource> dependencies = new ArrayList<>(2);
 
         if (!userRecord.mTransformQuotaTracker.isAvailable()) {
             return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
         }
-        SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
 
         int encapType, encapLocalPort = 0, encapRemotePort = 0;
         EncapSocketRecord socketRecord = null;
@@ -1149,51 +1134,46 @@
             encapRemotePort = c.getEncapRemotePort();
         }
 
-        for (int direction : DIRECTIONS) {
-            IpSecAlgorithm auth = c.getAuthentication(direction);
-            IpSecAlgorithm crypt = c.getEncryption(direction);
-            IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction);
+        IpSecAlgorithm auth = c.getAuthentication();
+        IpSecAlgorithm crypt = c.getEncryption();
+        IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption();
 
-            RefcountedResource<SpiRecord> refcountedSpiRecord =
-                    userRecord.mSpiRecords.getRefcountedResourceOrThrow(
-                            c.getSpiResourceId(direction));
-            dependencies.add(refcountedSpiRecord);
+        RefcountedResource<SpiRecord> refcountedSpiRecord =
+                userRecord.mSpiRecords.getRefcountedResourceOrThrow(c.getSpiResourceId());
+        dependencies.add(refcountedSpiRecord);
+        SpiRecord spiRecord = refcountedSpiRecord.getResource();
 
-            spis[direction] = refcountedSpiRecord.getResource();
-            int spi = spis[direction].getSpi();
-            try {
-                mSrvConfig
-                        .getNetdInstance()
-                        .ipSecAddSecurityAssociation(
-                                resourceId,
-                                c.getMode(),
-                                direction,
-                                c.getLocalAddress(),
-                                c.getRemoteAddress(),
-                                (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
-                                spi,
-                                (auth != null) ? auth.getName() : "",
-                                (auth != null) ? auth.getKey() : new byte[] {},
-                                (auth != null) ? auth.getTruncationLengthBits() : 0,
-                                (crypt != null) ? crypt.getName() : "",
-                                (crypt != null) ? crypt.getKey() : new byte[] {},
-                                (crypt != null) ? crypt.getTruncationLengthBits() : 0,
-                                (authCrypt != null) ? authCrypt.getName() : "",
-                                (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
-                                (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
-                                encapType,
-                                encapLocalPort,
-                                encapRemotePort);
-            } catch (ServiceSpecificException e) {
-                // FIXME: get the error code and throw is at an IOException from Errno Exception
-                return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
-            }
+        try {
+            mSrvConfig
+                    .getNetdInstance()
+                    .ipSecAddSecurityAssociation(
+                            resourceId,
+                            c.getMode(),
+                            c.getSourceAddress(),
+                            c.getDestinationAddress(),
+                            (c.getNetwork() != null) ? c.getNetwork().netId : 0,
+                            spiRecord.getSpi(),
+                            (auth != null) ? auth.getName() : "",
+                            (auth != null) ? auth.getKey() : new byte[] {},
+                            (auth != null) ? auth.getTruncationLengthBits() : 0,
+                            (crypt != null) ? crypt.getName() : "",
+                            (crypt != null) ? crypt.getKey() : new byte[] {},
+                            (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+                            (authCrypt != null) ? authCrypt.getName() : "",
+                            (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
+                            (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
+                            encapType,
+                            encapLocalPort,
+                            encapRemotePort);
+        } catch (ServiceSpecificException e) {
+            // FIXME: get the error code and throw is at an IOException from Errno Exception
+            return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
         }
         // Both SAs were created successfully, time to construct a record and lock it away
         userRecord.mTransformRecords.put(
                 resourceId,
                 new RefcountedResource<TransformRecord>(
-                        new TransformRecord(resourceId, c, spis, socketRecord),
+                        new TransformRecord(resourceId, c, spiRecord, socketRecord),
                         binder,
                         dependencies.toArray(new RefcountedResource[dependencies.size()])));
         return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
@@ -1217,9 +1197,9 @@
      */
     @Override
     public synchronized void applyTransportModeTransform(
-            ParcelFileDescriptor socket, int resourceId) throws RemoteException {
+            ParcelFileDescriptor socket, int direction, int resourceId) throws RemoteException {
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
-
+        checkDirection(direction);
         // Get transform record; if no transform is found, will throw IllegalArgumentException
         TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
 
@@ -1230,17 +1210,15 @@
 
         IpSecConfig c = info.getConfig();
         try {
-            for (int direction : DIRECTIONS) {
-                mSrvConfig
-                        .getNetdInstance()
-                        .ipSecApplyTransportModeTransform(
-                                socket.getFileDescriptor(),
-                                resourceId,
-                                direction,
-                                c.getLocalAddress(),
-                                c.getRemoteAddress(),
-                                info.getSpiRecord(direction).getSpi());
-            }
+            mSrvConfig
+                    .getNetdInstance()
+                    .ipSecApplyTransportModeTransform(
+                            socket.getFileDescriptor(),
+                            resourceId,
+                            direction,
+                            c.getSourceAddress(),
+                            c.getDestinationAddress(),
+                            info.getSpiRecord().getSpi());
         } catch (ServiceSpecificException e) {
             if (e.errorCode == EINVAL) {
                 throw new IllegalArgumentException(e.toString());
@@ -1251,14 +1229,14 @@
     }
 
     /**
-     * Remove a transport mode transform from a socket, applying the default (empty) policy. This
-     * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
-     * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
-     * used: reserved for future improved input validation.
+     * Remove transport mode transforms from a socket, applying the default (empty) policy. This
+     * ensures that NO IPsec policy is applied to the socket (would be the equivalent of applying a
+     * policy that performs no IPsec). Today the resourceId parameter is passed but not used:
+     * reserved for future improved input validation.
      */
     @Override
-    public synchronized void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
-            throws RemoteException {
+    public synchronized void removeTransportModeTransforms(
+            ParcelFileDescriptor socket, int resourceId) throws RemoteException {
         try {
             mSrvConfig
                     .getNetdInstance()
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c9b9a40..dfe89e0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -397,6 +397,7 @@
 import com.android.internal.os.TransferPipe;
 import com.android.internal.os.Zygote;
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.policy.KeyguardDismissCallback;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -6242,7 +6243,7 @@
 
                     // Clear its pending alarms
                     AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class);
-                    ami.removeAlarmsForUid(uid);
+                    ami.removeAlarmsForUid(appInfo.uid);
                 }
             } catch (RemoteException e) {
             }
@@ -7260,15 +7261,22 @@
             }
 
             ProfilerInfo profilerInfo = null;
-            String agent = null;
+            String preBindAgent = null;
             if (mProfileApp != null && mProfileApp.equals(processName)) {
                 mProfileProc = app;
-                profilerInfo = (mProfilerInfo != null && mProfilerInfo.profileFile != null) ?
-                        new ProfilerInfo(mProfilerInfo) : null;
-                agent = mProfilerInfo != null ? mProfilerInfo.agent : null;
+                if (mProfilerInfo != null) {
+                    // Send a profiler info object to the app if either a file is given, or
+                    // an agent should be loaded at bind-time.
+                    boolean needsInfo = mProfilerInfo.profileFile != null
+                            || mProfilerInfo.attachAgentDuringBind;
+                    profilerInfo = needsInfo ? new ProfilerInfo(mProfilerInfo) : null;
+                    if (!mProfilerInfo.attachAgentDuringBind) {
+                        preBindAgent = mProfilerInfo.agent;
+                    }
+                }
             } else if (app.instr != null && app.instr.mProfileFile != null) {
                 profilerInfo = new ProfilerInfo(app.instr.mProfileFile, null, 0, false, false,
-                        null);
+                        null, false);
             }
 
             boolean enableTrackAllocation = false;
@@ -7337,8 +7345,8 @@
 
             // If we were asked to attach an agent on startup, do so now, before we're binding
             // application code.
-            if (agent != null) {
-                thread.attachAgent(agent);
+            if (preBindAgent != null) {
+                thread.attachAgent(preBindAgent);
             }
 
             checkTime(startTime, "attachApplicationLocked: immediately before bindApplication");
@@ -8386,21 +8394,11 @@
                     // entering picture-in-picture (this will prompt the user to authenticate if the
                     // device is currently locked).
                     try {
-                        dismissKeyguard(token, new IKeyguardDismissCallback.Stub() {
-                            @Override
-                            public void onDismissError() throws RemoteException {
-                                // Do nothing
-                            }
-
+                        dismissKeyguard(token, new KeyguardDismissCallback() {
                             @Override
                             public void onDismissSucceeded() throws RemoteException {
                                 mHandler.post(enterPipRunnable);
                             }
-
-                            @Override
-                            public void onDismissCancelled() throws RemoteException {
-                                // Do nothing
-                            }
                         }, null /* message */);
                     } catch (RemoteException e) {
                         // Local call
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 4f60e17..1240f5e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -109,6 +109,7 @@
     private boolean mAutoStop;
     private boolean mStreaming;   // Streaming the profiling output to a file.
     private String mAgent;  // Agent to attach on startup.
+    private boolean mAttachAgentDuringBind;  // Whether agent should be attached late.
     private int mDisplayId;
     private int mWindowingMode;
     private int mActivityType;
@@ -296,7 +297,21 @@
                 } else if (opt.equals("--streaming")) {
                     mStreaming = true;
                 } else if (opt.equals("--attach-agent")) {
+                    if (mAgent != null) {
+                        cmd.getErrPrintWriter().println(
+                                "Multiple --attach-agent(-bind) not supported");
+                        return false;
+                    }
                     mAgent = getNextArgRequired();
+                    mAttachAgentDuringBind = false;
+                } else if (opt.equals("--attach-agent-bind")) {
+                    if (mAgent != null) {
+                        cmd.getErrPrintWriter().println(
+                                "Multiple --attach-agent(-bind) not supported");
+                        return false;
+                    }
+                    mAgent = getNextArgRequired();
+                    mAttachAgentDuringBind = true;
                 } else if (opt.equals("-R")) {
                     mRepeat = Integer.parseInt(getNextArgRequired());
                 } else if (opt.equals("-S")) {
@@ -384,7 +399,7 @@
                     }
                 }
                 profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop,
-                        mStreaming, mAgent);
+                        mStreaming, mAgent, mAttachAgentDuringBind);
             }
 
             pw.println("Starting: " + intent);
@@ -766,7 +781,7 @@
                 return -1;
             }
             profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming,
-                    null);
+                    null, false);
         }
 
         try {
@@ -2544,6 +2559,7 @@
             pw.println("          (use with --start-profiler)");
             pw.println("      -P <FILE>: like above, but profiling stops when app goes idle");
             pw.println("      --attach-agent <agent>: attach the given agent before binding");
+            pw.println("      --attach-agent-bind <agent>: attach the given agent during binding");
             pw.println("      -R: repeat the activity launch <COUNT> times.  Prior to each repeat,");
             pw.println("          the top activity will be finished.");
             pw.println("      -S: force stop the target app before starting the activity");
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index b74c8da..b5fbee6 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1609,9 +1609,8 @@
             mStackSupervisor.mStoppingActivities.remove(this);
             mStackSupervisor.mGoingToSleepActivities.remove(this);
 
-            // If an activity is not in the paused state when becoming visible, cycle to the paused
-            // state.
-            if (state != PAUSED) {
+            // If the activity is stopped or stopping, cycle to the paused state.
+            if (state == STOPPED || state == STOPPING) {
                 // An activity must be in the {@link PAUSING} state for the system to validate
                 // the move to {@link PAUSED}.
                 state = PAUSING;
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index 40a93c1..51499f7 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -122,10 +122,12 @@
 
             final long startUptime = mJobStartUptimes.get(jobId);
             final long nowUptime = SystemClock.uptimeMillis();
+            final long runtime = nowUptime - startUptime;
+
             if (startUptime == 0) {
                 wtf("Job " + jobId + " start uptime not found: "
                         + " params=" + jobParametersToString(params));
-            } else if ((nowUptime - startUptime) > 60 * 1000) {
+            } else if (runtime > 60 * 1000) {
                 // WTF if startSyncH() hasn't happened, *unless* onStopJob() was called too soon.
                 // (1 minute threshold.)
                 if (!mStartedSyncs.get(jobId)) {
@@ -134,6 +136,12 @@
                             + " nowUptime=" + nowUptime
                             + " params=" + jobParametersToString(params));
                 }
+            } else if (runtime < 10 * 1000) {
+                // Job stopped too soon. WTF.
+                wtf("Job " + jobId + " stopped in " + runtime + " ms: "
+                        + " startUptime=" + startUptime
+                        + " nowUptime=" + nowUptime
+                        + " params=" + jobParametersToString(params));
             }
 
             mStartedSyncs.delete(jobId);
@@ -183,6 +191,7 @@
             return "job:null";
         } else {
             return "job:#" + params.getJobId() + ":"
+                    + "sr=[" + params.getStopReason() + "/" + params.getDebugStopReason() + "]:"
                     + SyncOperation.maybeCreateFromJobExtras(params.getExtras());
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 02e4fe0..a55fec5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2009,5 +2009,14 @@
                 mDisplayPowerController.persistBrightnessSliderEvents();
             }
         }
+
+        @Override
+        public void onOverlayChanged() {
+            synchronized (mSyncRoot) {
+                if (updateLogicalDisplaysLocked()) {
+                    scheduleTraversalLocked(false);
+                }
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 733ed3d..bd1dbf9 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -934,12 +934,14 @@
      * @param uid Uid of the calling client.
      * @param jobId Id of the job, provided at schedule-time.
      */
-    public boolean cancelJob(int uid, int jobId) {
+    public boolean cancelJob(int uid, int jobId, int callingUid) {
         JobStatus toCancel;
         synchronized (mLock) {
             toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
             if (toCancel != null) {
-                cancelJobImplLocked(toCancel, null, "cancel() called by app");
+                cancelJobImplLocked(toCancel, null,
+                        "cancel() called by app, callingUid=" + callingUid
+                        + " uid=" + uid + " jobId=" + jobId);
             }
             return (toCancel != null);
         }
@@ -2341,7 +2343,8 @@
             final int uid = Binder.getCallingUid();
             long ident = Binder.clearCallingIdentity();
             try {
-                JobSchedulerService.this.cancelJobsForUid(uid, "cancelAll() called by app");
+                JobSchedulerService.this.cancelJobsForUid(uid,
+                        "cancelAll() called by app, callingUid=" + uid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2353,7 +2356,7 @@
 
             long ident = Binder.clearCallingIdentity();
             try {
-                JobSchedulerService.this.cancelJob(uid, jobId);
+                JobSchedulerService.this.cancelJob(uid, jobId, uid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2466,7 +2469,7 @@
             for (int i=0; i<mActiveServices.size(); i++) {
                 final JobServiceContext jc = mActiveServices.get(i);
                 final JobStatus js = jc.getRunningJobLocked();
-                if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId)) {
+                if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) {
                     foundSome = true;
                     pw.print("Timing out: ");
                     js.printUniqueId(pw);
@@ -2506,7 +2509,7 @@
             }
         } else {
             pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
-            if (!cancelJob(pkgUid, jobId)) {
+            if (!cancelJob(pkgUid, jobId, Process.SHELL_UID)) {
                 pw.println("No matching job found.");
             }
         }
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 709deeb..83a3c19 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -312,13 +312,14 @@
         return mTimeoutElapsed;
     }
 
-    boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId) {
+    boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId,
+            String reason) {
         final JobStatus executing = getRunningJobLocked();
         if (executing != null && (userId == UserHandle.USER_ALL || userId == executing.getUserId())
                 && (pkgName == null || pkgName.equals(executing.getSourcePackageName()))
                 && (!matchJobId || jobId == executing.getJobId())) {
             if (mVerb == VERB_EXECUTING) {
-                mParams.setStopReason(JobParameters.REASON_TIMEOUT);
+                mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
                 sendStopMessageLocked("force timeout from shell");
                 return true;
             }
@@ -537,7 +538,7 @@
             }
             return;
         }
-        mParams.setStopReason(arg1);
+        mParams.setStopReason(arg1, debugReason);
         if (arg1 == JobParameters.REASON_PREEMPT) {
             mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
                     NO_PREFERRED_UID;
@@ -687,7 +688,7 @@
                 // Not an error - client ran out of time.
                 Slog.i(TAG, "Client timed out while executing (no jobFinished received), " +
                         "sending onStop: " + getRunningJobNameLocked());
-                mParams.setStopReason(JobParameters.REASON_TIMEOUT);
+                mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");
                 sendStopMessageLocked("timeout while executing");
                 break;
             default:
diff --git a/services/core/java/com/android/server/media/MediaUpdateService.java b/services/core/java/com/android/server/media/MediaUpdateService.java
new file mode 100644
index 0000000..016d062
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaUpdateService.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.IMediaResourceMonitor;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+import android.util.Slog;
+import com.android.server.SystemService;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.PatternMatcher;
+import android.os.ServiceManager;
+import android.media.IMediaExtractorUpdateService;
+
+import java.lang.Exception;
+
+/** This class provides a system service that manages media framework updates. */
+public class MediaUpdateService extends SystemService {
+    private static final String TAG = "MediaUpdateService";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final String MEDIA_UPDATE_PACKAGE_NAME = "com.android.media.update";
+    private static final String EXTRACTOR_UPDATE_SERVICE_NAME = "media.extractor.update";
+
+    private IMediaExtractorUpdateService mMediaExtractorUpdateService;
+
+    public MediaUpdateService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        // TODO: Uncomment below once sepolicy change is landed.
+        /*
+        if ("userdebug".equals(android.os.Build.TYPE) || "eng".equals(android.os.Build.TYPE)) {
+            connect();
+            registerBroadcastReceiver();
+        }
+        */
+    }
+
+    private void connect() {
+        IBinder binder = ServiceManager.getService(EXTRACTOR_UPDATE_SERVICE_NAME);
+        if (binder != null) {
+            try {
+                binder.linkToDeath(new IBinder.DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        Slog.w(TAG, "mediaextractor died; reconnecting");
+                        mMediaExtractorUpdateService = null;
+                        connect();
+                    }
+                }, 0);
+            } catch (Exception e) {
+                binder = null;
+            }
+        }
+        if (binder != null) {
+            mMediaExtractorUpdateService = IMediaExtractorUpdateService.Stub.asInterface(binder);
+            packageStateChanged();
+        } else {
+            Slog.w(TAG, EXTRACTOR_UPDATE_SERVICE_NAME + " not found.");
+        }
+    }
+
+    private void registerBroadcastReceiver() {
+        BroadcastReceiver updateReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM)
+                            != UserHandle.USER_SYSTEM) {
+                        // Ignore broadcast for non system users. We don't want to update system
+                        // service multiple times.
+                        return;
+                    }
+                    switch (intent.getAction()) {
+                        case Intent.ACTION_PACKAGE_REMOVED:
+                            if (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) {
+                                // The existing package is updated. Will be handled with the
+                                // following ACTION_PACKAGE_ADDED case.
+                                return;
+                            }
+                            packageStateChanged();
+                            break;
+                        case Intent.ACTION_PACKAGE_CHANGED:
+                            packageStateChanged();
+                            break;
+                        case Intent.ACTION_PACKAGE_ADDED:
+                            packageStateChanged();
+                            break;
+                    }
+                }
+        };
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addDataScheme("package");
+        filter.addDataSchemeSpecificPart(MEDIA_UPDATE_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
+
+        getContext().registerReceiverAsUser(updateReceiver, UserHandle.ALL, filter,
+                null /* broadcast permission */, null /* handler */);
+    }
+
+    private void packageStateChanged() {
+        ApplicationInfo packageInfo = null;
+        boolean pluginsAvailable = false;
+        try {
+            packageInfo = getContext().getPackageManager().getApplicationInfo(
+                    MEDIA_UPDATE_PACKAGE_NAME, PackageManager.MATCH_SYSTEM_ONLY);
+            pluginsAvailable = packageInfo.enabled;
+        } catch (Exception e) {
+            Slog.v(TAG, "package '" + MEDIA_UPDATE_PACKAGE_NAME + "' not installed");
+        }
+        loadExtractorPlugins(
+                (packageInfo != null && pluginsAvailable) ? packageInfo.sourceDir : "");
+    }
+
+    private void loadExtractorPlugins(String apkPath) {
+        try {
+            if (mMediaExtractorUpdateService != null) {
+                mMediaExtractorUpdateService.loadPlugins(apkPath);
+            }
+        } catch (Exception e) {
+            Slog.w(TAG, "Error in loadPlugins", e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 575c44d..16b9257 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2917,12 +2917,34 @@
             }
         }
 
+        /**
+         * Sets the notification policy.  Apps that target API levels below
+         * {@link android.os.Build.VERSION_CODES#P} cannot make DND silence
+         * {@link Policy#PRIORITY_CATEGORY_ALARMS} or
+         * {@link Policy#PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER}
+         */
         @Override
         public void setNotificationPolicy(String pkg, Policy policy) {
             enforcePolicyAccess(pkg, "setNotificationPolicy");
             final long identity = Binder.clearCallingIdentity();
             try {
+                final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkg,
+                        0, UserHandle.getUserId(MY_UID));
+
+                if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.O_MR1) {
+                    Policy currPolicy = mZenModeHelper.getNotificationPolicy();
+
+                    int priorityCategories = policy.priorityCategories
+                            | (currPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS)
+                            | (currPolicy.priorityCategories &
+                            Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER);
+                    policy = new Policy(priorityCategories,
+                            policy.priorityCallSenders, policy.priorityMessageSenders,
+                            policy.suppressedVisualEffects);
+                }
+
                 mZenModeHelper.setNotificationPolicy(policy);
+            } catch (RemoteException e) {
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index be66fe2..4b3758d 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -281,13 +281,14 @@
     public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
             int dexoptNeeded, @Nullable String outputPath, int dexFlags,
             String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries,
-            @Nullable String seInfo, boolean downgrade)
+            @Nullable String seInfo, boolean downgrade, int targetSdkVersion)
             throws InstallerException {
         assertValidInstructionSet(instructionSet);
         if (!checkBeforeRemote()) return;
         try {
             mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
-                    dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade);
+                    dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade,
+                    targetSdkVersion);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index b06b583..1717b3d 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -652,6 +652,7 @@
                             activityInfo.name.equals(component.getClassName())) {
                         // Found an activity with category launcher that matches
                         // this component so ok to launch.
+                        launchIntent.setPackage(null);
                         launchIntent.setComponent(component);
                         mContext.startActivityAsUser(launchIntent, opts, user);
                         return;
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 03f662a..0395011 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -260,12 +260,13 @@
             public void dexopt(String apkPath, int uid, @Nullable String pkgName,
                     String instructionSet, int dexoptNeeded, @Nullable String outputPath,
                     int dexFlags, String compilerFilter, @Nullable String volumeUuid,
-                    @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade)
+                    @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade,
+                    int targetSdkVersion)
                     throws InstallerException {
                 final StringBuilder builder = new StringBuilder();
 
-                // The version. Right now it's 3.
-                builder.append("3 ");
+                // The version. Right now it's 4.
+                builder.append("4 ");
 
                 builder.append("dexopt");
 
@@ -281,6 +282,7 @@
                 encodeParameter(builder, sharedLibraries);
                 encodeParameter(builder, seInfo);
                 encodeParameter(builder, downgrade);
+                encodeParameter(builder, targetSdkVersion);
 
                 commands.add(builder.toString());
             }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 730a9fd..91df87b 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -274,7 +274,7 @@
             // primary dex files.
             mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
                     compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo,
-                    false /* downgrade*/);
+                    false /* downgrade*/, pkg.applicationInfo.targetSdkVersion);
 
             if (packageStats != null) {
                 long endTime = System.currentTimeMillis();
@@ -395,7 +395,7 @@
                 mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
                         /*oatDir*/ null, dexoptFlags,
                         compilerFilter, info.volumeUuid, classLoaderContext, info.seInfoUser,
-                        options.isDowngrade());
+                        options.isDowngrade(), info.targetSdkVersion);
             }
 
             return DEX_OPT_PERFORMED;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1e2f2b2..6e94523 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2697,6 +2697,12 @@
                                 mSettings.getDisabledSystemPkgLPr(ps.name);
                         if (disabledPs.codePath == null || !disabledPs.codePath.exists()
                                 || disabledPs.pkg == null) {
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "Possibly deleted app: " + ps.dumpState_temp()
+        + "; path: " + (disabledPs.codePath == null ? "<<NULL>>":disabledPs.codePath)
+        + "; pkg: " + (disabledPs.pkg==null?"<<NULL>>":disabledPs.pkg.toString()));
+}
                             possiblyDeletedUpdatedSystemApps.add(ps.name);
                         }
                     }
@@ -2748,6 +2754,10 @@
                 for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
                     PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
                     mSettings.removeDisabledSystemPackageLPw(deletedAppName);
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "remove update; name: " + deletedAppName + ", exists? " + (deletedPkg != null));
+}
                     final String msg;
                     if (deletedPkg == null) {
                         // should have found an update, but, we didn't; remove everything
@@ -8311,6 +8321,8 @@
         return scannedPkg;
     }
 
+    // Temporary to catch potential issues with refactoring
+    private static boolean REFACTOR_DEBUG = true;
     /**
      * Adds a new package to the internal data structures during platform initialization.
      * <p>After adding, the package is known to the system and available for querying.
@@ -8351,6 +8363,10 @@
         synchronized (mPackages) {
             renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage);
             final String realPkgName = getRealPackageName(pkg, renamedPkgName);
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "Add pkg: " + pkg.packageName + (realPkgName==null?"":", realName: " + realPkgName));
+}
             if (realPkgName != null) {
                 ensurePackageRenamed(pkg, renamedPkgName);
             }
@@ -8365,6 +8381,12 @@
             if (DEBUG_INSTALL && isSystemPkgUpdated) {
                 Slog.d(TAG, "updatedPkg = " + disabledPkgSetting);
             }
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "SSP? " + scanSystemPartition
+        + ", exists? " + pkgAlreadyExists + (pkgAlreadyExists?" "+pkgSetting.toString():"")
+        + ", upgraded? " + isSystemPkgUpdated + (isSystemPkgUpdated?" "+disabledPkgSetting.toString():""));
+}
 
             final SharedUserSetting sharedUserSetting = (pkg.mSharedUserId != null)
                     ? mSettings.getSharedUserLPw(pkg.mSharedUserId,
@@ -8376,6 +8398,12 @@
                 Log.d(TAG, "Shared UserID " + pkg.mSharedUserId
                         + " (uid=" + sharedUserSetting.userId + "):"
                         + " packages=" + sharedUserSetting.packages);
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "Shared UserID " + pkg.mSharedUserId
+        + " (uid=" + sharedUserSetting.userId + "):"
+        + " packages=" + sharedUserSetting.packages);
+}
             }
 
             if (scanSystemPartition) {
@@ -8384,6 +8412,10 @@
                 // version on /data, cycle through all of its children packages and
                 // remove children that are no longer defined.
                 if (isSystemPkgUpdated) {
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "Disable child packages");
+}
                     final int scannedChildCount = (pkg.childPackages != null)
                             ? pkg.childPackages.size() : 0;
                     final int disabledChildCount = disabledPkgSetting.childPackageNames != null
@@ -8395,11 +8427,19 @@
                         for (int j = 0; j < scannedChildCount; j++) {
                             PackageParser.Package childPkg = pkg.childPackages.get(j);
                             if (childPkg.packageName.equals(disabledChildPackageName)) {
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "Ignore " + disabledChildPackageName);
+}
                                 disabledPackageAvailable = true;
                                 break;
                             }
                         }
                         if (!disabledPackageAvailable) {
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "Disable " + disabledChildPackageName);
+}
                             mSettings.removeDisabledSystemPackageLPw(disabledChildPackageName);
                         }
                     }
@@ -8408,17 +8448,44 @@
                             disabledPkgSetting /* pkgSetting */, null /* disabledPkgSetting */,
                             null /* originalPkgSetting */, null, parseFlags, scanFlags,
                             (pkg == mPlatformPackage), user);
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "Scan disabled system package");
+Slog.e("TODD",
+        "Pre: " + request.pkgSetting.dumpState_temp());
+}
+final ScanResult result =
                     scanPackageOnlyLI(request, mFactoryTest, -1L);
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "Post: " + (result.success?result.pkgSetting.dumpState_temp():"FAILED scan"));
+}
                 }
             }
         }
 
         final boolean newPkgChangedPaths =
                 pkgAlreadyExists && !pkgSetting.codePathString.equals(pkg.codePath);
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "paths changed? " + newPkgChangedPaths
+        + "; old: " + pkg.codePath
+        + ", new: " + (pkgSetting==null?"<<NULL>>":pkgSetting.codePathString));
+}
         final boolean newPkgVersionGreater =
                 pkgAlreadyExists && pkg.getLongVersionCode() > pkgSetting.versionCode;
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "version greater? " + newPkgVersionGreater
+        + "; old: " + pkg.getLongVersionCode()
+        + ", new: " + (pkgSetting==null?"<<NULL>>":pkgSetting.versionCode));
+}
         final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated
                 && newPkgChangedPaths && newPkgVersionGreater;
+if (REFACTOR_DEBUG) {
+    Slog.e("TODD",
+            "system better? " + isSystemPkgBetter);
+}
         if (isSystemPkgBetter) {
             // The version of the application on /system is greater than the version on
             // /data. Switch back to the application on /system.
@@ -8434,6 +8501,13 @@
                     + " name: " + pkgSetting.name
                     + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
                     + "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "System package changed;"
+        + " name: " + pkgSetting.name
+        + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
+        + "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
+}
 
             final InstallArgs args = createInstallArgsForExisting(
                     packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString,
@@ -8445,6 +8519,10 @@
         }
 
         if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) {
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "THROW exception; system pkg version not good enough");
+}
             // The version of the application on the /system partition is less than or
             // equal to the version on the /data partition. Throw an exception and use
             // the application already installed on the /data partition.
@@ -8470,6 +8548,11 @@
                 logCriticalInfo(Log.WARN,
                         "System package signature mismatch;"
                         + " name: " + pkgSetting.name);
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "System package signature mismatch;"
+        + " name: " + pkgSetting.name);
+}
                 try (PackageFreezer freezer = freezePackage(pkg.packageName,
                         "scanPackageInternalLI")) {
                     deletePackageLIF(pkg.packageName, null, true, null, 0, null, false, null);
@@ -8484,6 +8567,13 @@
                         + " name: " + pkgSetting.name
                         + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
                         + "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "System package enabled;"
+        + " name: " + pkgSetting.name
+        + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
+        + "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
+}
                 InstallArgs args = createInstallArgsForExisting(
                         packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString,
                         pkgSetting.resourcePathString, getAppDexInstructionSets(pkgSetting));
@@ -8500,13 +8590,35 @@
                         + " name: " + pkgSetting.name
                         + "; old: " + pkgSetting.codePathString + " @ " + pkgSetting.versionCode
                         + "; new: " + pkg.codePath + " @ " + pkg.codePath);
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "System package disabled;"
+        + " name: " + pkgSetting.name
+        + "; old: " + pkgSetting.codePathString + " @ " + pkgSetting.versionCode
+        + "; new: " + pkg.codePath + " @ " + pkg.codePath);
+}
             }
         }
 
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "Scan package");
+Slog.e("TODD",
+        "Pre: " + (pkgSetting==null?"<<NONE>>":pkgSetting.dumpState_temp()));
+}
         final PackageParser.Package scannedPkg = scanPackageNewLI(pkg, parseFlags, scanFlags
                 | SCAN_UPDATE_SIGNATURE, currentTime, user);
+if (REFACTOR_DEBUG) {
+pkgSetting = mSettings.getPackageLPr(pkg.packageName);
+Slog.e("TODD",
+        "Post: " + (pkgSetting==null?"<<NONE>>":pkgSetting.dumpState_temp()));
+}
 
         if (shouldHideSystemApp) {
+if (REFACTOR_DEBUG) {
+Slog.e("TODD",
+        "Disable package: " + pkg.packageName);
+}
             synchronized (mPackages) {
                 mSettings.disableSystemPackageLPw(pkg.packageName, true);
             }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 2b91b7d..2a2430c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -97,6 +97,35 @@
             + " " + name + "/" + appId + "}";
     }
 
+    // Temporary to catch potential issues with refactoring
+    public String dumpState_temp() {
+        String flags = "";
+        flags += ((pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : "");
+        flags += ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : "");
+        if ("".equals(flags)) {
+            flags = "-";
+        }
+        String privFlags = "";
+        privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : "");
+        privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : "");
+        privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : "");
+        if ("".equals(privFlags)) {
+            privFlags = "-";
+        }
+        return "PackageSetting{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + name + (realName == null ? "" : "("+realName+")") + "/" + appId + (sharedUser==null?"":" u:" + sharedUser.name+"("+sharedUserId+")")
+                + ", ver:" + versionCode
+                + ", path: " + codePath
+                + ", pABI: " + primaryCpuAbiString
+                + ", sABI: " + secondaryCpuAbiString
+                + ", oABI: " + cpuAbiOverrideString
+                + ", flags: " + flags
+                + ", privFlags: " + privFlags
+                + ", pkg: " + (pkg == null ? "<<NULL>>" : pkg.dumpState_temp())
+                + "}";
+    }
+
     public void copyFrom(PackageSetting orig) {
         super.copyFrom(orig);
         doCopy(orig);
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 50690cb..cc07d82 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -25,12 +25,14 @@
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
+import android.provider.Settings;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.util.Log;
@@ -119,7 +121,8 @@
             UserManager.DISALLOW_AIRPLANE_MODE,
             UserManager.DISALLOW_CONFIG_BRIGHTNESS,
             UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
-            UserManager.DISALLOW_AMBIENT_DISPLAY
+            UserManager.DISALLOW_AMBIENT_DISPLAY,
+            UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT
     });
 
     /**
@@ -542,6 +545,22 @@
                             android.provider.Settings.Global.SAFE_BOOT_DISALLOWED,
                             newValue ? 1 : 0);
                     break;
+                case UserManager.DISALLOW_AIRPLANE_MODE:
+                    if (newValue) {
+                        final boolean airplaneMode = Settings.Global.getInt(
+                                context.getContentResolver(),
+                                Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+                        if (airplaneMode) {
+                            android.provider.Settings.Global.putInt(
+                                    context.getContentResolver(),
+                                    android.provider.Settings.Global.AIRPLANE_MODE_ON, 0);
+                            // Post the intent.
+                            Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+                            intent.putExtra("state", 0);
+                            context.sendBroadcastAsUser(intent, UserHandle.ALL);
+                        }
+                    }
+                    break;
             }
         } finally {
             Binder.restoreCallingIdentity(id);
diff --git a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
index d6281c5..a517d6d 100644
--- a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
@@ -111,6 +111,7 @@
 
         final long ident = mInjector.clearCallingIdentity();
         try {
+            launchIntent.setPackage(null);
             launchIntent.setComponent(component);
             mContext.startActivityAsUser(launchIntent,
                     ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(), user);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0470607..a453c33 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -48,7 +48,6 @@
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
@@ -65,6 +64,9 @@
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
@@ -267,6 +269,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
+import com.android.internal.policy.KeyguardDismissCallback;
 import com.android.internal.policy.PhoneWindow;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.ArrayUtils;
@@ -4209,20 +4212,23 @@
             if (isKeyguardShowingAndNotOccluded()) {
                 // don't launch home if keyguard showing
                 return;
-            }
-
-            if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) {
+            } else if (mKeyguardOccluded && mKeyguardDelegate.isShowing()) {
+                mKeyguardDelegate.dismiss(new KeyguardDismissCallback() {
+                    @Override
+                    public void onDismissSucceeded() throws RemoteException {
+                        mHandler.post(() -> {
+                            startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
+                        });
+                    }
+                }, null /* message */);
+                return;
+            } else if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) {
                 // when in keyguard restricted mode, must first verify unlock
                 // before launching home
                 mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
                     @Override
                     public void onKeyguardExitResult(boolean success) {
                         if (success) {
-                            try {
-                                ActivityManager.getService().stopAppSwitches();
-                            } catch (RemoteException e) {
-                            }
-                            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
                             startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
                         }
                     }
@@ -4232,11 +4238,11 @@
         }
 
         // no keyguard stuff to worry about, just launch home!
-        try {
-            ActivityManager.getService().stopAppSwitches();
-        } catch (RemoteException e) {
-        }
         if (mRecentsVisible) {
+            try {
+                ActivityManager.getService().stopAppSwitches();
+            } catch (RemoteException e) {}
+
             // Hide Recents and notify it to launch Home
             if (awakenFromDreams) {
                 awakenDreams();
@@ -4244,7 +4250,6 @@
             hideRecentApps(false, true);
         } else {
             // Otherwise, just launch Home
-            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
             startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
         }
     }
@@ -4867,7 +4872,6 @@
 
         final int type = attrs.type;
         final int fl = PolicyControl.getWindowFlags(win, attrs);
-        final long fl2 = attrs.flags2;
         final int pfl = attrs.privateFlags;
         final int sim = attrs.softInputMode;
         final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null);
@@ -4889,12 +4893,10 @@
         final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
 
         final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0
-                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0
-                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0;
+                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
 
         final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
         final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
-        final boolean layoutInCutout = (fl2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0;
 
         sf.set(displayFrames.mStable);
 
@@ -5054,9 +5056,6 @@
                         // moving from a window that is not hiding the status bar to one that is.
                         cf.set(displayFrames.mRestricted);
                     }
-                    if (requestedFullscreen && !layoutInCutout) {
-                        pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
-                    }
                     applyStableConstraints(sysUiFl, fl, cf, displayFrames);
                     if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
                         vf.set(displayFrames.mCurrent);
@@ -5142,9 +5141,6 @@
                     of.set(displayFrames.mUnrestricted);
                     df.set(displayFrames.mUnrestricted);
                     pf.set(displayFrames.mUnrestricted);
-                    if (requestedFullscreen && !layoutInCutout) {
-                        pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
-                    }
                 } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
                     of.set(displayFrames.mRestricted);
                     df.set(displayFrames.mRestricted);
@@ -5219,15 +5215,18 @@
             }
         }
 
-        // Ensure that windows that did not request to be laid out in the cutout don't get laid
-        // out there.
-        if (!layoutInCutout) {
+        final int cutoutMode = attrs.layoutInDisplayCutoutMode;
+        // Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in
+        // the cutout safe zone.
+        if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
             final Rect displayCutoutSafeExceptMaybeTop = mTmpRect;
             displayCutoutSafeExceptMaybeTop.set(displayFrames.mDisplayCutoutSafe);
-            if (layoutInScreen && layoutInsetDecor) {
+            if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
+                    && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
                 // At the top we have the status bar, so apps that are
-                // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR already expect that there's an inset
-                // there and we don't need to exclude the window from that area.
+                // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN
+                // already expect that there's an inset there and we don't need to exclude
+                // the window from that area.
                 displayCutoutSafeExceptMaybeTop.top = Integer.MIN_VALUE;
             }
             pf.intersectUnchecked(displayCutoutSafeExceptMaybeTop);
@@ -7637,6 +7636,11 @@
     }
 
     void startDockOrHome(boolean fromHomeKey, boolean awakenFromDreams) {
+        try {
+            ActivityManager.getService().stopAppSwitches();
+        } catch (RemoteException e) {}
+        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+
         if (awakenFromDreams) {
             awakenDreams();
         }
@@ -7676,11 +7680,6 @@
         }
         if (false) {
             // This code always brings home to the front.
-            try {
-                ActivityManager.getService().stopAppSwitches();
-            } catch (RemoteException e) {
-            }
-            sendCloseSystemWindows();
             startDockOrHome(false /*fromHomeKey*/, true /* awakenFromDreams */);
         } else {
             // This code brings home to the front or, if it is already
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 64a280c..60dae53 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -138,11 +138,6 @@
  * </dl>
  */
 public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
-    // Navigation bar position values
-    int NAV_BAR_LEFT = 1 << 0;
-    int NAV_BAR_RIGHT = 1 << 1;
-    int NAV_BAR_BOTTOM = 1 << 2;
-
     @Retention(SOURCE)
     @IntDef({NAV_BAR_LEFT, NAV_BAR_RIGHT, NAV_BAR_BOTTOM})
     @interface NavigationBarPosition {}
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java
index 1b5a521..48a196d 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java
@@ -32,6 +32,7 @@
 import android.view.Surface;
 
 import java.io.PrintWriter;
+import java.util.List;
 
 /**
  * A special helper class used by the WindowManager
@@ -90,7 +91,28 @@
         mHandler = handler;
         mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
         mRate = rate;
-        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_DEVICE_ORIENTATION);
+        List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION);
+        Sensor wakeUpDeviceOrientationSensor = null;
+        Sensor nonWakeUpDeviceOrientationSensor = null;
+        /**
+         *  Prefer the wakeup form of the sensor if implemented.
+         *  It's OK to look for just two types of this sensor and use
+         *  the last found. Typical devices will only have one sensor of
+         *  this type.
+         */
+        for (Sensor s : l) {
+            if (s.isWakeUpSensor()) {
+                wakeUpDeviceOrientationSensor = s;
+            } else {
+                nonWakeUpDeviceOrientationSensor = s;
+            }
+        }
+
+        if (wakeUpDeviceOrientationSensor != null) {
+            mSensor = wakeUpDeviceOrientationSensor;
+        } else {
+            mSensor = nonWakeUpDeviceOrientationSensor;
+        }
 
         if (mSensor != null) {
             mOrientationJudge = new OrientationSensorJudge();
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 2c84cb1..a254ba2 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1427,13 +1427,13 @@
             mLetterbox.setDimensions(mPendingTransaction, getParent().getBounds(), w.mFrame);
         } else if (mLetterbox != null) {
             final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            // Make sure we have a transaction here, in case we're called outside of a transaction.
+            // This does not use mPendingTransaction, because SurfaceAnimator uses a
+            // global transaction in onAnimationEnd.
             SurfaceControl.openTransaction();
             try {
                 mLetterbox.hide(t);
             } finally {
-                // TODO: This should use pendingTransaction eventually, but right now things
-                // happening on the animation finished callback are happening on the global
-                // transaction.
                 SurfaceControl.mergeToGlobalTransaction(t);
                 SurfaceControl.closeTransaction();
             }
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 28b4c1d..0171b56 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -136,7 +136,7 @@
             outSurface.copyFrom(surface);
             final IBinder winBinder = window.asBinder();
             IBinder token = new Binder();
-            mDragState = new DragState(mService, token, surface, flags, winBinder);
+            mDragState = new DragState(mService, this, token, surface, flags, winBinder);
             mDragState.mPid = callerPid;
             mDragState.mUid = callerUid;
             mDragState.mOriginalAlpha = alpha;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index b9f437a..1ac9b88 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -42,6 +42,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.IUserManager;
+import android.os.UserManagerInternal;
 import android.util.Slog;
 import android.view.Display;
 import android.view.DragEvent;
@@ -55,6 +56,7 @@
 import android.view.animation.Interpolator;
 
 import com.android.internal.view.IDragAndDropPermissions;
+import com.android.server.LocalServices;
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.input.InputWindowHandle;
 
@@ -116,10 +118,10 @@
     private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
     private Point mDisplaySize = new Point();
 
-    DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
-            int flags, IBinder localWin) {
+    DragState(WindowManagerService service, DragDropController controller, IBinder token,
+            SurfaceControl surface, int flags, IBinder localWin) {
         mService = service;
-        mDragDropController = service.mDragDropController;
+        mDragDropController = controller;
         mToken = token;
         mSurfaceControl = surface;
         mFlags = flags;
@@ -318,15 +320,9 @@
 
         mSourceUserId = UserHandle.getUserId(mUid);
 
-        final IUserManager userManager =
-                (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
-        try {
-            mCrossProfileCopyAllowed = !userManager.getUserRestrictions(mSourceUserId).getBoolean(
-                    UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
-        } catch (RemoteException e) {
-            Slog.e(TAG_WM, "Remote Exception calling UserManager: " + e);
-            mCrossProfileCopyAllowed = false;
-        }
+        final UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
+        mCrossProfileCopyAllowed = !userManager.getUserRestriction(
+                mSourceUserId, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
 
         if (DEBUG_DRAG) {
             Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
@@ -534,7 +530,8 @@
         final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
 
         final DragAndDropPermissionsHandler dragAndDropPermissions;
-        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
+        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
+                && mData != null) {
             dragAndDropPermissions = new DragAndDropPermissionsHandler(
                     mData,
                     mUid,
@@ -546,7 +543,9 @@
             dragAndDropPermissions = null;
         }
         if (mSourceUserId != targetUserId){
-            mData.fixUris(mSourceUserId);
+            if (mData != null) {
+                mData.fixUris(mSourceUserId);
+            }
         }
         final int myPid = Process.myPid();
         final IBinder token = touchedWin.mClient.asBinder();
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 69cbe46..62519e1 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -360,14 +360,19 @@
      * Sets the Ime state and height.
      */
     void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
-        // Return early if there is no state change
-        if (mIsImeShowing == adjustedForIme && mImeHeight == imeHeight) {
+        // Due to the order of callbacks from the system, we may receive an ime height even when
+        // {@param adjustedForIme} is false, and also a zero height when {@param adjustedForIme}
+        // is true.  Instead, ensure that the ime state changes with the height and if the ime is
+        // showing, then the height is non-zero.
+        final boolean imeShowing = adjustedForIme && imeHeight > 0;
+        imeHeight = imeShowing ? imeHeight : 0;
+        if (imeShowing == mIsImeShowing && imeHeight == mImeHeight) {
             return;
         }
 
-        mIsImeShowing = adjustedForIme;
+        mIsImeShowing = imeShowing;
         mImeHeight = imeHeight;
-        notifyImeVisibilityChanged(adjustedForIme, imeHeight);
+        notifyImeVisibilityChanged(imeShowing, imeHeight);
         notifyMovementBoundsChanged(true /* fromImeAdjustment */);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7b33533..58673db 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5894,6 +5894,7 @@
      * the screen is.
      * @see WindowManagerPolicy#getNavBarPosition()
      */
+    @Override
     @WindowManagerPolicy.NavigationBarPosition
     public int getNavBarPosition() {
         synchronized (mWindowMap) {
@@ -6599,6 +6600,7 @@
     public void onOverlayChanged() {
         synchronized (mWindowMap) {
             mPolicy.onOverlayChangedLw();
+            mDisplayManagerInternal.onOverlayChanged();
             requestTraversal();
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5d74238..d068c49 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -21,14 +21,12 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.SurfaceControl.Transaction;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
-import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
@@ -43,6 +41,8 @@
 import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.FORMAT_CHANGED;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -2994,16 +2994,19 @@
             // No cutout, no letterbox.
             return false;
         }
-        if ((mAttrs.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0) {
+        if (mAttrs.layoutInDisplayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
             // Layout in cutout, no letterbox.
             return false;
         }
         // TODO: handle dialogs and other non-filling windows
+        if (mAttrs.layoutInDisplayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER) {
+            // Never layout in cutout, always letterbox.
+            return true;
+        }
         // Letterbox if any fullscreen mode is set.
         final int fl = mAttrs.flags;
         final int sysui = mSystemUiVisibility;
-        return (fl & FLAG_FULLSCREEN) != 0
-                || (sysui & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_FULLSCREEN)) != 0;
+        return (fl & FLAG_FULLSCREEN) != 0 || (sysui & (SYSTEM_UI_FLAG_FULLSCREEN)) != 0;
     }
 
     boolean isDragResizeChanged() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bf2b137..62a97f8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -226,6 +226,7 @@
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -790,6 +791,7 @@
         private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
         private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
         private static final String TAG_IS_LOGOUT_ENABLED = "is_logout_enabled";
+        private static final String TAG_MANDATORY_BACKUP_TRANSPORT = "mandatory_backup_transport";
 
         DeviceAdminInfo info;
 
@@ -906,6 +908,10 @@
         // The blacklist data is stored in a file whose name is stored in the XML
         String passwordBlacklistFile = null;
 
+        // The component name of the backup transport which has to be used if backups are mandatory
+        // or null if backups are not mandatory.
+        ComponentName mandatoryBackupTransport = null;
+
         ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
             info = _info;
             isParent = parent;
@@ -1169,6 +1175,11 @@
                 out.attribute(null, ATTR_VALUE, Boolean.toString(isLogoutEnabled));
                 out.endTag(null, TAG_IS_LOGOUT_ENABLED);
             }
+            if (mandatoryBackupTransport != null) {
+                out.startTag(null, TAG_MANDATORY_BACKUP_TRANSPORT);
+                out.attribute(null, ATTR_VALUE, mandatoryBackupTransport.flattenToString());
+                out.endTag(null, TAG_MANDATORY_BACKUP_TRANSPORT);
+            }
         }
 
         void writePackageListToXml(XmlSerializer out, String outerTag,
@@ -1347,6 +1358,9 @@
                 } else if (TAG_IS_LOGOUT_ENABLED.equals(tag)) {
                     isLogoutEnabled = Boolean.parseBoolean(
                             parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_MANDATORY_BACKUP_TRANSPORT.equals(tag)) {
+                    mandatoryBackupTransport = ComponentName.unflattenFromString(
+                            parser.getAttributeValue(null, ATTR_VALUE));
                 } else {
                     Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -7319,6 +7333,7 @@
         policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
         policy.mAffiliationIds.clear();
         policy.mLockTaskPackages.clear();
+        updateLockTaskPackagesLocked(policy.mLockTaskPackages, userId);
         policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
         saveSettingsLocked(userId);
 
@@ -11337,7 +11352,12 @@
         }
         Preconditions.checkNotNull(admin);
         synchronized (this) {
-            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(
+                    admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            if (!enabled) {
+                activeAdmin.mandatoryBackupTransport = null;
+                saveSettingsLocked(UserHandle.USER_SYSTEM);
+            }
         }
 
         final long ident = mInjector.binderClearCallingIdentity();
@@ -11372,6 +11392,50 @@
     }
 
     @Override
+    public void setMandatoryBackupTransport(
+            ComponentName admin, ComponentName backupTransportComponent) {
+        if (!mHasFeature) {
+            return;
+        }
+        Preconditions.checkNotNull(admin);
+        synchronized (this) {
+            ActiveAdmin activeAdmin =
+                    getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            if (!Objects.equals(backupTransportComponent, activeAdmin.mandatoryBackupTransport)) {
+                activeAdmin.mandatoryBackupTransport = backupTransportComponent;
+                saveSettingsLocked(UserHandle.USER_SYSTEM);
+            }
+        }
+        final long identity = mInjector.binderClearCallingIdentity();
+        try {
+            IBackupManager ibm = mInjector.getIBackupManager();
+            if (ibm != null && backupTransportComponent != null) {
+                if (!ibm.isBackupServiceActive(UserHandle.USER_SYSTEM)) {
+                    ibm.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+                }
+                ibm.selectBackupTransportAsync(backupTransportComponent, null);
+                ibm.setBackupEnabled(true);
+            }
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Failed to set mandatory backup transport.", e);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public ComponentName getMandatoryBackupTransport() {
+        if (!mHasFeature) {
+            return null;
+        }
+        synchronized (this) {
+            ActiveAdmin activeAdmin = getDeviceOwnerAdminLocked();
+            return activeAdmin == null ? null : activeAdmin.mandatoryBackupTransport;
+        }
+    }
+
+
+    @Override
     public boolean bindDeviceAdminServiceAsUser(
             @NonNull ComponentName admin, @NonNull IApplicationThread caller,
             @Nullable IBinder activtiyToken, @NonNull Intent serviceIntent,
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4310a98..3199bfa 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -80,6 +80,7 @@
 import com.android.server.lights.LightsService;
 import com.android.server.media.MediaResourceMonitorService;
 import com.android.server.media.MediaRouterService;
+import com.android.server.media.MediaUpdateService;
 import com.android.server.media.MediaSessionService;
 import com.android.server.media.projection.MediaProjectionManagerService;
 import com.android.server.net.NetworkPolicyManagerService;
@@ -1442,6 +1443,10 @@
             mSystemServiceManager.startService(MediaSessionService.class);
             traceEnd();
 
+            traceBeginAndSlog("StartMediaUpdateService");
+            mSystemServiceManager.startService(MediaUpdateService.class);
+            traceEnd();
+
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
                 traceBeginAndSlog("StartHdmiControlService");
                 mSystemServiceManager.startService(HdmiControlService.class);
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index bf58224..4ff24e9 100644
--- a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -44,6 +44,7 @@
 import android.provider.Settings;
 
 import com.android.server.backup.testing.ShadowAppBackupUtils;
+import com.android.server.backup.testing.ShadowBackupPolicyEnforcer;
 import com.android.server.backup.testing.TransportData;
 import com.android.server.backup.testing.TransportTestUtils.TransportMock;
 import com.android.server.backup.transport.TransportNotRegisteredException;
@@ -66,13 +67,14 @@
 
 import java.io.File;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 @RunWith(FrameworkRobolectricTestRunner.class)
 @Config(
     manifest = Config.NONE,
     sdk = 26,
-    shadows = {ShadowAppBackupUtils.class}
+    shadows = {ShadowAppBackupUtils.class, ShadowBackupPolicyEnforcer.class}
 )
 @SystemLoaderClasses({RefactoredBackupManagerService.class, TransportManager.class})
 @Presubmit
@@ -109,12 +111,15 @@
         File cacheDir = mContext.getCacheDir();
         mBaseStateDir = new File(cacheDir, "base_state_dir");
         mDataDir = new File(cacheDir, "data_dir");
+
+        ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(null);
     }
 
     @After
     public void tearDown() throws Exception {
         mBackupThread.quit();
         ShadowAppBackupUtils.reset();
+        ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(null);
     }
 
     /* Tests for destination string */
@@ -250,15 +255,22 @@
 
     /* Tests for select transport */
 
-    private TransportData mNewTransport;
-    private TransportData mOldTransport;
     private ComponentName mNewTransportComponent;
+    private TransportData mNewTransport;
+    private TransportMock mNewTransportMock;
+    private ComponentName mOldTransportComponent;
+    private TransportData mOldTransport;
+    private TransportMock mOldTransportMock;
 
     private void setUpForSelectTransport() throws Exception {
         mNewTransport = backupTransport();
         mNewTransportComponent = mNewTransport.getTransportComponent();
         mOldTransport = d2dTransport();
-        setUpTransports(mTransportManager, mNewTransport, mOldTransport, localTransport());
+        mOldTransportComponent = mOldTransport.getTransportComponent();
+        List<TransportMock> transportMocks =
+                setUpTransports(mTransportManager, mNewTransport, mOldTransport, localTransport());
+        mNewTransportMock = transportMocks.get(0);
+        mOldTransportMock = transportMocks.get(1);
         when(mTransportManager.selectTransport(eq(mNewTransport.transportName)))
                 .thenReturn(mOldTransport.transportName);
     }
@@ -275,6 +287,8 @@
 
         assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName);
         assertThat(oldTransport).isEqualTo(mOldTransport.transportName);
+        verify(mTransportManager)
+                .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any());
     }
 
     @Test
@@ -304,6 +318,46 @@
         mShadowBackupLooper.runToEndOfTasks();
         assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName);
         verify(callback).onSuccess(eq(mNewTransport.transportName));
+        verify(mTransportManager)
+                .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any());
+    }
+
+    @Test
+    public void testSelectBackupTransportAsync_whenMandatoryTransport() throws Exception {
+        setUpForSelectTransport();
+        ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(mNewTransportComponent);
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent)))
+                .thenReturn(BackupManager.SUCCESS);
+        ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
+
+        backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
+
+        mShadowBackupLooper.runToEndOfTasks();
+        assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName);
+        verify(callback).onSuccess(eq(mNewTransport.transportName));
+        verify(mTransportManager)
+                .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any());
+    }
+
+    @Test
+    public void testSelectBackupTransportAsync_whenOtherThanMandatoryTransport() throws Exception {
+        setUpForSelectTransport();
+        ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(mOldTransportComponent);
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent)))
+                .thenReturn(BackupManager.SUCCESS);
+        ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
+
+        backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
+
+        mShadowBackupLooper.runToEndOfTasks();
+        assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName);
+        verify(callback).onFailure(eq(BackupManager.ERROR_BACKUP_NOT_ALLOWED));
     }
 
     @Test
diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowBackupPolicyEnforcer.java b/services/robotests/src/com/android/server/backup/testing/ShadowBackupPolicyEnforcer.java
new file mode 100644
index 0000000..88b30da
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/testing/ShadowBackupPolicyEnforcer.java
@@ -0,0 +1,24 @@
+package com.android.server.backup.testing;
+
+import android.content.ComponentName;
+
+import com.android.server.backup.BackupPolicyEnforcer;
+import com.android.server.backup.RefactoredBackupManagerService;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(BackupPolicyEnforcer.class)
+public class ShadowBackupPolicyEnforcer {
+
+    private static ComponentName sMandatoryBackupTransport;
+
+    public static void setMandatoryBackupTransport(ComponentName backupTransportComponent) {
+        sMandatoryBackupTransport = backupTransportComponent;
+    }
+
+    @Implementation
+    public ComponentName getMandatoryBackupTransport() {
+        return sMandatoryBackupTransport;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
index b38a413..9923fa8 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -22,6 +22,7 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING;
 import static com.android.server.am.ActivityStack.ActivityState.PAUSING;
 import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
@@ -122,7 +123,15 @@
         mActivity.makeVisibleIfNeeded(null /* starting */);
 
         assertEquals(mActivity.state, PAUSING);
+
         assertTrue(pauseFound.value);
+
+        // Make sure that the state does not change for current non-stopping states.
+        mActivity.state = INITIALIZING;
+
+        mActivity.makeVisibleIfNeeded(null /* starting */);
+
+        assertEquals(mActivity.state, INITIALIZING);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
index 9a6da0e..b6c370e 100644
--- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
@@ -16,15 +16,16 @@
 
 package com.android.server.policy;
 
-import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
+import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
@@ -34,7 +35,6 @@
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.view.Surface;
 import android.view.WindowManager;
 
 import org.junit.Before;
@@ -128,7 +128,23 @@
     }
 
     @Test
-    public void layoutWindowLw_withDisplayCutout_fullscreen() {
+    public void layoutWindowLw_withhDisplayCutout_never() {
+        addDisplayCutout();
+
+        mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_layoutFullscreen() {
         addDisplayCutout();
 
         mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
@@ -137,6 +153,22 @@
         mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
 
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_fullscreen() {
+        addDisplayCutout();
+
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
         assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0);
         assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
@@ -147,8 +179,8 @@
     public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() {
         addDisplayCutout();
 
-        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-        mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
+        mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         mPolicy.addWindow(mAppWindow);
 
         mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -217,7 +249,7 @@
         setRotation(ROTATION_90);
 
         mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-        mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
+        mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         mPolicy.addWindow(mAppWindow);
 
         mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
diff --git a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
index ce76a22..ac29163 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
@@ -16,27 +16,38 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.content.ClipData;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManagerInternal;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.InputChannel;
 import android.view.Surface;
 import android.view.SurfaceSession;
+import android.view.View;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+
 /**
  * Tests for the {@link DragDropController} class.
  *
@@ -46,36 +57,92 @@
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 public class DragDropControllerTests extends WindowTestsBase {
-    private static final int TIMEOUT_MS = 1000;
-    private DragDropController mTarget;
+    private static final int TIMEOUT_MS = 3000;
+    private TestDragDropController mTarget;
     private WindowState mWindow;
     private IBinder mToken;
 
+    static class TestDragDropController extends DragDropController {
+        @GuardedBy("sWm.mWindowMap")
+        private Runnable mCloseCallback;
+
+        TestDragDropController(WindowManagerService service, Looper looper) {
+            super(service, looper);
+        }
+
+        void setOnClosedCallbackLocked(Runnable runnable) {
+            assertTrue(dragDropActiveLocked());
+            mCloseCallback = runnable;
+        }
+
+        @Override
+        void onDragStateClosedLocked(DragState dragState) {
+            super.onDragStateClosedLocked(dragState);
+            if (mCloseCallback != null) {
+                mCloseCallback.run();
+                mCloseCallback = null;
+            }
+        }
+    }
+
+    /**
+     * Creates a window state which can be used as a drop target.
+     */
+    private WindowState createDropTargetWindow(String name, int ownerId) {
+        final WindowTestUtils.TestAppWindowToken token = new WindowTestUtils.TestAppWindowToken(
+                mDisplayContent);
+        final TaskStack stack = createStackControllerOnStackOnDisplay(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer;
+        final Task task = createTaskInStack(stack, ownerId);
+        task.addChild(token, 0);
+
+        final WindowState window = createWindow(
+                null, TYPE_BASE_APPLICATION, token, name, ownerId, false);
+        window.mInputChannel = new InputChannel();
+        window.mHasSurface = true;
+        return window;
+    }
+
     @Before
     public void setUp() throws Exception {
+        final UserManagerInternal userManager = mock(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, userManager);
+
         super.setUp();
-        assertNotNull(sWm.mDragDropController);
-        mTarget = sWm.mDragDropController;
-        mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
+
+        mTarget = new TestDragDropController(sWm, sWm.mH.getLooper());
+        mDisplayContent = spy(mDisplayContent);
+        mWindow = createDropTargetWindow("Drag test window", 0);
+        when(mDisplayContent.getTouchableWinAtPointLocked(0, 0)).thenReturn(mWindow);
+        when(sWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true);
+
         synchronized (sWm.mWindowMap) {
-            // Because sWm is a static object, the previous operation may remain.
-            assertFalse(mTarget.dragDropActiveLocked());
+            sWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
         }
     }
 
     @After
-    public void tearDown() {
-        if (mToken != null) {
-            mTarget.cancelDragAndDrop(mToken);
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        final CountDownLatch latch;
+        synchronized (sWm.mWindowMap) {
+            if (!mTarget.dragDropActiveLocked()) {
+                return;
+            }
+            if (mToken != null) {
+                mTarget.cancelDragAndDrop(mToken);
+            }
+            latch = new CountDownLatch(1);
+            mTarget.setOnClosedCallbackLocked(() -> {
+                latch.countDown();
+            });
         }
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     @Test
-    public void testPrepareDrag() throws Exception {
-        final Surface surface = new Surface();
-        mToken = mTarget.prepareDrag(
-                new SurfaceSession(), 0, 0, mWindow.mClient, 0, 100, 100, surface);
-        assertNotNull(mToken);
+    public void testDragFlow() throws Exception {
+        dragFlow(0, ClipData.newPlainText("label", "Test"), 0, 0);
     }
 
     @Test
@@ -85,4 +152,33 @@
                 new SurfaceSession(), 0, 0, mWindow.mClient, 0, 0, 0, surface);
         assertNull(mToken);
     }
+
+    @Test
+    public void testPerformDrag_NullDataWithGrantUri() throws Exception {
+        dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0);
+    }
+
+    @Test
+    public void testPerformDrag_NullDataToOtherUser() throws Exception {
+        final WindowState otherUsersWindow =
+                createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE);
+        when(mDisplayContent.getTouchableWinAtPointLocked(10, 10))
+                .thenReturn(otherUsersWindow);
+
+        dragFlow(0, null, 10, 10);
+    }
+
+    private void dragFlow(int flag, ClipData data, float dropX, float dropY) {
+        final Surface surface = new Surface();
+        mToken = mTarget.prepareDrag(
+                new SurfaceSession(), 0, 0, mWindow.mClient, flag, 100, 100, surface);
+        assertNotNull(mToken);
+
+        assertTrue(sWm.mInputManager.transferTouchFocus(null, null));
+        assertTrue(mTarget.performDrag(
+                mWindow.mClient, mToken, 0, 0, 0, 0, 0, data));
+
+        mTarget.handleMotionEvent(false, dropX, dropY);
+        mToken = mWindow.mClient.asBinder();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index c699a94..69b1378 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -230,20 +230,22 @@
             boolean ownerCanAddInternalSystemWindow) {
         final WindowToken token = createWindowToken(
                 dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
-        return createWindow(parent, type, token, name, ownerCanAddInternalSystemWindow);
+        return createWindow(parent, type, token, name, 0 /* ownerId */,
+                ownerCanAddInternalSystemWindow);
     }
 
     static WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
-        return createWindow(parent, type, token, name, false /* ownerCanAddInternalSystemWindow */);
+        return createWindow(parent, type, token, name, 0 /* ownerId */,
+                false /* ownerCanAddInternalSystemWindow */);
     }
 
     static WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
-            boolean ownerCanAddInternalSystemWindow) {
+            int ownerId, boolean ownerCanAddInternalSystemWindow) {
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
         attrs.setTitle(name);
 
         final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent, OP_NONE,
-                0, attrs, VISIBLE, 0, ownerCanAddInternalSystemWindow);
+                0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow);
         // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
         // adding it to the token...
         token.addWindow(w);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 5a1a3e3..ce0b551 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -960,8 +960,9 @@
     public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool";
 
     /**
-     * String to identify carrier name in CarrierConfig app. This string is used only if
-     * #KEY_CARRIER_NAME_OVERRIDE_BOOL is true
+     * String to identify carrier name in CarrierConfig app. This string overrides SPN if
+     * #KEY_CARRIER_NAME_OVERRIDE_BOOL is true; otherwise, it will be used if its value is provided
+     * and SPN is unavailable
      * @hide
      */
     public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f411ef7..8edc8b1 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -53,6 +53,7 @@
 
 import com.android.ims.internal.IImsMMTelFeature;
 import com.android.ims.internal.IImsRcsFeature;
+import com.android.ims.internal.IImsRegistration;
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.ITelecomService;
@@ -2514,6 +2515,33 @@
         }
     }
 
+    /**
+     * Resets the Carrier Keys in the database. This involves 2 steps:
+     *  1. Delete the keys from the database.
+     *  2. Send an intent to download new Certificates.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * @hide
+     */
+    public void resetCarrierKeysForImsiEncryption() {
+        try {
+            IPhoneSubInfo info = getSubscriberInfo();
+            if (info == null) {
+                throw new RuntimeException("IMSI error: Subscriber Info is null");
+            }
+            int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId());
+            info.resetCarrierKeysForImsiEncryption(subId, mContext.getOpPackageName());
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getCarrierInfoForImsiEncryption RemoteException" + ex);
+            throw new RuntimeException("IMSI error: Remote Exception");
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            Rlog.e(TAG, "getCarrierInfoForImsiEncryption NullPointerException" + ex);
+            throw new RuntimeException("IMSI error: Null Pointer exception");
+        }
+    }
+
    /**
      * @param keyAvailability bitmask that defines the availabilty of keys for a type.
      * @param keyType the key type which is being checked. (WLAN, EPDG)
@@ -2549,7 +2577,7 @@
      * device keystore.
      * <p>
      * Requires Permission:
-     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      * @param imsiEncryptionInfo which includes the Key Type, the Public Key
      *        (java.security.PublicKey) and the Key Identifier.and the Key Identifier.
      *        The keyIdentifier Attribute value pair that helps a server locate
@@ -4932,6 +4960,25 @@
     }
 
     /**
+     * @return the {@IImsRegistration} interface that corresponds with the slot index and feature.
+     * @param slotIndex The SIM slot corresponding to the ImsService ImsRegistration is active for.
+     * @param feature An integer indicating the feature that we wish to get the ImsRegistration for.
+     * Corresponds to features defined in ImsFeature.
+     * @hide
+     */
+    public @Nullable IImsRegistration getImsRegistration(int slotIndex, int feature) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getImsRegistration(slotIndex, feature);
+            }
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "getImsRegistration, RemoteException: " + e.getMessage());
+        }
+        return null;
+    }
+
+    /**
      * Set IMS registration state
      *
      * @param Registration state
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 8230eaf..aaa0f08 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -26,12 +26,14 @@
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MMTelFeature;
 import android.telephony.ims.feature.RcsFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.ims.internal.IImsMMTelFeature;
 import com.android.ims.internal.IImsRcsFeature;
+import com.android.ims.internal.IImsRegistration;
 import com.android.ims.internal.IImsServiceController;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -113,6 +115,12 @@
                 throws RemoteException {
             ImsService.this.removeImsFeature(slotId, featureType, c);
         }
+
+        @Override
+        public IImsRegistration getRegistration(int slotId) throws RemoteException {
+            ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId);
+            return r != null ? r.getBinder() : null;
+        }
     };
 
     /**
@@ -174,6 +182,8 @@
         f.setSlotId(slotId);
         f.addImsFeatureStatusCallback(c);
         addImsFeature(slotId, featureType, f);
+        // TODO: Remove once new onFeatureReady AIDL is merged in.
+        f.onFeatureReady();
     }
 
     private void addImsFeature(int slotId, int featureType, ImsFeature f) {
@@ -236,4 +246,13 @@
     public @Nullable RcsFeature onCreateRcsFeature(int slotId) {
         return null;
     }
+
+    /**
+     * @param slotId The slot that is associated with the IMS Registration.
+     * @return the ImsRegistration implementation associated with the slot.
+     * @hide
+     */
+    public ImsRegistrationImplBase getRegistration(int slotId) {
+        return new ImsRegistrationImplBase();
+    }
 }
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index ca4a210..d47cea30 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -96,7 +96,7 @@
             new WeakHashMap<IImsFeatureStatusCallback, Boolean>());
     private @ImsState int mState = STATE_NOT_AVAILABLE;
     private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-    private Context mContext;
+    protected Context mContext;
 
     public void setContext(Context context) {
         mContext = context;
diff --git a/telephony/java/android/telephony/ims/internal/ImsService.java b/telephony/java/android/telephony/ims/internal/ImsService.java
index b7c8ca0..afaf332 100644
--- a/telephony/java/android/telephony/ims/internal/ImsService.java
+++ b/telephony/java/android/telephony/ims/internal/ImsService.java
@@ -24,7 +24,6 @@
 import android.telephony.ims.internal.aidl.IImsConfig;
 import android.telephony.ims.internal.aidl.IImsMmTelFeature;
 import android.telephony.ims.internal.aidl.IImsRcsFeature;
-import android.telephony.ims.internal.aidl.IImsRegistration;
 import android.telephony.ims.internal.aidl.IImsServiceController;
 import android.telephony.ims.internal.aidl.IImsServiceControllerListener;
 import android.telephony.ims.internal.feature.ImsFeature;
@@ -32,11 +31,12 @@
 import android.telephony.ims.internal.feature.RcsFeature;
 import android.telephony.ims.internal.stub.ImsConfigImplBase;
 import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
-import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
+import com.android.ims.internal.IImsRegistration;
 import com.android.internal.annotations.VisibleForTesting;
 
 /**
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl
index 8332bc0..43f5098 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl
@@ -24,4 +24,5 @@
  */
 oneway interface IImsMmTelListener {
     void onIncomingCall(IImsCallSession c);
+    void onVoiceMessageCountUpdate(int count);
 }
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl
index 8afb955..82a8525 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl
@@ -18,12 +18,12 @@
 
 import android.telephony.ims.internal.aidl.IImsMmTelFeature;
 import android.telephony.ims.internal.aidl.IImsRcsFeature;
-import android.telephony.ims.internal.aidl.IImsRegistration;
 import android.telephony.ims.internal.aidl.IImsConfig;
 import android.telephony.ims.internal.aidl.IImsServiceControllerListener;
 import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
+import com.android.ims.internal.IImsRegistration;
 
 /**
  * See ImsService and MmTelFeature for more information.
diff --git a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java
index 4d18873..5dbf077 100644
--- a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java
+++ b/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java
@@ -18,7 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.ArraySet;
 
 import java.util.ArrayList;
diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
index 238411e..8d888c2 100644
--- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
@@ -28,8 +28,8 @@
 import android.telephony.ims.internal.aidl.IImsCapabilityCallback;
 import android.telephony.ims.internal.aidl.IImsMmTelFeature;
 import android.telephony.ims.internal.aidl.IImsMmTelListener;
-import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
 import android.telephony.ims.internal.aidl.IImsSmsListener;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.telephony.ims.stub.ImsEcbmImplBase;
 import android.telephony.ims.stub.ImsMultiEndpointImplBase;
 import android.telephony.ims.stub.ImsUtImplBase;
@@ -262,6 +262,15 @@
         }
 
         /**
+         * Updates the Listener when the voice message count for IMS has changed.
+         * @param count an integer representing the new message count.
+         */
+        @Override
+        public void onVoiceMessageCountUpdate(int count) {
+
+        }
+
+        /**
          * Called when the IMS provider receives an incoming call.
          * @param c The {@link ImsCallSession} associated with the new call.
          */
diff --git a/telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
similarity index 83%
rename from telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java
rename to telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index 558b009..42af083 100644
--- a/telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -14,16 +14,19 @@
  * limitations under the License
  */
 
-package android.telephony.ims.internal.stub;
+package android.telephony.ims.stub;
 
 import android.annotation.IntDef;
+import android.net.Uri;
+import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.telephony.ims.internal.aidl.IImsRegistration;
-import android.telephony.ims.internal.aidl.IImsRegistrationCallback;
 import android.util.Log;
 
 import com.android.ims.ImsReasonInfo;
+import com.android.ims.internal.IImsRegistration;
+import com.android.ims.internal.IImsRegistrationCallback;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -62,23 +65,25 @@
 
     // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current
     // state.
+    // The unknown state is set as the initialization state. This is so that we do not call back
+    // with NOT_REGISTERED in the case where the ImsService has not updated the registration state
+    // yet.
+    private static final int REGISTRATION_STATE_UNKNOWN = -1;
     private static final int REGISTRATION_STATE_NOT_REGISTERED = 0;
     private static final int REGISTRATION_STATE_REGISTERING = 1;
     private static final int REGISTRATION_STATE_REGISTERED = 2;
 
-
     /**
      * Callback class for receiving Registration callback events.
+     * @hide
      */
-    public static class Callback extends IImsRegistrationCallback.Stub {
-
+    public static class Callback {
         /**
          * Notifies the framework when the IMS Provider is connected to the IMS network.
          *
          * @param imsRadioTech the radio access technology. Valid values are defined in
          * {@link ImsRegistrationTech}.
          */
-        @Override
         public void onRegistered(@ImsRegistrationTech int imsRadioTech) {
         }
 
@@ -88,7 +93,6 @@
          * @param imsRadioTech the radio access technology. Valid values are defined in
          * {@link ImsRegistrationTech}.
          */
-        @Override
         public void onRegistering(@ImsRegistrationTech int imsRadioTech) {
         }
 
@@ -97,7 +101,6 @@
          *
          * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
          */
-        @Override
         public void onDeregistered(ImsReasonInfo info) {
         }
 
@@ -108,10 +111,19 @@
          * @param imsRadioTech The {@link ImsRegistrationTech} type that has failed
          * @param info A {@link ImsReasonInfo} that identifies the reason for failure.
          */
-        @Override
         public void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
                 ImsReasonInfo info) {
         }
+
+        /**
+         * Returns a list of subscriber {@link Uri}s associated with this IMS subscription when
+         * it changes.
+         * @param uris new array of subscriber {@link Uri}s that are associated with this IMS
+         *         subscription.
+         */
+        public void onSubscriberAssociatedUriChanged(Uri[] uris) {
+
+        }
     }
 
     private final IImsRegistration mBinder = new IImsRegistration.Stub() {
@@ -139,9 +151,9 @@
     private @ImsRegistrationTech
     int mConnectionType = REGISTRATION_TECH_NONE;
     // Locked on mLock
-    private int mRegistrationState = REGISTRATION_STATE_NOT_REGISTERED;
-    // Locked on mLock
-    private ImsReasonInfo mLastDisconnectCause;
+    private int mRegistrationState = REGISTRATION_STATE_UNKNOWN;
+    // Locked on mLock, create unspecified disconnect cause.
+    private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo();
 
     public final IImsRegistration getBinder() {
         return mBinder;
@@ -221,6 +233,17 @@
         });
     }
 
+    public final void onSubscriberAssociatedUriChanged(Uri[] uris) {
+        mCallbacks.broadcast((c) -> {
+            try {
+                c.onSubscriberAssociatedUriChanged(uris);
+            } catch (RemoteException e) {
+                Log.w(LOG_TAG, e + " " + "onSubscriberAssociatedUriChanged() - Skipping " +
+                        "callback.");
+            }
+        });
+    }
+
     private void updateToState(@ImsRegistrationTech int connType, int newState) {
         synchronized (mLock) {
             mConnectionType = connType;
@@ -241,7 +264,8 @@
         }
     }
 
-    private @ImsRegistrationTech int getConnectionType() {
+    @VisibleForTesting
+    public final @ImsRegistrationTech int getConnectionType() {
         synchronized (mLock) {
             return mConnectionType;
         }
@@ -271,6 +295,10 @@
                 c.onRegistered(getConnectionType());
                 break;
             }
+            case REGISTRATION_STATE_UNKNOWN: {
+                // Do not callback if the state has not been updated yet by the ImsService.
+                break;
+            }
         }
     }
 }
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl b/telephony/java/com/android/ims/internal/IImsRegistration.aidl
similarity index 82%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl
rename to telephony/java/com/android/ims/internal/IImsRegistration.aidl
index 687b7ca..6de264e 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl
+++ b/telephony/java/com/android/ims/internal/IImsRegistration.aidl
@@ -15,10 +15,9 @@
  */
 
 
-package android.telephony.ims.internal.aidl;
+package com.android.ims.internal;
 
-import android.telephony.ims.internal.aidl.IImsRegistrationCallback;
-import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
+import com.android.ims.internal.IImsRegistrationCallback;
 
 /**
  * See ImsRegistration for more information.
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl
similarity index 89%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl
rename to telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl
index a50575b..5f21167 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl
+++ b/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl
@@ -15,8 +15,9 @@
  */
 
 
-package android.telephony.ims.internal.aidl;
+package com.android.ims.internal;
 
+import android.net.Uri;
 import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
 
 import com.android.ims.ImsReasonInfo;
@@ -31,4 +32,5 @@
    void onRegistering(int imsRadioTech);
    void onDeregistered(in ImsReasonInfo info);
    void onTechnologyChangeFailed(int imsRadioTech, in ImsReasonInfo info);
+   void onSubscriberAssociatedUriChanged(in Uri[] uris);
 }
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
index 857089f..7ac25ac 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -18,6 +18,7 @@
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRegistration;
 import com.android.ims.internal.IImsRcsFeature;
 
 /**
@@ -29,4 +30,5 @@
     IImsMMTelFeature createMMTelFeature(int slotId, in IImsFeatureStatusCallback c);
     IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c);
     void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c);
+    IImsRegistration getRegistration(int slotId);
 }
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index 0f31821..f8a040d 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -152,6 +152,13 @@
     in ImsiEncryptionInfo imsiEncryptionInfo);
 
     /**
+     * Resets the Carrier Keys in the database. This involves 2 steps:
+     *  1. Delete the keys from the database.
+     *  2. Send an intent to download new Certificates.
+     */
+    void resetCarrierKeysForImsiEncryption(int subId, String callingPackage);
+
+    /**
      * Retrieves the alpha identifier associated with the voice mail number.
      */
     String getVoiceMailAlphaTag(String callingPackage);
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 416146f..fba82ee 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -40,6 +40,7 @@
 import android.telephony.VisualVoicemailSmsFilterSettings;
 import com.android.ims.internal.IImsMMTelFeature;
 import com.android.ims.internal.IImsRcsFeature;
+import com.android.ims.internal.IImsRegistration;
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.OperatorInfo;
@@ -808,6 +809,11 @@
     IImsRcsFeature getRcsFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
 
     /**
+    * Returns the IImsRegistration associated with the slot and feature specified.
+    */
+    IImsRegistration getImsRegistration(int slotId, int feature);
+
+    /**
      * Set the network selection mode to automatic.
      *
      * @param subId the id of the subscription to update.
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index f29d993c..51369d0 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -486,4 +486,10 @@
     */
     public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
             "com.android.omadm.service.CONFIGURATION_UPDATE";
+
+    /**
+     * Broadcast action to trigger the Carrier Certificate download.
+     */
+    public static final String ACTION_CARRIER_CERTIFICATE_DOWNLOAD =
+            "com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD";
 }
diff --git a/test-base/Android.bp b/test-base/Android.bp
index d2da613..ccf57b0 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -49,7 +49,8 @@
 
 // Build the repackaged.android.test.base library
 // ==============================================
-// This contains repackaged versions of the classes from legacy-test.
+// This contains repackaged versions of the classes from
+// android.test.base.
 java_library_static {
     name: "repackaged.android.test.base",
 
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 8eddec4..b1ae40e 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -24,7 +24,6 @@
     no_framework_libs: true,
     libs: [
         "framework",
-        "legacy-test",
     ],
 }
 
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index d495e90..dfaeed5 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -40,7 +40,7 @@
     no_framework_libs: true,
     libs: [
         "framework",
-        "legacy-test",
+        "android.test.base",
         "android.test.mock",
         "junit",
     ],
diff --git a/tests/net/java/android/net/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java
index efc01f2a..f6c5532 100644
--- a/tests/net/java/android/net/IpSecConfigTest.java
+++ b/tests/net/java/android/net/IpSecConfigTest.java
@@ -36,19 +36,16 @@
     public void testDefaults() throws Exception {
         IpSecConfig c = new IpSecConfig();
         assertEquals(IpSecTransform.MODE_TRANSPORT, c.getMode());
-        assertEquals("", c.getLocalAddress());
-        assertEquals("", c.getRemoteAddress());
+        assertEquals("", c.getSourceAddress());
+        assertEquals("", c.getDestinationAddress());
         assertNull(c.getNetwork());
         assertEquals(IpSecTransform.ENCAP_NONE, c.getEncapType());
         assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getEncapSocketResourceId());
         assertEquals(0, c.getEncapRemotePort());
         assertEquals(0, c.getNattKeepaliveInterval());
-        for (int direction :
-                new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}) {
-            assertNull(c.getEncryption(direction));
-            assertNull(c.getAuthentication(direction));
-            assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId(direction));
-        }
+        assertNull(c.getEncryption());
+        assertNull(c.getAuthentication());
+        assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId());
     }
 
     @Test
@@ -57,34 +54,21 @@
 
         IpSecConfig c = new IpSecConfig();
         c.setMode(IpSecTransform.MODE_TUNNEL);
-        c.setLocalAddress("0.0.0.0");
-        c.setRemoteAddress("1.2.3.4");
+        c.setSourceAddress("0.0.0.0");
+        c.setDestinationAddress("1.2.3.4");
         c.setEncapType(android.system.OsConstants.UDP_ENCAP_ESPINUDP);
         c.setEncapSocketResourceId(7);
         c.setEncapRemotePort(22);
         c.setNattKeepaliveInterval(42);
         c.setEncryption(
-                IpSecTransform.DIRECTION_OUT,
                 new IpSecAlgorithm(
                         IpSecAlgorithm.CRYPT_AES_CBC,
                         new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}));
         c.setAuthentication(
-                IpSecTransform.DIRECTION_OUT,
                 new IpSecAlgorithm(
                         IpSecAlgorithm.AUTH_HMAC_MD5,
                         new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0}));
-        c.setSpiResourceId(IpSecTransform.DIRECTION_OUT, 1984);
-        c.setEncryption(
-                IpSecTransform.DIRECTION_IN,
-                new IpSecAlgorithm(
-                        IpSecAlgorithm.CRYPT_AES_CBC,
-                        new byte[] {2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}));
-        c.setAuthentication(
-                IpSecTransform.DIRECTION_IN,
-                new IpSecAlgorithm(
-                        IpSecAlgorithm.AUTH_HMAC_MD5,
-                        new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 1}));
-        c.setSpiResourceId(IpSecTransform.DIRECTION_IN, 99);
+        c.setSpiResourceId(1984);
         assertParcelingIsLossless(c);
     }
 
diff --git a/tests/net/java/android/net/IpSecManagerTest.java b/tests/net/java/android/net/IpSecManagerTest.java
index 0f40b45..cc3366f 100644
--- a/tests/net/java/android/net/IpSecManagerTest.java
+++ b/tests/net/java/android/net/IpSecManagerTest.java
@@ -81,15 +81,13 @@
         IpSecSpiResponse spiResp =
                 new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, DROID_SPI);
         when(mMockIpSecService.allocateSecurityParameterIndex(
-                        eq(IpSecTransform.DIRECTION_IN),
                         eq(GOOGLE_DNS_4.getHostAddress()),
                         eq(DROID_SPI),
                         anyObject()))
                 .thenReturn(spiResp);
 
         IpSecManager.SecurityParameterIndex droidSpi =
-                mIpSecManager.allocateSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_IN, GOOGLE_DNS_4, DROID_SPI);
+                mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4, DROID_SPI);
         assertEquals(DROID_SPI, droidSpi.getSpi());
 
         droidSpi.close();
@@ -103,15 +101,13 @@
         IpSecSpiResponse spiResp =
                 new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, DROID_SPI);
         when(mMockIpSecService.allocateSecurityParameterIndex(
-                        eq(IpSecTransform.DIRECTION_OUT),
                         eq(GOOGLE_DNS_4.getHostAddress()),
                         eq(IpSecManager.INVALID_SECURITY_PARAMETER_INDEX),
                         anyObject()))
                 .thenReturn(spiResp);
 
         IpSecManager.SecurityParameterIndex randomSpi =
-                mIpSecManager.allocateSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4);
+                mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4);
 
         assertEquals(DROID_SPI, randomSpi.getSpi());
 
@@ -124,16 +120,15 @@
      * Throws resource unavailable exception
      */
     @Test
-    public void testAllocSpiResUnavaiableExeption() throws Exception {
+    public void testAllocSpiResUnavailableException() throws Exception {
         IpSecSpiResponse spiResp =
                 new IpSecSpiResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE, 0, 0);
         when(mMockIpSecService.allocateSecurityParameterIndex(
-                        anyInt(), anyString(), anyInt(), anyObject()))
+                        anyString(), anyInt(), anyObject()))
                 .thenReturn(spiResp);
 
         try {
-            mIpSecManager.allocateSecurityParameterIndex(
-                    IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4);
+            mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4);
             fail("ResourceUnavailableException was not thrown");
         } catch (IpSecManager.ResourceUnavailableException e) {
         }
@@ -143,15 +138,14 @@
      * Throws spi unavailable exception
      */
     @Test
-    public void testAllocSpiSpiUnavaiableExeption() throws Exception {
+    public void testAllocSpiSpiUnavailableException() throws Exception {
         IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.SPI_UNAVAILABLE, 0, 0);
         when(mMockIpSecService.allocateSecurityParameterIndex(
-                        anyInt(), anyString(), anyInt(), anyObject()))
+                        anyString(), anyInt(), anyObject()))
                 .thenReturn(spiResp);
 
         try {
-            mIpSecManager.allocateSecurityParameterIndex(
-                    IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4);
+            mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4);
             fail("ResourceUnavailableException was not thrown");
         } catch (IpSecManager.ResourceUnavailableException e) {
         }
@@ -163,8 +157,7 @@
     @Test
     public void testRequestAllocInvalidSpi() throws Exception {
         try {
-            mIpSecManager.allocateSecurityParameterIndex(
-                    IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4, 0);
+            mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4, 0);
             fail("Able to allocate invalid spi");
         } catch (IllegalArgumentException e) {
         }
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index 473dc538..9aad413 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -67,7 +67,7 @@
             assertEquals(msg, t.expectedType, got);
 
             if (got != MacAddress.TYPE_UNKNOWN) {
-                assertEquals(got, MacAddress.fromBytes(t.addr).addressType());
+                assertEquals(got, MacAddress.fromBytes(t.addr).getAddressType());
             }
         }
     }
@@ -191,7 +191,7 @@
 
             assertTrue(stringRepr + " expected to be a locally assigned address",
                     mac.isLocallyAssigned());
-            assertEquals(MacAddress.TYPE_UNICAST, mac.addressType());
+            assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType());
             assertTrue(stringRepr + " expected to begin with " + expectedLocalOui,
                     stringRepr.startsWith(expectedLocalOui));
         }
diff --git a/tests/net/java/android/net/NetworkTest.java b/tests/net/java/android/net/NetworkTest.java
index bacf986..94d01e9 100644
--- a/tests/net/java/android/net/NetworkTest.java
+++ b/tests/net/java/android/net/NetworkTest.java
@@ -147,9 +147,9 @@
 
         // Adjust as necessary to test an implementation's specific constants.
         // When running with runtest, "adb logcat -s TestRunner" can be useful.
-        assertEquals(4311403230L, one.getNetworkHandle());
-        assertEquals(8606370526L, two.getNetworkHandle());
-        assertEquals(12901337822L, three.getNetworkHandle());
+        assertEquals(7700664333L, one.getNetworkHandle());
+        assertEquals(11995631629L, two.getNetworkHandle());
+        assertEquals(16290598925L, three.getNetworkHandle());
     }
 
     private static <T> void assertNotEqual(T t1, T t2) {
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 2b0349c..b8e37f3 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -1392,39 +1392,75 @@
             return null;
         }
 
-        void expectAvailableCallbacks(
-                MockNetworkAgent agent, boolean expectSuspended, int timeoutMs) {
+        // Expects onAvailable and the callbacks that follow it. These are:
+        // - onSuspended, iff the network was suspended when the callbacks fire.
+        // - onCapabilitiesChanged.
+        // - onLinkPropertiesChanged.
+        //
+        // @param agent the network to expect the callbacks on.
+        // @param expectSuspended whether to expect a SUSPENDED callback.
+        // @param expectValidated the expected value of the VALIDATED capability in the
+        //        onCapabilitiesChanged callback.
+        // @param timeoutMs how long to wait for the callbacks.
+        void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended,
+                boolean expectValidated, int timeoutMs) {
             expectCallback(CallbackState.AVAILABLE, agent, timeoutMs);
             if (expectSuspended) {
                 expectCallback(CallbackState.SUSPENDED, agent, timeoutMs);
             }
-            expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs);
+            if (expectValidated) {
+                expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
+            } else {
+                expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, agent);
+            }
             expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs);
         }
 
-        void expectAvailableCallbacks(MockNetworkAgent agent) {
-            expectAvailableCallbacks(agent, false, TIMEOUT_MS);
+        // Expects the available callbacks (validated), plus onSuspended.
+        void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent, boolean expectValidated) {
+            expectAvailableCallbacks(agent, true, expectValidated, TIMEOUT_MS);
         }
 
-        void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent) {
-            expectAvailableCallbacks(agent, true, TIMEOUT_MS);
+        void expectAvailableCallbacksValidated(MockNetworkAgent agent) {
+            expectAvailableCallbacks(agent, false, true, TIMEOUT_MS);
         }
 
-        void expectAvailableAndValidatedCallbacks(MockNetworkAgent agent) {
-            expectAvailableCallbacks(agent, false, TIMEOUT_MS);
+        void expectAvailableCallbacksUnvalidated(MockNetworkAgent agent) {
+            expectAvailableCallbacks(agent, false, false, TIMEOUT_MS);
+        }
+
+        // Expects the available callbacks (where the onCapabilitiesChanged must contain the
+        // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the
+        // one we just sent.
+        // TODO: this is likely a bug. Fix it and remove this method.
+        void expectAvailableDoubleValidatedCallbacks(MockNetworkAgent agent) {
+            expectCallback(CallbackState.AVAILABLE, agent, TIMEOUT_MS);
+            NetworkCapabilities nc1 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
+            expectCallback(CallbackState.LINK_PROPERTIES, agent, TIMEOUT_MS);
+            NetworkCapabilities nc2 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
+            assertEquals(nc1, nc2);
+        }
+
+        // Expects the available callbacks where the onCapabilitiesChanged must not have validated,
+        // then expects another onCapabilitiesChanged that has the validated bit set. This is used
+        // when a network connects and satisfies a callback, and then immediately validates.
+        void expectAvailableThenValidatedCallbacks(MockNetworkAgent agent) {
+            expectAvailableCallbacksUnvalidated(agent);
             expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
         }
 
-        void expectCapabilitiesWith(int capability, MockNetworkAgent agent) {
+        NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent) {
             CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
             NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
             assertTrue(nc.hasCapability(capability));
+            return nc;
         }
 
-        void expectCapabilitiesWithout(int capability, MockNetworkAgent agent) {
+        NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent) {
             CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
             NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
             assertFalse(nc.hasCapability(capability));
+            return nc;
         }
 
         void assertNoCallback() {
@@ -1461,8 +1497,8 @@
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);
-        genericNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
-        cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         waitFor(cv);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1476,8 +1512,8 @@
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
-        wifiNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         waitFor(cv);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1500,8 +1536,8 @@
         // Test validated networks
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        genericNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
-        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        genericNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
@@ -1513,10 +1549,10 @@
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        wifiNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1552,32 +1588,32 @@
         mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
 
         mCellNetworkAgent.connect(true);
-        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.connect(true);
         // We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request.
         // We then get LOSING when wifi validates and cell is outscored.
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mEthernetNetworkAgent.connect(true);
-        callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
         // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mEthernetNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
 
         for (int i = 0; i < 4; i++) {
             MockNetworkAgent oldNetwork, newNetwork;
@@ -1594,7 +1630,7 @@
             callback.expectCallback(CallbackState.LOSING, oldNetwork);
             // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no
             // longer lingering?
-            defaultCallback.expectAvailableCallbacks(newNetwork);
+            defaultCallback.expectAvailableCallbacksValidated(newNetwork);
             assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork());
         }
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1614,7 +1650,7 @@
         // Disconnect our test networks.
         mWiFiNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
 
@@ -1630,22 +1666,22 @@
 
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);   // Score: 10
-        callback.expectAvailableCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi with a score of 20.
         // Cell stays up because it would satisfy the default request if it validated.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);   // Score: 20
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi with a score of 70.
@@ -1653,33 +1689,33 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.adjustScore(50);
         mWiFiNetworkAgent.connect(false);   // Score: 70
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Tear down wifi.
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but
         // it's arguably correct to linger it, since it was the default network before it validated.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
@@ -1687,12 +1723,12 @@
         // If a network is lingering, and we add and remove a request from it, resume lingering.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
@@ -1711,7 +1747,7 @@
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
 
         // Cell is now the default network. Pin it with a cell-specific request.
         noopCallback = new NetworkCallback();  // Can't reuse NetworkCallbacks. http://b/20701525
@@ -1720,8 +1756,8 @@
         // Now connect wifi, and expect it to become the default network.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         // The default request is lingering on cell, but nothing happens to cell, and we send no
         // callbacks for it, because it's kept up by cellRequest.
         callback.assertNoCallback();
@@ -1737,14 +1773,14 @@
         // Register a TRACK_DEFAULT request and check that it does not affect lingering.
         TestNetworkCallback trackDefaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(trackDefaultCallback);
-        trackDefaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        trackDefaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
         mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
         mEthernetNetworkAgent.connect(true);
-        callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
         callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
-        trackDefaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+        trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
 
         // Let linger run its course.
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
@@ -1771,13 +1807,13 @@
         // Bring up validated cell.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
 
         // Bring up unvalidated wifi with explicitlySelected=true.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.explicitlySelected(false);
         mWiFiNetworkAgent.connect(false);
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
 
         // Cell Remains the default.
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1800,7 +1836,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.explicitlySelected(false);
         mWiFiNetworkAgent.connect(false);
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
 
         // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the
         // network to disconnect.
@@ -1811,7 +1847,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.explicitlySelected(false);
         mWiFiNetworkAgent.connect(true);
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1820,7 +1856,7 @@
         // TODO: fix this.
         mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
         mEthernetNetworkAgent.connect(true);
-        callback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+        callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         callback.assertNoCallback();
 
@@ -1993,7 +2029,7 @@
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mCellNetworkAgent.connectWithoutInternet();
-        networkCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         verifyActiveNetwork(TRANSPORT_WIFI);
 
         // Test releasing NetworkRequest disconnects cellular with MMS
@@ -2022,7 +2058,7 @@
         MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mmsNetworkAgent.connectWithoutInternet();
-        networkCallback.expectAvailableCallbacks(mmsNetworkAgent);
+        networkCallback.expectAvailableCallbacksUnvalidated(mmsNetworkAgent);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
 
         // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
@@ -2049,7 +2085,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         String firstRedirectUrl = "http://example.com/firstPath";
         mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
-        captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
 
         // Take down network.
@@ -2062,7 +2098,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         String secondRedirectUrl = "http://example.com/secondPath";
         mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
-        captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl);
 
         // Make captive portal disappear then revalidate.
@@ -2072,9 +2108,7 @@
         captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
-        validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
-        // TODO: Investigate only sending available callbacks.
-        validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
 
         // Break network connectivity.
         // Expect NET_CAPABILITY_VALIDATED onLost callback.
@@ -2098,7 +2132,7 @@
         // Bring up wifi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        validatedCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Check that calling startCaptivePortalApp does nothing.
@@ -2109,7 +2143,7 @@
         // Turn into a captive portal.
         mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302;
         mCm.reportNetworkConnectivity(wifiNetwork, false);
-        captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // Check that startCaptivePortalApp sends the expected intent.
@@ -2122,7 +2156,7 @@
         mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
         CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
         c.reportCaptivePortalDismissed();
-        validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
         captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         mCm.unregisterNetworkCallback(validatedCallback);
@@ -2165,7 +2199,7 @@
         mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
 
         // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
-        validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
         // But there should be no CaptivePortal callback.
         captivePortalCallback.assertNoCallback();
     }
@@ -2203,14 +2237,14 @@
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        cEmpty1.expectAvailableCallbacks(mWiFiNetworkAgent);
-        cEmpty2.expectAvailableCallbacks(mWiFiNetworkAgent);
-        cEmpty3.expectAvailableCallbacks(mWiFiNetworkAgent);
-        cEmpty4.expectAvailableCallbacks(mWiFiNetworkAgent);
+        cEmpty1.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        cEmpty2.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        cEmpty3.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        cEmpty4.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertNoCallbacks(cFoo, cBar);
 
         mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("foo"));
-        cFoo.expectAvailableCallbacks(mWiFiNetworkAgent);
+        cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         for (TestNetworkCallback c: emptyCallbacks) {
             c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
         }
@@ -2219,7 +2253,7 @@
 
         mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("bar"));
         cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        cBar.expectAvailableCallbacks(mWiFiNetworkAgent);
+        cBar.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         for (TestNetworkCallback c: emptyCallbacks) {
             c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
         }
@@ -2348,14 +2382,14 @@
         // Bring up cell and expect CALLBACK_AVAILABLE.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
-        defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
 
         // Bring up wifi and expect CALLBACK_AVAILABLE.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         cellNetworkCallback.assertNoCallback();
-        defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
 
         // Bring down cell. Expect no default network callback, since it wasn't the default.
         mCellNetworkAgent.disconnect();
@@ -2365,7 +2399,7 @@
         // Bring up cell. Expect no default network callback, since it won't be the default.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
 
         // Bring down wifi. Expect the default network callback to notified of LOST wifi
@@ -2373,7 +2407,7 @@
         mWiFiNetworkAgent.disconnect();
         cellNetworkCallback.assertNoCallback();
         defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
         defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
@@ -2394,7 +2428,7 @@
         // We should get onAvailable(), onCapabilitiesChanged(), and
         // onLinkPropertiesChanged() in rapid succession. Additionally, we
         // should get onCapabilitiesChanged() when the mobile network validates.
-        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
         // Update LinkProperties.
@@ -2415,7 +2449,7 @@
         mCm.registerDefaultNetworkCallback(dfltNetworkCallback);
         // We should get onAvailable(), onCapabilitiesChanged(), onLinkPropertiesChanged(),
         // as well as onNetworkSuspended() in rapid succession.
-        dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent);
+        dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent, true);
         dfltNetworkCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(dfltNetworkCallback);
@@ -2455,18 +2489,18 @@
 
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
-        fgCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        fgCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
 
         // When wifi connects, cell lingers.
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        fgCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
@@ -2490,8 +2524,8 @@
         // is currently delivered before the onAvailable() callbacks.
         // TODO: Fix this.
         cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
-        cellCallback.expectAvailableCallbacks(mCellNetworkAgent);
-        fgCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        cellCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         // Expect a network capabilities update with FOREGROUND, because the most recent
         // request causes its state to change.
         callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
@@ -2511,7 +2545,7 @@
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        fgCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
 
         mCm.unregisterNetworkCallback(callback);
@@ -2651,7 +2685,7 @@
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         testFactory.expectAddRequests(2);  // Because the cell request changes score twice.
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         testFactory.waitForNetworkRequests(2);
         assertFalse(testFactory.getMyStartRequested());  // Because the cell network outscores us.
 
@@ -2742,16 +2776,15 @@
         // Bring up validated cell.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         Network cellNetwork = mCellNetworkAgent.getNetwork();
 
         // Bring up validated wifi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
-        validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
-        validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi.
@@ -2772,18 +2805,18 @@
         // that we switch back to cell.
         tracker.configRestrictsAvoidBadWifi = false;
         tracker.reevaluate();
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
         // Switch back to a restrictive carrier.
         tracker.configRestrictsAvoidBadWifi = true;
         tracker.reevaluate();
-        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
 
         // Simulate the user selecting "switch" on the dialog, and check that we switch to cell.
         mCm.setAvoidUnvalidated(wifiNetwork);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -2794,9 +2827,8 @@
         mWiFiNetworkAgent.disconnect();
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
-        validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
-        validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi and expect the dialog to appear.
@@ -2810,7 +2842,7 @@
         tracker.reevaluate();
 
         // We now switch to cell.
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -2821,17 +2853,17 @@
         // We switch to wifi and then to cell.
         Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
         tracker.reevaluate();
-        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
         Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
         tracker.reevaluate();
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
         // If cell goes down, we switch to wifi.
         mCellNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         validatedWifiCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(cellNetworkCallback);
@@ -2873,7 +2905,7 @@
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, timeoutMs);
+        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, timeoutMs);
 
         // pass timeout and validate that UNAVAILABLE is not called
         networkCallback.assertNoCallback();
@@ -2894,7 +2926,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
         final int assertTimeoutMs = 100;
-        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, assertTimeoutMs);
+        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, assertTimeoutMs);
         mWiFiNetworkAgent.disconnect();
         networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
@@ -3381,7 +3413,7 @@
 
         // Bring up wifi aware network.
         wifiAware.connect(false, false);
-        callback.expectAvailableCallbacks(wifiAware);
+        callback.expectAvailableCallbacksUnvalidated(wifiAware);
 
         assertNull(mCm.getActiveNetworkInfo());
         assertNull(mCm.getActiveNetwork());
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 2282c13..1ddab5b 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -32,7 +32,6 @@
 import android.net.IpSecConfig;
 import android.net.IpSecManager;
 import android.net.IpSecSpiResponse;
-import android.net.IpSecTransform;
 import android.net.IpSecTransformResponse;
 import android.net.NetworkUtils;
 import android.os.Binder;
@@ -54,14 +53,14 @@
 @RunWith(Parameterized.class)
 public class IpSecServiceParameterizedTest {
 
-    private static final int TEST_SPI_OUT = 0xD1201D;
-    private static final int TEST_SPI_IN = TEST_SPI_OUT + 1;
+    private static final int TEST_SPI = 0xD1201D;
 
-    private final String mRemoteAddr;
+    private final String mDestinationAddr;
+    private final String mSourceAddr;
 
     @Parameterized.Parameters
     public static Collection ipSecConfigs() {
-        return Arrays.asList(new Object[][] {{"8.8.4.4"}, {"2601::10"}});
+        return Arrays.asList(new Object[][] {{"1.2.3.4", "8.8.4.4"}, {"2601::2", "2601::10"}});
     }
 
     private static final byte[] AEAD_KEY = {
@@ -96,11 +95,9 @@
     private static final IpSecAlgorithm AEAD_ALGO =
             new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
 
-    private static final int[] DIRECTIONS =
-            new int[] {IpSecTransform.DIRECTION_IN, IpSecTransform.DIRECTION_OUT};
-
-    public IpSecServiceParameterizedTest(String remoteAddr) {
-        mRemoteAddr = remoteAddr;
+    public IpSecServiceParameterizedTest(String sourceAddr, String destAddr) {
+        mSourceAddr = sourceAddr;
+        mDestinationAddr = destAddr;
     }
 
     @Before
@@ -116,44 +113,30 @@
 
     @Test
     public void testIpSecServiceReserveSpi() throws Exception {
-        when(mMockNetd.ipSecAllocateSpi(
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        eq(mRemoteAddr),
-                        eq(TEST_SPI_OUT)))
-                .thenReturn(TEST_SPI_OUT);
+        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI)))
+                .thenReturn(TEST_SPI);
 
         IpSecSpiResponse spiResp =
                 mIpSecService.allocateSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
+                        mDestinationAddr, TEST_SPI, new Binder());
         assertEquals(IpSecManager.Status.OK, spiResp.status);
-        assertEquals(TEST_SPI_OUT, spiResp.spi);
+        assertEquals(TEST_SPI, spiResp.spi);
     }
 
     @Test
     public void testReleaseSecurityParameterIndex() throws Exception {
-        when(mMockNetd.ipSecAllocateSpi(
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        eq(mRemoteAddr),
-                        eq(TEST_SPI_OUT)))
-                .thenReturn(TEST_SPI_OUT);
+        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI)))
+                .thenReturn(TEST_SPI);
 
         IpSecSpiResponse spiResp =
                 mIpSecService.allocateSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
+                        mDestinationAddr, TEST_SPI, new Binder());
 
         mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
 
         verify(mMockNetd)
                 .ipSecDeleteSecurityAssociation(
-                        eq(spiResp.resourceId),
-                        anyInt(),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_OUT));
+                        eq(spiResp.resourceId), anyString(), anyString(), eq(TEST_SPI));
 
         // Verify quota and RefcountedResource objects cleaned up
         IpSecService.UserRecord userRecord =
@@ -169,17 +152,12 @@
 
     @Test
     public void testSecurityParameterIndexBinderDeath() throws Exception {
-        when(mMockNetd.ipSecAllocateSpi(
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        eq(mRemoteAddr),
-                        eq(TEST_SPI_OUT)))
-                .thenReturn(TEST_SPI_OUT);
+        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI)))
+                .thenReturn(TEST_SPI);
 
         IpSecSpiResponse spiResp =
                 mIpSecService.allocateSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
+                        mDestinationAddr, TEST_SPI, new Binder());
 
         IpSecService.UserRecord userRecord =
                 mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
@@ -190,11 +168,7 @@
 
         verify(mMockNetd)
                 .ipSecDeleteSecurityAssociation(
-                        eq(spiResp.resourceId),
-                        anyInt(),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_OUT));
+                        eq(spiResp.resourceId), anyString(), anyString(), eq(TEST_SPI));
 
         // Verify quota and RefcountedResource objects cleaned up
         assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent);
@@ -206,14 +180,12 @@
         }
     }
 
-    private int getNewSpiResourceId(int direction, String remoteAddress, int returnSpi)
-            throws Exception {
-        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyInt(), anyString(), anyString(), anyInt()))
+    private int getNewSpiResourceId(String remoteAddress, int returnSpi) throws Exception {
+        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), anyString(), anyInt()))
                 .thenReturn(returnSpi);
 
         IpSecSpiResponse spi =
                 mIpSecService.allocateSecurityParameterIndex(
-                        direction,
                         NetworkUtils.numericToInetAddress(remoteAddress).getHostAddress(),
                         IpSecManager.INVALID_SECURITY_PARAMETER_INDEX,
                         new Binder());
@@ -221,20 +193,14 @@
     }
 
     private void addDefaultSpisAndRemoteAddrToIpSecConfig(IpSecConfig config) throws Exception {
-        config.setSpiResourceId(
-                IpSecTransform.DIRECTION_OUT,
-                getNewSpiResourceId(IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT));
-        config.setSpiResourceId(
-                IpSecTransform.DIRECTION_IN,
-                getNewSpiResourceId(IpSecTransform.DIRECTION_IN, mRemoteAddr, TEST_SPI_IN));
-        config.setRemoteAddress(mRemoteAddr);
+        config.setSpiResourceId(getNewSpiResourceId(mDestinationAddr, TEST_SPI));
+        config.setSourceAddress(mSourceAddr);
+        config.setDestinationAddress(mDestinationAddr);
     }
 
     private void addAuthAndCryptToIpSecConfig(IpSecConfig config) throws Exception {
-        for (int direction : DIRECTIONS) {
-            config.setEncryption(direction, CRYPT_ALGO);
-            config.setAuthentication(direction, AUTH_ALGO);
-        }
+        config.setEncryption(CRYPT_ALGO);
+        config.setAuthentication(AUTH_ALGO);
     }
 
     @Test
@@ -251,32 +217,10 @@
                 .ipSecAddSecurityAssociation(
                         eq(createTransformResp.resourceId),
                         anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
                         anyString(),
                         anyString(),
                         anyLong(),
-                        eq(TEST_SPI_OUT),
-                        eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
-                        eq(AUTH_KEY),
-                        anyInt(),
-                        eq(IpSecAlgorithm.CRYPT_AES_CBC),
-                        eq(CRYPT_KEY),
-                        anyInt(),
-                        eq(""),
-                        eq(new byte[] {}),
-                        eq(0),
-                        anyInt(),
-                        anyInt(),
-                        anyInt());
-        verify(mMockNetd)
-                .ipSecAddSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        anyLong(),
-                        eq(TEST_SPI_IN),
+                        eq(TEST_SPI),
                         eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
                         eq(AUTH_KEY),
                         anyInt(),
@@ -296,8 +240,7 @@
         IpSecConfig ipSecConfig = new IpSecConfig();
         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
 
-        ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_OUT, AEAD_ALGO);
-        ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
+        ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO);
 
         IpSecTransformResponse createTransformResp =
                 mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
@@ -307,32 +250,10 @@
                 .ipSecAddSecurityAssociation(
                         eq(createTransformResp.resourceId),
                         anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
                         anyString(),
                         anyString(),
                         anyLong(),
-                        eq(TEST_SPI_OUT),
-                        eq(""),
-                        eq(new byte[] {}),
-                        eq(0),
-                        eq(""),
-                        eq(new byte[] {}),
-                        eq(0),
-                        eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM),
-                        eq(AEAD_KEY),
-                        anyInt(),
-                        anyInt(),
-                        anyInt(),
-                        anyInt());
-        verify(mMockNetd)
-                .ipSecAddSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        anyLong(),
-                        eq(TEST_SPI_IN),
+                        eq(TEST_SPI),
                         eq(""),
                         eq(new byte[] {}),
                         eq(0),
@@ -359,18 +280,7 @@
 
         verify(mMockNetd)
                 .ipSecDeleteSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_OUT));
-        verify(mMockNetd)
-                .ipSecDeleteSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_IN));
+                        eq(createTransformResp.resourceId), anyString(), anyString(), eq(TEST_SPI));
 
         // Verify quota and RefcountedResource objects cleaned up
         IpSecService.UserRecord userRecord =
@@ -404,18 +314,7 @@
 
         verify(mMockNetd)
                 .ipSecDeleteSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_OUT));
-        verify(mMockNetd)
-                .ipSecDeleteSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_IN));
+                        eq(createTransformResp.resourceId), anyString(), anyString(), eq(TEST_SPI));
 
         // Verify quota and RefcountedResource objects cleaned up
         assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent);
@@ -439,30 +338,22 @@
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
 
         int resourceId = createTransformResp.resourceId;
-        mIpSecService.applyTransportModeTransform(pfd, resourceId);
+        mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId);
 
         verify(mMockNetd)
                 .ipSecApplyTransportModeTransform(
                         eq(pfd.getFileDescriptor()),
                         eq(resourceId),
-                        eq(IpSecTransform.DIRECTION_OUT),
+                        eq(IpSecManager.DIRECTION_OUT),
                         anyString(),
                         anyString(),
-                        eq(TEST_SPI_OUT));
-        verify(mMockNetd)
-                .ipSecApplyTransportModeTransform(
-                        eq(pfd.getFileDescriptor()),
-                        eq(resourceId),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_IN));
+                        eq(TEST_SPI));
     }
 
     @Test
     public void testRemoveTransportModeTransform() throws Exception {
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
-        mIpSecService.removeTransportModeTransform(pfd, 1);
+        mIpSecService.removeTransportModeTransforms(pfd, 1);
 
         verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor());
     }
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index 0467989..b2a27e8 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -105,9 +105,6 @@
     private static final IpSecAlgorithm AEAD_ALGO =
             new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
 
-    private static final int[] DIRECTIONS =
-            new int[] {IpSecTransform.DIRECTION_IN, IpSecTransform.DIRECTION_OUT};
-
     static {
         try {
             INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
@@ -303,83 +300,75 @@
 
     @Test
     public void testValidateAlgorithmsAuth() {
-        for (int direction : DIRECTIONS) {
-            // Validate that correct algorithm type succeeds
-            IpSecConfig config = new IpSecConfig();
-            config.setAuthentication(direction, AUTH_ALGO);
-            mIpSecService.validateAlgorithms(config, direction);
+        // Validate that correct algorithm type succeeds
+        IpSecConfig config = new IpSecConfig();
+        config.setAuthentication(AUTH_ALGO);
+        mIpSecService.validateAlgorithms(config);
 
-            // Validate that incorrect algorithm types fails
-            for (IpSecAlgorithm algo : new IpSecAlgorithm[] {CRYPT_ALGO, AEAD_ALGO}) {
-                try {
-                    config = new IpSecConfig();
-                    config.setAuthentication(direction, algo);
-                    mIpSecService.validateAlgorithms(config, direction);
-                    fail("Did not throw exception on invalid algorithm type");
-                } catch (IllegalArgumentException expected) {
-                }
+        // Validate that incorrect algorithm types fails
+        for (IpSecAlgorithm algo : new IpSecAlgorithm[] {CRYPT_ALGO, AEAD_ALGO}) {
+            try {
+                config = new IpSecConfig();
+                config.setAuthentication(algo);
+                mIpSecService.validateAlgorithms(config);
+                fail("Did not throw exception on invalid algorithm type");
+            } catch (IllegalArgumentException expected) {
             }
         }
     }
 
     @Test
     public void testValidateAlgorithmsCrypt() {
-        for (int direction : DIRECTIONS) {
-            // Validate that correct algorithm type succeeds
-            IpSecConfig config = new IpSecConfig();
-            config.setEncryption(direction, CRYPT_ALGO);
-            mIpSecService.validateAlgorithms(config, direction);
+        // Validate that correct algorithm type succeeds
+        IpSecConfig config = new IpSecConfig();
+        config.setEncryption(CRYPT_ALGO);
+        mIpSecService.validateAlgorithms(config);
 
-            // Validate that incorrect algorithm types fails
-            for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, AEAD_ALGO}) {
-                try {
-                    config = new IpSecConfig();
-                    config.setEncryption(direction, algo);
-                    mIpSecService.validateAlgorithms(config, direction);
-                    fail("Did not throw exception on invalid algorithm type");
-                } catch (IllegalArgumentException expected) {
-                }
+        // Validate that incorrect algorithm types fails
+        for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, AEAD_ALGO}) {
+            try {
+                config = new IpSecConfig();
+                config.setEncryption(algo);
+                mIpSecService.validateAlgorithms(config);
+                fail("Did not throw exception on invalid algorithm type");
+            } catch (IllegalArgumentException expected) {
             }
         }
     }
 
     @Test
     public void testValidateAlgorithmsAead() {
-        for (int direction : DIRECTIONS) {
-            // Validate that correct algorithm type succeeds
-            IpSecConfig config = new IpSecConfig();
-            config.setAuthenticatedEncryption(direction, AEAD_ALGO);
-            mIpSecService.validateAlgorithms(config, direction);
+        // Validate that correct algorithm type succeeds
+        IpSecConfig config = new IpSecConfig();
+        config.setAuthenticatedEncryption(AEAD_ALGO);
+        mIpSecService.validateAlgorithms(config);
 
-            // Validate that incorrect algorithm types fails
-            for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, CRYPT_ALGO}) {
-                try {
-                    config = new IpSecConfig();
-                    config.setAuthenticatedEncryption(direction, algo);
-                    mIpSecService.validateAlgorithms(config, direction);
-                    fail("Did not throw exception on invalid algorithm type");
-                } catch (IllegalArgumentException expected) {
-                }
+        // Validate that incorrect algorithm types fails
+        for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, CRYPT_ALGO}) {
+            try {
+                config = new IpSecConfig();
+                config.setAuthenticatedEncryption(algo);
+                mIpSecService.validateAlgorithms(config);
+                fail("Did not throw exception on invalid algorithm type");
+            } catch (IllegalArgumentException expected) {
             }
         }
     }
 
     @Test
     public void testValidateAlgorithmsAuthCrypt() {
-        for (int direction : DIRECTIONS) {
-            // Validate that correct algorithm type succeeds
-            IpSecConfig config = new IpSecConfig();
-            config.setAuthentication(direction, AUTH_ALGO);
-            config.setEncryption(direction, CRYPT_ALGO);
-            mIpSecService.validateAlgorithms(config, direction);
-        }
+        // Validate that correct algorithm type succeeds
+        IpSecConfig config = new IpSecConfig();
+        config.setAuthentication(AUTH_ALGO);
+        config.setEncryption(CRYPT_ALGO);
+        mIpSecService.validateAlgorithms(config);
     }
 
     @Test
     public void testValidateAlgorithmsNoAlgorithms() {
         IpSecConfig config = new IpSecConfig();
         try {
-            mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN);
+            mIpSecService.validateAlgorithms(config);
             fail("Expected exception; no algorithms specified");
         } catch (IllegalArgumentException expected) {
         }
@@ -388,10 +377,10 @@
     @Test
     public void testValidateAlgorithmsAeadWithAuth() {
         IpSecConfig config = new IpSecConfig();
-        config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
-        config.setAuthentication(IpSecTransform.DIRECTION_IN, AUTH_ALGO);
+        config.setAuthenticatedEncryption(AEAD_ALGO);
+        config.setAuthentication(AUTH_ALGO);
         try {
-            mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN);
+            mIpSecService.validateAlgorithms(config);
             fail("Expected exception; both AEAD and auth algorithm specified");
         } catch (IllegalArgumentException expected) {
         }
@@ -400,10 +389,10 @@
     @Test
     public void testValidateAlgorithmsAeadWithCrypt() {
         IpSecConfig config = new IpSecConfig();
-        config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
-        config.setEncryption(IpSecTransform.DIRECTION_IN, CRYPT_ALGO);
+        config.setAuthenticatedEncryption(AEAD_ALGO);
+        config.setEncryption(CRYPT_ALGO);
         try {
-            mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN);
+            mIpSecService.validateAlgorithms(config);
             fail("Expected exception; both AEAD and crypt algorithm specified");
         } catch (IllegalArgumentException expected) {
         }
@@ -412,11 +401,11 @@
     @Test
     public void testValidateAlgorithmsAeadWithAuthAndCrypt() {
         IpSecConfig config = new IpSecConfig();
-        config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
-        config.setAuthentication(IpSecTransform.DIRECTION_IN, AUTH_ALGO);
-        config.setEncryption(IpSecTransform.DIRECTION_IN, CRYPT_ALGO);
+        config.setAuthenticatedEncryption(AEAD_ALGO);
+        config.setAuthentication(AUTH_ALGO);
+        config.setEncryption(CRYPT_ALGO);
         try {
-            mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN);
+            mIpSecService.validateAlgorithms(config);
             fail("Expected exception; AEAD, auth and crypt algorithm specified");
         } catch (IllegalArgumentException expected) {
         }
@@ -434,7 +423,7 @@
     @Test
     public void testRemoveTransportModeTransform() throws Exception {
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
-        mIpSecService.removeTransportModeTransform(pfd, 1);
+        mIpSecService.removeTransportModeTransforms(pfd, 1);
 
         verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor());
     }
@@ -447,7 +436,7 @@
             try {
                 IpSecSpiResponse spiResp =
                         mIpSecService.allocateSecurityParameterIndex(
-                                IpSecTransform.DIRECTION_OUT, address, DROID_SPI, new Binder());
+                                address, DROID_SPI, new Binder());
                 fail("Invalid address was passed through IpSecService validation: " + address);
             } catch (IllegalArgumentException e) {
             } catch (Exception e) {
@@ -519,7 +508,6 @@
         // tracks the resource ID.
         when(mMockNetd.ipSecAllocateSpi(
                         anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
                         anyString(),
                         eq(InetAddress.getLoopbackAddress().getHostAddress()),
                         anyInt()))
@@ -528,7 +516,6 @@
         for (int i = 0; i < MAX_NUM_SPIS; i++) {
             IpSecSpiResponse newSpi =
                     mIpSecService.allocateSecurityParameterIndex(
-                            0x1,
                             InetAddress.getLoopbackAddress().getHostAddress(),
                             DROID_SPI + i,
                             new Binder());
@@ -544,7 +531,6 @@
         // Try to reserve one more SPI, and should fail.
         IpSecSpiResponse extraSpi =
                 mIpSecService.allocateSecurityParameterIndex(
-                        0x1,
                         InetAddress.getLoopbackAddress().getHostAddress(),
                         DROID_SPI + MAX_NUM_SPIS,
                         new Binder());
@@ -558,7 +544,6 @@
         // Should successfully reserve one more spi.
         extraSpi =
                 mIpSecService.allocateSecurityParameterIndex(
-                        0x1,
                         InetAddress.getLoopbackAddress().getHostAddress(),
                         DROID_SPI + MAX_NUM_SPIS,
                         new Binder());
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 80853b1..0e57f7f 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "Collation.h"
+#include "frameworks/base/cmds/statsd/src/atoms.pb.h"
 
 #include <stdio.h>
 #include <map>
@@ -137,6 +138,16 @@
 }
 
 /**
+ * Gather the enums info.
+ */
+void collate_enums(const EnumDescriptor &enumDescriptor, AtomField *atomField) {
+    for (int i = 0; i < enumDescriptor.value_count(); i++) {
+        atomField->enumValues[enumDescriptor.value(i)->number()] =
+            enumDescriptor.value(i)->name().c_str();
+    }
+}
+
+/**
  * Gather the info about an atom proto.
  */
 int collate_atom(const Descriptor *atom, AtomDecl *atomDecl,
@@ -221,11 +232,7 @@
     if (javaType == JAVA_TYPE_ENUM) {
       // All enums are treated as ints when it comes to function signatures.
       signature->push_back(JAVA_TYPE_INT);
-      const EnumDescriptor *enumDescriptor = field->enum_type();
-      for (int i = 0; i < enumDescriptor->value_count(); i++) {
-        atField.enumValues[enumDescriptor->value(i)->number()] =
-            enumDescriptor->value(i)->name().c_str();
-      }
+      collate_enums(*field->enum_type(), &atField);
     } else {
       signature->push_back(javaType);
     }
@@ -235,6 +242,53 @@
   return errorCount;
 }
 
+// This function flattens the fields of the AttributionNode proto in an Atom proto and generates
+// the corresponding atom decl and signature.
+bool get_non_chained_node(const Descriptor *atom, AtomDecl *atomDecl,
+                          vector<java_type_t> *signature) {
+    // Build a sorted list of the fields. Descriptor has them in source file
+    // order.
+    map<int, const FieldDescriptor *> fields;
+    for (int j = 0; j < atom->field_count(); j++) {
+        const FieldDescriptor *field = atom->field(j);
+        fields[field->number()] = field;
+    }
+
+    AtomDecl attributionDecl;
+    vector<java_type_t> attributionSignature;
+    collate_atom(android::os::statsd::AttributionNode::descriptor(),
+                 &attributionDecl, &attributionSignature);
+
+    // Build the type signature and the atom data.
+    bool has_attribution_node = false;
+    for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
+        it != fields.end(); it++) {
+        const FieldDescriptor *field = it->second;
+        java_type_t javaType = java_type(field);
+        if (javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+            atomDecl->fields.insert(
+                atomDecl->fields.end(),
+                attributionDecl.fields.begin(), attributionDecl.fields.end());
+            signature->insert(
+                signature->end(),
+                attributionSignature.begin(), attributionSignature.end());
+            has_attribution_node = true;
+
+        } else {
+            AtomField atField(field->name(), javaType);
+            if (javaType == JAVA_TYPE_ENUM) {
+                // All enums are treated as ints when it comes to function signatures.
+                signature->push_back(JAVA_TYPE_INT);
+                collate_enums(*field->enum_type(), &atField);
+            } else {
+                signature->push_back(javaType);
+            }
+            atomDecl->fields.push_back(atField);
+        }
+    }
+    return has_attribution_node;
+}
+
 /**
  * Gather the info about the atoms.
  */
@@ -266,6 +320,13 @@
     errorCount += collate_atom(atom, &atomDecl, &signature);
     atoms->signatures.insert(signature);
     atoms->decls.insert(atomDecl);
+
+    AtomDecl nonChainedAtomDecl(atomField->number(), atomField->name(), atom->name());
+    vector<java_type_t> nonChainedSignature;
+    if (get_non_chained_node(atom, &nonChainedAtomDecl, &nonChainedSignature)) {
+        atoms->non_chained_signatures.insert(nonChainedSignature);
+        atoms->non_chained_decls.insert(nonChainedAtomDecl);
+    }
   }
 
   if (dbg) {
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index cd0625c..0455eca 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -32,6 +32,7 @@
 using std::string;
 using std::vector;
 using google::protobuf::Descriptor;
+using google::protobuf::FieldDescriptor;
 
 /**
  * The types for atom parameters.
@@ -93,14 +94,15 @@
 struct Atoms {
     set<vector<java_type_t>> signatures;
     set<AtomDecl> decls;
+    set<AtomDecl> non_chained_decls;
+    set<vector<java_type_t>> non_chained_signatures;
 };
 
 /**
  * Gather the information about the atoms.  Returns the number of errors.
  */
 int collate_atoms(const Descriptor* descriptor, Atoms* atoms);
-int collate_atom(const Descriptor *atom, AtomDecl *atomDecl,
-                 vector<java_type_t> *signature);
+int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, vector<java_type_t> *signature);
 
 }  // namespace stats_log_api_gen
 }  // namespace android
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index bbe6d63..e0e6b58 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -195,6 +195,47 @@
         fprintf(out, "\n");
     }
 
+    for (set<vector<java_type_t>>::const_iterator signature = atoms.non_chained_signatures.begin();
+        signature != atoms.non_chained_signatures.end(); signature++) {
+        int argIndex;
+
+        fprintf(out, "void\n");
+        fprintf(out, "stats_write_non_chained(int32_t code");
+        argIndex = 1;
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+            arg != signature->end(); arg++) {
+            fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
+            argIndex++;
+        }
+        fprintf(out, ")\n");
+
+        fprintf(out, "{\n");
+        argIndex = 1;
+        fprintf(out, "    android_log_event_list event(kStatsEventTag);\n");
+        fprintf(out, "    event << code;\n\n");
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+            arg != signature->end(); arg++) {
+            if (argIndex == 1) {
+                fprintf(out, "    event.begin();\n\n");
+                fprintf(out, "    event.begin();\n");
+            }
+            if (*arg == JAVA_TYPE_STRING) {
+                fprintf(out, "    if (arg%d == NULL) {\n", argIndex);
+                fprintf(out, "        arg%d = \"\";\n", argIndex);
+                fprintf(out, "    }\n");
+            }
+            fprintf(out, "    event << arg%d;\n", argIndex);
+            if (argIndex == 2) {
+                fprintf(out, "    event.end();\n\n");
+                fprintf(out, "    event.end();\n\n");
+            }
+            argIndex++;
+        }
+
+        fprintf(out, "    event.write(LOG_ID_STATS);\n");
+        fprintf(out, "}\n");
+        fprintf(out, "\n");
+    }
     // Print footer
     fprintf(out, "\n");
     fprintf(out, "} // namespace util\n");
@@ -203,6 +244,68 @@
     return 0;
 }
 
+void build_non_chained_decl_map(const Atoms& atoms,
+                                std::map<int, set<AtomDecl>::const_iterator>* decl_map){
+    for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin();
+        atom != atoms.non_chained_decls.end(); atom++) {
+        decl_map->insert(std::make_pair(atom->code, atom));
+    }
+}
+
+static void write_cpp_usage(
+    FILE* out, const string& method_name, const string& atom_code_name,
+    const AtomDecl& atom, const AtomDecl &attributionDecl) {
+    fprintf(out, "     * Usage: %s(StatsLog.%s", method_name.c_str(), atom_code_name.c_str());
+    for (vector<AtomField>::const_iterator field = atom.fields.begin();
+            field != atom.fields.end(); field++) {
+        if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+            for (auto chainField : attributionDecl.fields) {
+                if (chainField.javaType == JAVA_TYPE_STRING) {
+                    fprintf(out, ", const std::vector<%s>& %s",
+                         cpp_type_name(chainField.javaType),
+                         chainField.name.c_str());
+                } else {
+                    fprintf(out, ", const %s* %s, size_t %s_length",
+                         cpp_type_name(chainField.javaType),
+                         chainField.name.c_str(), chainField.name.c_str());
+                }
+            }
+        } else {
+            fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
+        }
+    }
+    fprintf(out, ");\n");
+}
+
+static void write_cpp_method_header(
+    FILE* out, const string& method_name, const set<vector<java_type_t>>& signatures,
+    const AtomDecl &attributionDecl) {
+    for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
+            signature != signatures.end(); signature++) {
+        fprintf(out, "void %s(int32_t code ", method_name.c_str());
+        int argIndex = 1;
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+            arg != signature->end(); arg++) {
+            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (auto chainField : attributionDecl.fields) {
+                    if (chainField.javaType == JAVA_TYPE_STRING) {
+                        fprintf(out, ", const std::vector<%s>& %s",
+                            cpp_type_name(chainField.javaType), chainField.name.c_str());
+                    } else {
+                        fprintf(out, ", const %s* %s, size_t %s_length",
+                            cpp_type_name(chainField.javaType),
+                            chainField.name.c_str(), chainField.name.c_str());
+                    }
+                }
+            } else {
+                fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
+            }
+            argIndex++;
+        }
+        fprintf(out, ");\n");
+
+    }
+}
 
 static int
 write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
@@ -228,6 +331,9 @@
     fprintf(out, " */\n");
     fprintf(out, "enum {\n");
 
+    std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
+    build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
+
     size_t i = 0;
     // Print constants
     for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
@@ -236,26 +342,13 @@
         fprintf(out, "\n");
         fprintf(out, "    /**\n");
         fprintf(out, "     * %s %s\n", atom->message.c_str(), atom->name.c_str());
-        fprintf(out, "     * Usage: stats_write(StatsLog.%s", constant.c_str());
-        for (vector<AtomField>::const_iterator field = atom->fields.begin();
-                field != atom->fields.end(); field++) {
-            if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
-                for (auto chainField : attributionDecl.fields) {
-                    if (chainField.javaType == JAVA_TYPE_STRING) {
-                        fprintf(out, ", const std::vector<%s>& %s",
-                             cpp_type_name(chainField.javaType),
-                             chainField.name.c_str());
-                    } else {
-                        fprintf(out, ", const %s* %s, size_t %s_length",
-                             cpp_type_name(chainField.javaType),
-                             chainField.name.c_str(), chainField.name.c_str());
-                    }
-                }
-            } else {
-                fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
-            }
+        write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl);
+
+        auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
+        if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
+            write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second,
+                attributionDecl);
         }
-        fprintf(out, ");\n");
         fprintf(out, "     */\n");
         char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
         fprintf(out, "    %s = %d%s\n", constant.c_str(), atom->code, comma);
@@ -274,30 +367,13 @@
     fprintf(out, "//\n");
     fprintf(out, "// Write methods\n");
     fprintf(out, "//\n");
-    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
-            signature != atoms.signatures.end(); signature++) {
-        fprintf(out, "void stats_write(int32_t code ");
-        int argIndex = 1;
-        for (vector<java_type_t>::const_iterator arg = signature->begin();
-            arg != signature->end(); arg++) {
-            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
-                for (auto chainField : attributionDecl.fields) {
-                    if (chainField.javaType == JAVA_TYPE_STRING) {
-                        fprintf(out, ", const std::vector<%s>& %s",
-                            cpp_type_name(chainField.javaType), chainField.name.c_str());
-                    } else {
-                        fprintf(out, ", const %s* %s, size_t %s_length",
-                            cpp_type_name(chainField.javaType),
-                            chainField.name.c_str(), chainField.name.c_str());
-                    }
-                }
-            } else {
-                fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
-            }
-            argIndex++;
-        }
-        fprintf(out, ");\n");
-    }
+    write_cpp_method_header(out, "stats_write", atoms.signatures, attributionDecl);
+
+    fprintf(out, "//\n");
+    fprintf(out, "// Write flattened methods\n");
+    fprintf(out, "//\n");
+    write_cpp_method_header(out, "stats_write_non_chained", atoms.non_chained_signatures,
+        attributionDecl);
 
     fprintf(out, "\n");
     fprintf(out, "} // namespace util\n");
@@ -306,6 +382,49 @@
     return 0;
 }
 
+static void write_java_usage(
+    FILE* out, const string& method_name, const string& atom_code_name,
+    const AtomDecl& atom, const AtomDecl &attributionDecl) {
+    fprintf(out, "     * Usage: StatsLog.%s(StatsLog.%s",
+        method_name.c_str(), atom_code_name.c_str());
+    for (vector<AtomField>::const_iterator field = atom.fields.begin();
+        field != atom.fields.end(); field++) {
+        if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+            for (auto chainField : attributionDecl.fields) {
+                fprintf(out, ", %s[] %s",
+                    java_type_name(chainField.javaType), chainField.name.c_str());
+            }
+        } else {
+            fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str());
+        }
+    }
+    fprintf(out, ");\n");
+}
+
+static void write_java_method(
+    FILE* out, const string& method_name, const set<vector<java_type_t>>& signatures,
+    const AtomDecl &attributionDecl) {
+    for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
+        signature != signatures.end(); signature++) {
+        fprintf(out, "    public static native void %s(int code", method_name.c_str());
+        int argIndex = 1;
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+            arg != signature->end(); arg++) {
+            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (auto chainField : attributionDecl.fields) {
+                    fprintf(out, ", %s[] %s",
+                        java_type_name(chainField.javaType), chainField.name.c_str());
+                }
+            } else {
+                fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
+            }
+            argIndex++;
+        }
+        fprintf(out, ");\n");
+    }
+}
+
+
 static int
 write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
 {
@@ -322,6 +441,9 @@
     fprintf(out, "public class StatsLogInternal {\n");
     fprintf(out, "    // Constants for atom codes.\n");
 
+    std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
+    build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
+
     // Print constants for the atom codes.
     for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
             atom != atoms.decls.end(); atom++) {
@@ -329,19 +451,12 @@
         fprintf(out, "\n");
         fprintf(out, "    /**\n");
         fprintf(out, "     * %s %s\n", atom->message.c_str(), atom->name.c_str());
-        fprintf(out, "     * Usage: StatsLog.write(StatsLog.%s", constant.c_str());
-        for (vector<AtomField>::const_iterator field = atom->fields.begin();
-            field != atom->fields.end(); field++) {
-            if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
-                for (auto chainField : attributionDecl.fields) {
-                    fprintf(out, ", %s[] %s",
-                        java_type_name(chainField.javaType), chainField.name.c_str());
-                }
-            } else {
-                fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str());
-            }
+        write_java_usage(out, "write", constant, *atom, attributionDecl);
+        auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
+        if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
+            write_java_usage(out, "write_non_chained", constant, *non_chained_decl->second,
+             attributionDecl);
         }
-        fprintf(out, ");\n");
         fprintf(out, "     */\n");
         fprintf(out, "    public static final int %s = %d;\n", constant.c_str(), atom->code);
     }
@@ -371,24 +486,8 @@
 
     // Print write methods
     fprintf(out, "    // Write methods\n");
-    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
-        signature != atoms.signatures.end(); signature++) {
-        fprintf(out, "    public static native void write(int code");
-        int argIndex = 1;
-        for (vector<java_type_t>::const_iterator arg = signature->begin();
-            arg != signature->end(); arg++) {
-            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
-                for (auto chainField : attributionDecl.fields) {
-                    fprintf(out, ", %s[] %s",
-                        java_type_name(chainField.javaType), chainField.name.c_str());
-                }
-            } else {
-                fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
-            }
-            argIndex++;
-        }
-        fprintf(out, ");\n");
-    }
+    write_java_method(out, "write", atoms.signatures, attributionDecl);
+    write_java_method(out, "write_non_chained", atoms.non_chained_signatures, attributionDecl);
 
     fprintf(out, "}\n");
 
@@ -431,9 +530,9 @@
 }
 
 static string
-jni_function_name(const vector<java_type_t>& signature)
+jni_function_name(const string& method_name, const vector<java_type_t>& signature)
 {
-    string result("StatsLog_write");
+    string result("StatsLog_" + method_name);
     for (vector<java_type_t>::const_iterator arg = signature.begin();
         arg != signature.end(); arg++) {
         switch (*arg) {
@@ -509,34 +608,17 @@
 }
 
 static int
-write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
+write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp_method_name,
+    const set<vector<java_type_t>>& signatures, const AtomDecl &attributionDecl)
 {
-    // Print prelude
-    fprintf(out, "// This file is autogenerated\n");
-    fprintf(out, "\n");
-
-    fprintf(out, "#include <statslog.h>\n");
-    fprintf(out, "\n");
-    fprintf(out, "#include <nativehelper/JNIHelp.h>\n");
-    fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n");
-    fprintf(out, "#include <utils/Vector.h>\n");
-    fprintf(out, "#include \"core_jni_helpers.h\"\n");
-    fprintf(out, "#include \"jni.h\"\n");
-    fprintf(out, "\n");
-    fprintf(out, "#define UNUSED  __attribute__((__unused__))\n");
-    fprintf(out, "\n");
-
-    fprintf(out, "namespace android {\n");
-    fprintf(out, "\n");
-
     // Print write methods
-    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
-        signature != atoms.signatures.end(); signature++) {
+    for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
+        signature != signatures.end(); signature++) {
         int argIndex;
 
         fprintf(out, "static void\n");
         fprintf(out, "%s(JNIEnv* env, jobject clazz UNUSED, jint code",
-                jni_function_name(*signature).c_str());
+                jni_function_name(java_method_name, *signature).c_str());
         argIndex = 1;
         for (vector<java_type_t>::const_iterator arg = signature->begin();
                 arg != signature->end(); arg++) {
@@ -624,7 +706,7 @@
 
         // stats_write call
         argIndex = 1;
-        fprintf(out, "    android::util::stats_write(code");
+        fprintf(out, "    android::util::%s(code", cpp_method_name.c_str());
         for (vector<java_type_t>::const_iterator arg = signature->begin();
                 arg != signature->end(); arg++) {
             if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
@@ -675,17 +757,53 @@
         fprintf(out, "\n");
     }
 
+
+    return 0;
+}
+
+void write_jni_registration(FILE* out, const string& java_method_name,
+    const set<vector<java_type_t>>& signatures, const AtomDecl &attributionDecl) {
+    for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
+            signature != signatures.end(); signature++) {
+        fprintf(out, "    { \"%s\", \"%s\", (void*)%s },\n",
+            java_method_name.c_str(),
+            jni_function_signature(*signature, attributionDecl).c_str(),
+            jni_function_name(java_method_name, *signature).c_str());
+    }
+}
+
+static int
+write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
+{
+    // Print prelude
+    fprintf(out, "// This file is autogenerated\n");
+    fprintf(out, "\n");
+
+    fprintf(out, "#include <statslog.h>\n");
+    fprintf(out, "\n");
+    fprintf(out, "#include <nativehelper/JNIHelp.h>\n");
+    fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n");
+    fprintf(out, "#include <utils/Vector.h>\n");
+    fprintf(out, "#include \"core_jni_helpers.h\"\n");
+    fprintf(out, "#include \"jni.h\"\n");
+    fprintf(out, "\n");
+    fprintf(out, "#define UNUSED  __attribute__((__unused__))\n");
+    fprintf(out, "\n");
+
+    fprintf(out, "namespace android {\n");
+    fprintf(out, "\n");
+
+    write_stats_log_jni(out, "write", "stats_write", atoms.signatures, attributionDecl);
+    write_stats_log_jni(out, "write_non_chained", "stats_write_non_chained",
+        atoms.non_chained_signatures, attributionDecl);
+
     // Print registration function table
     fprintf(out, "/*\n");
     fprintf(out, " * JNI registration.\n");
     fprintf(out, " */\n");
     fprintf(out, "static const JNINativeMethod gRegisterMethods[] = {\n");
-    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
-            signature != atoms.signatures.end(); signature++) {
-        fprintf(out, "    { \"write\", \"%s\", (void*)%s },\n",
-            jni_function_signature(*signature, attributionDecl).c_str(),
-            jni_function_name(*signature).c_str());
-    }
+    write_jni_registration(out, "write", atoms.signatures, attributionDecl);
+    write_jni_registration(out, "write_non_chained", atoms.non_chained_signatures, attributionDecl);
     fprintf(out, "};\n");
     fprintf(out, "\n");
 
@@ -699,11 +817,9 @@
 
     fprintf(out, "\n");
     fprintf(out, "} // namespace android\n");
-
     return 0;
 }
 
-
 static void
 print_usage()
 {