Merge "[Magnifier - 5] Auto invalidate and misc"
diff --git a/Android.mk b/Android.mk
index b5efd47..c87aec4 100644
--- a/Android.mk
+++ b/Android.mk
@@ -425,7 +425,6 @@
 	core/java/com/android/internal/widget/ICheckCredentialProgressCallback.aidl \
 	core/java/com/android/internal/widget/ILockSettings.aidl \
 	core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \
-	core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl \
 	keystore/java/android/security/IKeyChainAliasCallback.aidl \
 	keystore/java/android/security/IKeyChainService.aidl \
 	location/java/android/location/IBatchedLocationCallback.aidl \
@@ -570,6 +569,7 @@
 	../../system/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl \
 
 LOCAL_SRC_FILES += \
+	../../system/core/storaged/binder/android/os/IStoraged.aidl \
 	../../system/netd/server/binder/android/net/INetd.aidl \
 	../../system/vold/binder/android/os/IVold.aidl \
 	../../system/vold/binder/android/os/IVoldListener.aidl \
@@ -578,6 +578,8 @@
 
 LOCAL_AIDL_INCLUDES += system/update_engine/binder_bindings
 
+LOCAL_AIDL_INCLUDES += core/java/android/os/StatsLogEventWrapper.aidl
+
 LOCAL_AIDL_INCLUDES += frameworks/base/lowpan/java
 LOCAL_SRC_FILES += \
 	lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl \
@@ -608,6 +610,7 @@
 	frameworks/av/drm/libmediadrm/aidl \
 	frameworks/av/media/libaudioclient/aidl \
 	frameworks/native/aidl/gui \
+	system/core/storaged/binder \
 	system/netd/server/binder \
 	system/vold/binder \
 	system/bt/binder
@@ -648,6 +651,8 @@
 
 LOCAL_MODULE := framework
 
+LOCAL_JAVAC_SHARD_SIZE := 150
+
 LOCAL_DX_FLAGS := --core-library --multi-dex
 LOCAL_JACK_FLAGS := --multi-dex native
 
diff --git a/apct-tests/perftests/core/Android.mk b/apct-tests/perftests/core/Android.mk
index 200f92f..f08b402 100644
--- a/apct-tests/perftests/core/Android.mk
+++ b/apct-tests/perftests/core/Android.mk
@@ -4,11 +4,14 @@
 LOCAL_MODULE_TAGS := tests
 
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+  $(call all-java-files-under, src) \
+  src/android/os/ISomeService.aidl
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     apct-perftests-utils \
+    guava \
     legacy-android-test
 
 LOCAL_PACKAGE_NAME := CorePerfTests
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index a709ee3..f27e8c8 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -5,6 +5,7 @@
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.perftests.utils.StubActivity" />
+        <service android:name="android.os.SomeService" android:exported="false" android:process=":some_service" />
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/apct-tests/perftests/core/src/android/os/ISomeService.aidl b/apct-tests/perftests/core/src/android/os/ISomeService.aidl
new file mode 100644
index 0000000..0658b69
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/ISomeService.aidl
@@ -0,0 +1,5 @@
+package android.os;
+
+interface ISomeService {
+    void readDisk(int times);
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/os/SomeService.java b/apct-tests/perftests/core/src/android/os/SomeService.java
new file mode 100644
index 0000000..bdfaa44
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/SomeService.java
@@ -0,0 +1,42 @@
+package android.os;
+
+import android.app.Service;
+import android.content.Intent;
+import java.io.File;
+import java.io.IOException;
+
+/** Service in separate process available for calling over binder. */
+public class SomeService extends Service {
+
+    private File mTempFile;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        try {
+            mTempFile = File.createTempFile("foo", "bar");
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private final ISomeService.Stub mBinder =
+            new ISomeService.Stub() {
+                public void readDisk(int times) {
+                    for (int i = 0; i < times; i++) {
+                        mTempFile.exists();
+                    }
+                }
+            };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mTempFile.delete();
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/os/StrictModeTest.java b/apct-tests/perftests/core/src/android/os/StrictModeTest.java
new file mode 100644
index 0000000..d973c20
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/StrictModeTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.os;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import com.google.common.util.concurrent.SettableFuture;
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class StrictModeTest {
+    @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void timeVmViolation() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+        causeVmViolations(state);
+    }
+
+    @Test
+    public void timeVmViolationNoStrictMode() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        causeVmViolations(state);
+    }
+
+    private static void causeVmViolations(BenchmarkState state) {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setDataAndType(Uri.parse("content://com.example/foobar"), "image/jpeg");
+        final Context context = InstrumentationRegistry.getTargetContext();
+        while (state.keepRunning()) {
+            context.startActivity(intent);
+        }
+    }
+
+    @Test
+    public void timeThreadViolation() throws IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        causeThreadViolations(state);
+    }
+
+    @Test
+    public void timeThreadViolationNoStrictMode() throws IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        causeThreadViolations(state);
+    }
+
+    private static void causeThreadViolations(BenchmarkState state) throws IOException {
+        final File test = File.createTempFile("foo", "bar");
+        while (state.keepRunning()) {
+            test.exists();
+        }
+        test.delete();
+    }
+
+    @Test
+    public void timeCrossBinderThreadViolation() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        causeCrossProcessThreadViolations(state);
+    }
+
+    @Test
+    public void timeCrossBinderThreadViolationNoStrictMode() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        causeCrossProcessThreadViolations(state);
+    }
+
+    private static void causeCrossProcessThreadViolations(BenchmarkState state)
+            throws ExecutionException, InterruptedException, RemoteException {
+        final Context context = InstrumentationRegistry.getTargetContext();
+
+        SettableFuture<IBinder> binder = SettableFuture.create();
+        ServiceConnection connection =
+                new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName className, IBinder service) {
+                        binder.set(service);
+                    }
+
+                    @Override
+                    public void onServiceDisconnected(ComponentName arg0) {
+                        binder.set(null);
+                    }
+                };
+        context.bindService(
+                new Intent(context, SomeService.class), connection, Context.BIND_AUTO_CREATE);
+        ISomeService someService = ISomeService.Stub.asInterface(binder.get());
+        while (state.keepRunning()) {
+            // Violate strictmode heavily.
+            someService.readDisk(10);
+        }
+        context.unbindService(connection);
+    }
+}
diff --git a/api/current.txt b/api/current.txt
index 87fa407..df8b7ef 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -25754,6 +25754,7 @@
     method public java.lang.String getName();
     method public int getTruncationLengthBits();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final java.lang.String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
     field public static final java.lang.String AUTH_HMAC_MD5 = "hmac(md5)";
     field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
     field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
@@ -25801,6 +25802,7 @@
   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.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
@@ -35072,6 +35074,7 @@
     field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
+    field public static final java.lang.String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
     field public static final java.lang.String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -37281,10 +37284,15 @@
   }
 
   public static final class FillEventHistory.Event {
+    method public java.util.Map<android.view.autofill.AutofillId, java.lang.String> getChangedFields();
     method public android.os.Bundle getClientState();
     method public java.lang.String getDatasetId();
+    method public java.util.Set<java.lang.String> getIgnoredDatasetIds();
+    method public java.util.Map<android.view.autofill.AutofillId, java.util.Set<java.lang.String>> getManuallyEnteredField();
+    method public java.util.Set<java.lang.String> getSelectedDatasetIds();
     method public int getType();
     field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
+    field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4
     field public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; // 0x1
     field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
     field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
@@ -37305,6 +37313,7 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+    field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
   }
 
   public static final class FillResponse.Builder {
@@ -37313,6 +37322,7 @@
     method public android.service.autofill.FillResponse build();
     method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
     method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
+    method public android.service.autofill.FillResponse.Builder setFlags(int);
     method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
     method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
   }
@@ -47119,9 +47129,9 @@
     method public abstract void setInputType(int);
     method public abstract void setLocaleList(android.os.LocaleList);
     method public abstract void setLongClickable(boolean);
-    method public abstract void setMaxTextEms(int);
-    method public abstract void setMaxTextLength(int);
-    method public abstract void setMinTextEms(int);
+    method public void setMaxTextEms(int);
+    method public void setMaxTextLength(int);
+    method public void setMinTextEms(int);
     method public abstract void setOpaque(boolean);
     method public abstract void setSelected(boolean);
     method public abstract void setText(java.lang.CharSequence);
@@ -52060,6 +52070,7 @@
     method public android.graphics.Typeface getTypeface();
     method public android.text.style.URLSpan[] getUrls();
     method public boolean hasSelection();
+    method public void invalidate(int, int, int, int);
     method public boolean isAllCaps();
     method public boolean isCursorVisible();
     method public boolean isElegantTextHeight();
diff --git a/api/system-current.txt b/api/system-current.txt
index a868549..007316f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -27996,6 +27996,7 @@
     method public java.lang.String getName();
     method public int getTruncationLengthBits();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final java.lang.String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
     field public static final java.lang.String AUTH_HMAC_MD5 = "hmac(md5)";
     field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
     field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
@@ -28043,6 +28044,7 @@
   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.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
@@ -38136,6 +38138,7 @@
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
     field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
+    field public static final java.lang.String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
     field public static final java.lang.String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -40383,10 +40386,15 @@
   }
 
   public static final class FillEventHistory.Event {
+    method public java.util.Map<android.view.autofill.AutofillId, java.lang.String> getChangedFields();
     method public android.os.Bundle getClientState();
     method public java.lang.String getDatasetId();
+    method public java.util.Set<java.lang.String> getIgnoredDatasetIds();
+    method public java.util.Map<android.view.autofill.AutofillId, java.util.Set<java.lang.String>> getManuallyEnteredField();
+    method public java.util.Set<java.lang.String> getSelectedDatasetIds();
     method public int getType();
     field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
+    field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4
     field public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; // 0x1
     field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
     field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
@@ -40407,6 +40415,7 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+    field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
   }
 
   public static final class FillResponse.Builder {
@@ -40415,6 +40424,7 @@
     method public android.service.autofill.FillResponse build();
     method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
     method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
+    method public android.service.autofill.FillResponse.Builder setFlags(int);
     method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
     method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
   }
@@ -41092,6 +41102,7 @@
     method public android.os.IBinder onBind(android.content.Intent);
     method public abstract java.util.List<android.service.settings.suggestions.Suggestion> onGetSuggestions();
     method public abstract void onSuggestionDismissed(android.service.settings.suggestions.Suggestion);
+    method public abstract void onSuggestionLaunched(android.service.settings.suggestions.Suggestion);
   }
 
 }
@@ -50851,9 +50862,9 @@
     method public abstract void setInputType(int);
     method public abstract void setLocaleList(android.os.LocaleList);
     method public abstract void setLongClickable(boolean);
-    method public abstract void setMaxTextEms(int);
-    method public abstract void setMaxTextLength(int);
-    method public abstract void setMinTextEms(int);
+    method public void setMaxTextEms(int);
+    method public void setMaxTextLength(int);
+    method public void setMinTextEms(int);
     method public abstract void setOpaque(boolean);
     method public abstract void setSelected(boolean);
     method public abstract void setText(java.lang.CharSequence);
@@ -56163,6 +56174,7 @@
     method public android.graphics.Typeface getTypeface();
     method public android.text.style.URLSpan[] getUrls();
     method public boolean hasSelection();
+    method public void invalidate(int, int, int, int);
     method public boolean isAllCaps();
     method public boolean isCursorVisible();
     method public boolean isElegantTextHeight();
diff --git a/api/test-current.txt b/api/test-current.txt
index c93722f..faccb6b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -25954,6 +25954,7 @@
     method public java.lang.String getName();
     method public int getTruncationLengthBits();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final java.lang.String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
     field public static final java.lang.String AUTH_HMAC_MD5 = "hmac(md5)";
     field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
     field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
@@ -26001,6 +26002,7 @@
   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.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
@@ -35343,6 +35345,7 @@
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
     field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
+    field public static final java.lang.String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
     field public static final java.lang.String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -37573,10 +37576,15 @@
   }
 
   public static final class FillEventHistory.Event {
+    method public java.util.Map<android.view.autofill.AutofillId, java.lang.String> getChangedFields();
     method public android.os.Bundle getClientState();
     method public java.lang.String getDatasetId();
+    method public java.util.Set<java.lang.String> getIgnoredDatasetIds();
+    method public java.util.Map<android.view.autofill.AutofillId, java.util.Set<java.lang.String>> getManuallyEnteredField();
+    method public java.util.Set<java.lang.String> getSelectedDatasetIds();
     method public int getType();
     field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
+    field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4
     field public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; // 0x1
     field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
     field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
@@ -37597,6 +37605,7 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+    field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
   }
 
   public static final class FillResponse.Builder {
@@ -37605,6 +37614,7 @@
     method public android.service.autofill.FillResponse build();
     method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
     method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
+    method public android.service.autofill.FillResponse.Builder setFlags(int);
     method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
     method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
   }
@@ -47713,9 +47723,9 @@
     method public abstract void setInputType(int);
     method public abstract void setLocaleList(android.os.LocaleList);
     method public abstract void setLongClickable(boolean);
-    method public abstract void setMaxTextEms(int);
-    method public abstract void setMaxTextLength(int);
-    method public abstract void setMinTextEms(int);
+    method public void setMaxTextEms(int);
+    method public void setMaxTextLength(int);
+    method public void setMinTextEms(int);
     method public abstract void setOpaque(boolean);
     method public abstract void setSelected(boolean);
     method public abstract void setText(java.lang.CharSequence);
@@ -52668,6 +52678,7 @@
     method public android.graphics.Typeface getTypeface();
     method public android.text.style.URLSpan[] getUrls();
     method public boolean hasSelection();
+    method public void invalidate(int, int, int, int);
     method public boolean isAllCaps();
     method public boolean isCursorVisible();
     method public boolean isElegantTextHeight();
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 38dcc62..8505d4c 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -24,6 +24,7 @@
     src/condition/CombinationConditionTracker.cpp \
     src/condition/condition_util.cpp \
     src/condition/SimpleConditionTracker.cpp \
+    src/condition/ConditionWizard.cpp \
     src/config/ConfigKey.cpp \
     src/config/ConfigListener.cpp \
     src/config/ConfigManager.cpp \
@@ -37,6 +38,7 @@
     src/matchers/SimpleLogMatchingTracker.cpp \
     src/metrics/CountAnomalyTracker.cpp \
     src/metrics/CountMetricProducer.cpp \
+    src/metrics/DurationMetricProducer.cpp \
     src/metrics/MetricsManager.cpp \
     src/metrics/metrics_manager_util.cpp \
     src/packages/UidMap.cpp \
@@ -47,7 +49,8 @@
     src/stats_util.cpp
 
 statsd_common_c_includes := \
-    $(LOCAL_PATH)/src
+    $(LOCAL_PATH)/src \
+    $(LOCAL_PATH)/../../libs/services/include
 
 statsd_common_aidl_includes := \
     $(LOCAL_PATH)/../../core/java
@@ -93,7 +96,7 @@
 endif
 LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
 
-LOCAL_AIDL_INCLUDES := $(statsd_common_c_includes)
+LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
 LOCAL_C_INCLUDES += $(statsd_common_c_includes)
 
 LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries)
@@ -115,7 +118,7 @@
 LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_AIDL_INCLUDES := $(statsd_common_c_includes)
+LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
 LOCAL_C_INCLUDES += $(statsd_common_c_includes)
 
 LOCAL_CFLAGS += \
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 3def13f..e7825cf 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -73,6 +73,16 @@
     }
 }
 
+vector<StatsLogReport> StatsLogProcessor::onDumpReport(const ConfigKey& key) {
+    auto it = mMetricsManagers.find(key);
+    if (it == mMetricsManagers.end()) {
+        ALOGW("Config source %s does not exist", key.ToString().c_str());
+        return vector<StatsLogReport>();
+    }
+
+    return it->second->onDumpReport();
+}
+
 void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
     auto it = mMetricsManagers.find(key);
     if (it != mMetricsManagers.end()) {
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index dc60485..3cefd29 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -33,7 +33,7 @@
 
 class StatsLogProcessor : public ConfigListener {
 public:
-    StatsLogProcessor(const sp<UidMap> &uidMap);
+    StatsLogProcessor(const sp<UidMap>& uidMap);
     virtual ~StatsLogProcessor();
 
     virtual void OnLogEvent(const LogEvent& event);
@@ -41,6 +41,9 @@
     void OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config);
     void OnConfigRemoved(const ConfigKey& key);
 
+    // TODO: Once we have the ProtoOutputStream in c++, we can just return byte array.
+    std::vector<StatsLogReport> onDumpReport(const ConfigKey& key);
+
 private:
     // TODO: use EventMetrics to log the events.
     DropboxWriter m_dropbox_writer;
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index b72e04e..1faeee0 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -63,10 +63,9 @@
 
 // ======================================================================
 StatsService::StatsService(const sp<Looper>& handlerLooper)
-    : mStatsPullerManager(),
-
-      mAnomalyMonitor(new AnomalyMonitor(2))  // TODO: Put this comment somewhere better
+    : mAnomalyMonitor(new AnomalyMonitor(2))  // TODO: Put this comment somewhere better
 {
+    mStatsPullerManager = new StatsPullerManager();
     mUidMap = new UidMap();
     mConfigManager = new ConfigManager();
     mProcessor = new StatsLogProcessor(mUidMap);
@@ -89,7 +88,7 @@
 
 void StatsService::init_build_type_callback(void* cookie, const char* /*name*/, const char* value,
                                             uint32_t serial) {
-    if (0 == strcmp("eng", value)) {
+    if (0 == strcmp("eng", value) || 0 == strcmp("userdebug", value)) {
         reinterpret_cast<StatsService*>(cookie)->mEngBuild = true;
     }
 }
@@ -187,10 +186,17 @@
             return cmd_print_stats_log(out, args);
         }
 
-        // adb shell cmd stats print-stats-log
         if (!args[0].compare(String8("print-uid-map"))) {
             return cmd_print_uid_map(out);
         }
+
+        if (!args[0].compare(String8("dump-report"))) {
+            return cmd_dump_report(out, err, args);
+        }
+
+        if (!args[0].compare(String8("pull-source")) && args.size() > 1) {
+            return cmd_print_pulled_metrics(out, args);
+        }
     }
 
     print_cmd_help(out);
@@ -208,6 +214,11 @@
     fprintf(out, "  Prints the UID, app name, version mapping.\n");
     fprintf(out, "\n");
     fprintf(out, "\n");
+    fprintf(out, "usage: adb shell cmds stats pull-source [int] \n");
+    fprintf(out, "\n");
+    fprintf(out, "  Prints the output of a pulled metrics source (int indicates source)\n");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
     fprintf(out, "usage: adb shell cmd stats config remove [UID] NAME\n");
     fprintf(out, "usage: adb shell cmd stats config update [UID] NAME\n");
     fprintf(out, "\n");
@@ -248,7 +259,9 @@
                         }
                     }
                 } else {
-                    fprintf(err, "The config can only be set for other UIDs on eng builds.\n");
+                    fprintf(err,
+                            "The config can only be set for other UIDs on eng or userdebug "
+                            "builds.\n");
                 }
             }
 
@@ -287,6 +300,54 @@
     return UNKNOWN_ERROR;
 }
 
+status_t StatsService::cmd_dump_report(FILE* out, FILE* err, const Vector<String8>& args) {
+    if (mProcessor != nullptr) {
+        const int argCount = args.size();
+        bool good = false;
+        int uid;
+        string name;
+        if (argCount == 2) {
+            // Automatically pick the UID
+            uid = IPCThreadState::self()->getCallingUid();
+            // TODO: What if this isn't a binder call? Should we fail?
+            name.assign(args[2].c_str(), args[2].size());
+            good = true;
+        } else if (argCount == 3) {
+            // If it's a userdebug or eng build, then the shell user can
+            // impersonate other uids.
+            if (mEngBuild) {
+                const char* s = args[1].c_str();
+                if (*s != '\0') {
+                    char* end = NULL;
+                    uid = strtol(s, &end, 0);
+                    if (*end == '\0') {
+                        name.assign(args[2].c_str(), args[2].size());
+                        good = true;
+                    }
+                }
+            } else {
+                fprintf(out,
+                        "The metrics can only be dumped for other UIDs on eng or userdebug "
+                        "builds.\n");
+            }
+        }
+        if (good) {
+            mProcessor->onDumpReport(ConfigKey(uid, name));
+            // TODO: print the returned StatsLogReport to file instead of printing to logcat.
+            fprintf(out, "Dump report for Config [%d,%s]\n", uid, name.c_str());
+            fprintf(out, "See the StatsLogReport in logcat...\n");
+            return android::OK;
+        } else {
+            // If arg parsing failed, print the help text and return an error.
+            print_cmd_help(out);
+            return UNKNOWN_ERROR;
+        }
+    } else {
+        fprintf(out, "Log processor does not exist...\n");
+        return UNKNOWN_ERROR;
+    }
+}
+
 status_t StatsService::cmd_print_stats_log(FILE* out, const Vector<String8>& args) {
     long msec = 0;
 
@@ -301,6 +362,16 @@
     return NO_ERROR;
 }
 
+status_t StatsService::cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args) {
+    int s = atoi(args[1].c_str());
+    auto stats = mStatsPullerManager->Pull(s);
+    for (const auto& it : stats) {
+        fprintf(out, "Pull from %d: %s\n", s, it->ToString().c_str());
+    }
+    fprintf(out, "Pull from %d: Received %zu elements\n", s, stats.size());
+    return NO_ERROR;
+}
+
 Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
                                       const vector<String16>& app) {
     if (DEBUG) ALOGD("StatsService::informAllUidData was called");
@@ -362,10 +433,6 @@
 
     if (DEBUG) ALOGD("StatsService::informPollAlarmFired succeeded");
     // TODO: determine what services to poll and poll (or ask StatsCompanionService to poll) them.
-    String16 output = mStatsPullerManager.pull(StatsPullerManager::KERNEL_WAKELOCKS);
-    // TODO: do something useful with the output instead of writing a string to screen.
-    ALOGD("%s", String8(output).string());
-    ALOGD("%d", int(output.size()));
 
     return Status::ok();
 }
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 7d305e9..449a2b8 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -111,11 +111,21 @@
     status_t cmd_print_stats_log(FILE* out, const Vector<String8>& args);
 
     /**
+     * Print the event log.
+     */
+    status_t cmd_dump_report(FILE* out, FILE* err, const Vector<String8>& args);
+
+    /**
      * Print the mapping of uids to package names.
      */
     status_t cmd_print_uid_map(FILE* out);
 
     /**
+     * Print contents of a pulled metrics source.
+     */
+    status_t cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args);
+
+    /**
      * Update a configuration.
      */
     void set_config(int uid, const string& name, const StatsdConfig& config);
@@ -127,9 +137,8 @@
 
     /**
      * Fetches external metrics.
-     * TODO: This should be an sp<>
      */
-    StatsPullerManager mStatsPullerManager;
+    sp<StatsPullerManager> mStatsPullerManager;
 
     /**
      * Tracks the configurations that have been passed to statsd.
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index 014747f..f56c15a 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -25,6 +25,7 @@
 namespace os {
 namespace statsd {
 
+using std::map;
 using std::string;
 using std::unique_ptr;
 using std::unordered_map;
@@ -102,33 +103,62 @@
     return true;
 }
 
+void CombinationConditionTracker::isConditionMet(
+        const map<string, HashableDimensionKey>& conditionParameters,
+        const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) {
+    for (const int childIndex : mChildren) {
+        if (conditionCache[childIndex] == ConditionState::kNotEvaluated) {
+            allConditions[childIndex]->isConditionMet(conditionParameters, allConditions,
+                                                      conditionCache);
+        }
+    }
+    conditionCache[mIndex] =
+            evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
+}
+
 bool CombinationConditionTracker::evaluateCondition(
         const LogEvent& event, const std::vector<MatchingState>& eventMatcherValues,
         const std::vector<sp<ConditionTracker>>& mAllConditions,
-        std::vector<ConditionState>& conditionCache, std::vector<bool>& changedCache) {
+        std::vector<ConditionState>& nonSlicedConditionCache,
+        std::vector<bool>& nonSlicedChangedCache, vector<bool>& slicedConditionChanged) {
     // value is up to date.
-    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
+    if (nonSlicedConditionCache[mIndex] != ConditionState::kNotEvaluated) {
         return false;
     }
 
     for (const int childIndex : mChildren) {
-        if (conditionCache[childIndex] == ConditionState::kNotEvaluated) {
+        if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) {
             const sp<ConditionTracker>& child = mAllConditions[childIndex];
-            child->evaluateCondition(event, eventMatcherValues, mAllConditions, conditionCache,
-                                     changedCache);
+            child->evaluateCondition(event, eventMatcherValues, mAllConditions,
+                                     nonSlicedConditionCache, nonSlicedChangedCache,
+                                     slicedConditionChanged);
         }
     }
 
     ConditionState newCondition =
-            evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
+            evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);
 
-    bool changed = (mConditionState != newCondition);
-    mConditionState = newCondition;
+    bool nonSlicedChanged = (mNonSlicedConditionState != newCondition);
+    mNonSlicedConditionState = newCondition;
 
-    conditionCache[mIndex] = mConditionState;
+    nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
 
-    changedCache[mIndex] = changed;
-    return changed;
+    nonSlicedChangedCache[mIndex] = nonSlicedChanged;
+
+    if (mSliced) {
+        for (const int childIndex : mChildren) {
+            // If any of the sliced condition in children condition changes, the combination
+            // condition may be changed too.
+            if (slicedConditionChanged[childIndex]) {
+                slicedConditionChanged[mIndex] = true;
+                break;
+            }
+        }
+        ALOGD("CombinationCondition %s sliced may changed? %d", mName.c_str(),
+              slicedConditionChanged[mIndex] == true);
+    }
+
+    return nonSlicedChanged;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index 5d2d77e..fc88a88 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -39,7 +39,14 @@
                            const std::vector<MatchingState>& eventMatcherValues,
                            const std::vector<sp<ConditionTracker>>& mAllConditions,
                            std::vector<ConditionState>& conditionCache,
-                           std::vector<bool>& changedCache) override;
+                           std::vector<bool>& changedCache,
+                           std::vector<bool>& slicedConditionMayChanged) override;
+
+    void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
+                        const std::vector<sp<ConditionTracker>>& allConditions,
+                        std::vector<ConditionState>& conditionCache) override;
+
+    void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override{};
 
 private:
     LogicalOperation mLogicalOperation;
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 8cc7e23..055b478 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -38,8 +38,9 @@
         : mName(name),
           mIndex(index),
           mInitialized(false),
-          mConditionState(ConditionState::kUnknown),
-          mTrackerIndex(){};
+          mTrackerIndex(),
+          mNonSlicedConditionState(ConditionState::kUnknown),
+          mSliced(false){};
 
     virtual ~ConditionTracker(){};
 
@@ -63,25 +64,47 @@
     //                     event before ConditionTrackers, because ConditionTracker depends on
     //                     LogMatchingTrackers.
     // mAllConditions: the list of all ConditionTracker
-    // conditionCache: the cached results of the ConditionTrackers for this new event.
-    // changedCache: the bit map to record whether the condition has changed.
+    // conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event.
+    // nonSlicedConditionChanged: the bit map to record whether non-sliced condition has changed.
+    // slicedConditionMayChanged: the bit map to record whether sliced condition may have changed.
+    //      Because sliced condition needs parameters to determine the value. So the sliced
+    //      condition is not pushed to metrics. We only inform the relevant metrics that the sliced
+    //      condition may have changed, and metrics should pull the conditions that they are
+    //      interested in.
     virtual bool evaluateCondition(const LogEvent& event,
                                    const std::vector<MatchingState>& eventMatcherValues,
                                    const std::vector<sp<ConditionTracker>>& mAllConditions,
                                    std::vector<ConditionState>& conditionCache,
-                                   std::vector<bool>& changedCache) = 0;
+                                   std::vector<bool>& nonSlicedConditionChanged,
+                                   std::vector<bool>& slicedConditionMayChanged) = 0;
 
     // Return the current condition state.
     virtual ConditionState isConditionMet() {
-        ALOGW("Condition %s value %d", mName.c_str(), mConditionState);
-        return mConditionState;
+        return mNonSlicedConditionState;
     };
 
+    // Query the condition with parameters.
+    // [conditionParameters]: a map from condition name to the HashableDimensionKey to query the
+    //                       condition.
+    // [allConditions]: all condition trackers. This is needed because the condition evaluation is
+    //                  done recursively
+    // [conditionCache]: the cache holding the condition evaluation values.
+    virtual void isConditionMet(
+            const std::map<std::string, HashableDimensionKey>& conditionParameters,
+            const std::vector<sp<ConditionTracker>>& allConditions,
+            std::vector<ConditionState>& conditionCache) = 0;
+
     // return the list of LogMatchingTracker index that this ConditionTracker uses.
     virtual const std::set<int>& getLogTrackerIndex() const {
         return mTrackerIndex;
     }
 
+    virtual void setSliced(bool sliced) {
+        mSliced = mSliced | sliced;
+    }
+
+    virtual void addDimensions(const std::vector<KeyMatcher>& keyMatchers) = 0;
+
 protected:
     // We don't really need the string name, but having a name here makes log messages
     // easy to debug.
@@ -93,11 +116,12 @@
     // if it's properly initialized.
     bool mInitialized;
 
-    // current condition state.
-    ConditionState mConditionState;
-
     // the list of LogMatchingTracker index that this ConditionTracker uses.
     std::set<int> mTrackerIndex;
+
+    ConditionState mNonSlicedConditionState;
+
+    bool mSliced;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp
new file mode 100644
index 0000000..411f7e5
--- /dev/null
+++ b/cmds/statsd/src/condition/ConditionWizard.cpp
@@ -0,0 +1,36 @@
+/*
+ * 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 "ConditionWizard.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::map;
+using std::string;
+using std::vector;
+
+ConditionState ConditionWizard::query(const int index,
+                                      const map<string, HashableDimensionKey>& parameters) {
+    vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated);
+
+    mAllConditions[index]->isConditionMet(parameters, mAllConditions, cache);
+    return cache[index];
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
new file mode 100644
index 0000000..4889b64
--- /dev/null
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CONDITION_WIZARD_H
+#define CONDITION_WIZARD_H
+
+#include "ConditionTracker.h"
+#include "condition_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Held by MetricProducer, to query a condition state with input defined in EventConditionLink.
+class ConditionWizard : public virtual android::RefBase {
+public:
+    ConditionWizard(std::vector<sp<ConditionTracker>>& conditionTrackers)
+        : mAllConditions(conditionTrackers){};
+
+    // Query condition state, for a ConditionTracker at [conditionIndex], with [conditionParameters]
+    // [conditionParameters] mapping from condition name to the HashableDimensionKey to query the
+    //                       condition.
+    // The ConditionTracker at [conditionIndex] can be a CombinationConditionTracker. In this case,
+    // the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
+    ConditionState query(const int conditionIndex,
+                         const std::map<std::string, HashableDimensionKey>& conditionParameters);
+
+private:
+    std::vector<sp<ConditionTracker>>& mAllConditions;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // CONDITION_WIZARD_H
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index fa583bf..bde3846 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -25,6 +25,7 @@
 namespace os {
 namespace statsd {
 
+using std::map;
 using std::string;
 using std::unique_ptr;
 using std::unordered_map;
@@ -64,7 +65,7 @@
     if (simpleCondition.has_stop_all()) {
         auto pair = trackerNameIndexMap.find(simpleCondition.stop_all());
         if (pair == trackerNameIndexMap.end()) {
-            ALOGW("Stop matcher %s not found in the config", simpleCondition.stop().c_str());
+            ALOGW("Stop all matcher %s not found in the config", simpleCondition.stop().c_str());
             return;
         }
         mStopAllLogMatcherIndex = pair->second;
@@ -89,41 +90,136 @@
     return mInitialized;
 }
 
+void print(unordered_map<HashableDimensionKey, ConditionState>& conditions, const string& name) {
+    VLOG("%s DUMP:", name.c_str());
+
+    for (const auto& pair : conditions) {
+        VLOG("\t%s %d", pair.first.c_str(), pair.second);
+    }
+}
+
+void SimpleConditionTracker::addDimensions(const std::vector<KeyMatcher>& keyMatchers) {
+    VLOG("Added dimensions size %lu", (unsigned long)keyMatchers.size());
+    mDimensionsList.push_back(keyMatchers);
+    mSliced = true;
+}
+
 bool SimpleConditionTracker::evaluateCondition(const LogEvent& event,
                                                const vector<MatchingState>& eventMatcherValues,
                                                const vector<sp<ConditionTracker>>& mAllConditions,
                                                vector<ConditionState>& conditionCache,
-                                               vector<bool>& changedCache) {
+                                               vector<bool>& nonSlicedConditionChanged,
+                                               std::vector<bool>& slicedConditionChanged) {
     if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
         // it has been evaluated.
-        VLOG("Yes, already evaluated, %s %d", mName.c_str(), mConditionState);
+        VLOG("Yes, already evaluated, %s %d", mName.c_str(), mNonSlicedConditionState);
         return false;
     }
 
     // Ignore nesting, because we know we cannot trust ourselves on tracking nesting conditions.
-    ConditionState newCondition = mConditionState;
+
+    ConditionState newCondition = mNonSlicedConditionState;
+    bool matched = false;
     // Note: The order to evaluate the following start, stop, stop_all matters.
     // The priority of overwrite is stop_all > stop > start.
     if (mStartLogMatcherIndex >= 0 &&
         eventMatcherValues[mStartLogMatcherIndex] == MatchingState::kMatched) {
+        matched = true;
         newCondition = ConditionState::kTrue;
     }
 
     if (mStopLogMatcherIndex >= 0 &&
         eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) {
+        matched = true;
         newCondition = ConditionState::kFalse;
     }
 
+    bool stopAll = false;
     if (mStopAllLogMatcherIndex >= 0 &&
         eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
+        matched = true;
         newCondition = ConditionState::kFalse;
+        stopAll = true;
     }
 
-    bool changed = (mConditionState != newCondition);
-    mConditionState = newCondition;
-    conditionCache[mIndex] = mConditionState;
-    changedCache[mIndex] = changed;
-    return changed;
+    if (matched == false) {
+        slicedConditionChanged[mIndex] = false;
+        nonSlicedConditionChanged[mIndex] = false;
+        conditionCache[mIndex] = mNonSlicedConditionState;
+        return false;
+    }
+
+    bool nonSlicedChanged = mNonSlicedConditionState != newCondition;
+
+    bool slicedChanged = false;
+
+    if (stopAll) {
+        // TODO: handle stop all; all dimension should be cleared.
+    }
+
+    if (mDimensionsList.size() > 0) {
+        for (size_t i = 0; i < mDimensionsList.size(); i++) {
+            const auto& dim = mDimensionsList[i];
+            vector<KeyValuePair> key = getDimensionKey(event, dim);
+            HashableDimensionKey hashableKey = getHashableKey(key);
+            if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
+                mSlicedConditionState[hashableKey] != newCondition) {
+                slicedChanged = true;
+                mSlicedConditionState[hashableKey] = newCondition;
+            }
+            VLOG("key: %s %d", hashableKey.c_str(), newCondition);
+        }
+        // dump all dimensions for debugging
+        if (DEBUG) {
+            print(mSlicedConditionState, mName);
+        }
+    }
+
+    // even if this SimpleCondition is not sliced, it may be part of a sliced CombinationCondition
+    // if the nonSliced condition changed, it may affect the sliced condition in the parent node.
+    // so mark the slicedConditionChanged to be true.
+    // For example: APP_IN_BACKGROUND_OR_SCREEN_OFF
+    //     APP_IN_BACKGROUND is sliced [App_A->True, App_B->False].
+    //     SCREEN_OFF is not sliced, and it changes from False -> True;
+    //     We need to populate this change to parent condition. Because for App_B,
+    //     the APP_IN_BACKGROUND_OR_SCREEN_OFF condition would change from False->True.
+    slicedConditionChanged[mIndex] = mSliced ? slicedChanged : nonSlicedChanged;
+    nonSlicedConditionChanged[mIndex] = nonSlicedChanged;
+
+    VLOG("SimpleCondition %s nonSlicedChange? %d  SlicedChanged? %d", mName.c_str(),
+         nonSlicedConditionChanged[mIndex] == true, slicedConditionChanged[mIndex] == true);
+    mNonSlicedConditionState = newCondition;
+    conditionCache[mIndex] = mNonSlicedConditionState;
+
+    return nonSlicedConditionChanged[mIndex];
+}
+
+void SimpleConditionTracker::isConditionMet(
+        const map<string, HashableDimensionKey>& conditionParameters,
+        const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) {
+    const auto pair = conditionParameters.find(mName);
+    if (pair == conditionParameters.end()) {
+        // the query does not need my sliced condition. just return the non sliced condition.
+        conditionCache[mIndex] = mNonSlicedConditionState;
+        VLOG("Condition %s return %d", mName.c_str(), mNonSlicedConditionState);
+        return;
+    }
+
+    const HashableDimensionKey& key = pair->second;
+    VLOG("simpleCondition %s query key: %s", mName.c_str(), key.c_str());
+
+    if (mSlicedConditionState.find(key) == mSlicedConditionState.end()) {
+        // never seen this key before. the condition is unknown to us.
+        conditionCache[mIndex] = ConditionState::kUnknown;
+    } else {
+        conditionCache[mIndex] = mSlicedConditionState[key];
+    }
+
+    VLOG("Condition %s return %d", mName.c_str(), conditionCache[mIndex]);
+
+    if (DEBUG) {
+        print(mSlicedConditionState, mName);
+    }
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index 9dd06a1..1f357f0 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -19,6 +19,7 @@
 
 #include "ConditionTracker.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
 
 namespace android {
 namespace os {
@@ -26,6 +27,8 @@
 
 class SimpleConditionTracker : public virtual ConditionTracker {
 public:
+    // dimensions is a vector of vector because for one single condition, different metrics may be
+    // interested in slicing in different ways. one vector<KeyMatcher> defines one type of slicing.
     SimpleConditionTracker(const std::string& name, const int index,
                            const SimpleCondition& simpleCondition,
                            const std::unordered_map<std::string, int>& trackerNameIndexMap);
@@ -41,7 +44,14 @@
                            const std::vector<MatchingState>& eventMatcherValues,
                            const std::vector<sp<ConditionTracker>>& mAllConditions,
                            std::vector<ConditionState>& conditionCache,
-                           std::vector<bool>& changedCache) override;
+                           std::vector<bool>& changedCache,
+                           std::vector<bool>& slicedChangedCache) override;
+
+    void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
+                        const std::vector<sp<ConditionTracker>>& allConditions,
+                        std::vector<ConditionState>& conditionCache) override;
+
+    void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override;
 
 private:
     // The index of the LogEventMatcher which defines the start.
@@ -55,6 +65,13 @@
 
     // The index of the LogEventMatcher which defines the stop all.
     int mStopAllLogMatcherIndex;
+
+    // Different metrics may subscribe to different types of slicings. So it's a vector of vector.
+    std::vector<std::vector<KeyMatcher>> mDimensionsList;
+
+    // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
+    // that StatsLogReport wants.
+    std::unordered_map<HashableDimensionKey, ConditionState> mSlicedConditionState;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp
index c7c8fcc..40d41be 100644
--- a/cmds/statsd/src/condition/condition_util.cpp
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -23,6 +23,7 @@
 #include <log/logprint.h>
 #include <utils/Errors.h>
 #include <unordered_map>
+#include "../matchers/matcher_util.h"
 #include "ConditionTracker.h"
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
@@ -90,6 +91,25 @@
     return newCondition;
 }
 
+HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
+                                                 const EventConditionLink& link) {
+    vector<KeyMatcher> eventKey;
+    eventKey.reserve(link.key_in_main().size());
+
+    for (const auto& key : link.key_in_main()) {
+        eventKey.push_back(key);
+    }
+
+    vector<KeyValuePair> dimensionKey = getDimensionKey(event, eventKey);
+
+    for (int i = 0; i < link.key_in_main_size(); i++) {
+        auto& kv = dimensionKey[i];
+        kv.set_key(link.key_in_condition(i).key());
+    }
+
+    return getHashableKey(dimensionKey);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/condition/condition_util.h b/cmds/statsd/src/condition/condition_util.h
index a4fcea3..47e245e 100644
--- a/cmds/statsd/src/condition/condition_util.h
+++ b/cmds/statsd/src/condition/condition_util.h
@@ -18,6 +18,7 @@
 #define CONDITION_UTIL_H
 
 #include <vector>
+#include "../matchers/matcher_util.h"
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 
@@ -35,6 +36,9 @@
 ConditionState evaluateCombinationCondition(const std::vector<int>& children,
                                             const LogicalOperation& operation,
                                             const std::vector<ConditionState>& conditionCache);
+
+HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
+                                                 const EventConditionLink& link);
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 2a4d6e2..038edd3 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -118,130 +118,156 @@
     StatsdConfig config;
     config.set_config_id(12345L);
 
-    // One count metric to count screen on
+    int WAKE_LOCK_TAG_ID = 11;
+    int WAKE_LOCK_UID_KEY_ID = 1;
+    int WAKE_LOCK_STATE_KEY = 2;
+    int WAKE_LOCK_ACQUIRE_VALUE = 1;
+    int WAKE_LOCK_RELEASE_VALUE = 0;
+
+    int APP_USAGE_ID = 12345;
+    int APP_USAGE_UID_KEY_ID = 1;
+    int APP_USAGE_STATE_KEY = 2;
+    int APP_USAGE_FOREGROUND = 1;
+    int APP_USAGE_BACKGROUND = 0;
+
+    int SCREEN_EVENT_TAG_ID = 2;
+    int SCREEN_EVENT_STATE_KEY = 1;
+    int SCREEN_EVENT_ON_VALUE = 2;
+    int SCREEN_EVENT_OFF_VALUE = 1;
+
+    int UID_PROCESS_STATE_TAG_ID = 3;
+    int UID_PROCESS_STATE_UID_KEY = 1;
+
+    // Count Screen ON events.
     CountMetric* metric = config.add_count_metric();
-    metric->set_metric_id(20150717L);
-    metric->set_what("SCREEN_IS_ON");
+    metric->set_metric_id(1);
+    metric->set_what("SCREEN_TURNED_ON");
     metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
 
-    // One count metric to count PHOTO_CHANGE_OR_CHROME_CRASH
+    // Count process state changes, slice by uid.
     metric = config.add_count_metric();
-    metric->set_metric_id(20150718L);
-    metric->set_what("PHOTO_PROCESS_STATE_CHANGE");
-    metric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
-    metric->set_condition("SCREEN_IS_ON");
+    metric->set_metric_id(2);
+    metric->set_what("PROCESS_STATE_CHANGE");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    KeyMatcher* keyMatcher = metric->add_dimension();
+    keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
 
+    // Count process state changes, slice by uid, while SCREEN_IS_OFF
+    metric = config.add_count_metric();
+    metric->set_metric_id(3);
+    metric->set_what("PROCESS_STATE_CHANGE");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    keyMatcher = metric->add_dimension();
+    keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
+    metric->set_condition("SCREEN_IS_OFF");
+
+    // Count wake lock, slice by uid, while SCREEN_IS_OFF and app in background
+    metric = config.add_count_metric();
+    metric->set_metric_id(4);
+    metric->set_what("APP_GET_WL");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    keyMatcher = metric->add_dimension();
+    keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
+    metric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    EventConditionLink* link = metric->add_links();
+    link->set_condition("APP_IS_BACKGROUND");
+    link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+    link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+    // Duration of an app holding wl, while screen on and app in background
+    DurationMetric* durationMetric = config.add_duration_metric();
+    durationMetric->set_metric_id(5);
+    durationMetric->set_start("APP_GET_WL");
+    durationMetric->set_stop("APP_RELEASE_WL");
+    durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
+    keyMatcher = durationMetric->add_dimension();
+    keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
+    durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    link = durationMetric->add_links();
+    link->set_condition("APP_IS_BACKGROUND");
+    link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+    link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+    // Event matchers............
     LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
-    eventMatcher->set_name("SCREEN_IS_ON");
-
+    eventMatcher->set_name("SCREEN_TURNED_ON");
     SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
-    simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
-    simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
-            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+    simpleLogEntryMatcher->set_tag(SCREEN_EVENT_TAG_ID);
+    KeyValueMatcher* keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+    keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY);
+    keyValueMatcher->set_eq_int(SCREEN_EVENT_ON_VALUE);
 
     eventMatcher = config.add_log_entry_matcher();
-    eventMatcher->set_name("SCREEN_IS_OFF");
-
+    eventMatcher->set_name("SCREEN_TURNED_OFF");
     simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
-    simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
-    simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
+    simpleLogEntryMatcher->set_tag(SCREEN_EVENT_TAG_ID);
+    keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+    keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY);
+    keyValueMatcher->set_eq_int(SCREEN_EVENT_OFF_VALUE);
 
-    LogEntryMatcher* procEventMatcher = config.add_log_entry_matcher();
-    procEventMatcher->set_name("PHOTO_CRASH");
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("PROCESS_STATE_CHANGE");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(UID_PROCESS_STATE_TAG_ID);
 
-    SimpleLogEntryMatcher* simpleLogMatcher2 = procEventMatcher->mutable_simple_log_entry_matcher();
-    simpleLogMatcher2->add_tag(1112 /*PROCESS_STATE_CHANGE*/);
-    KeyValueMatcher* keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
-    keyValueMatcher->mutable_key_matcher()->set_key(1002 /*pkg*/);
-    keyValueMatcher->set_eq_string(
-            "com.google.android.apps.photos" /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("APP_GOES_BACKGROUND");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(APP_USAGE_ID);
+    keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+    keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY);
+    keyValueMatcher->set_eq_int(APP_USAGE_BACKGROUND);
 
-    keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
-    keyValueMatcher->mutable_key_matcher()->set_key(1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    keyValueMatcher->set_eq_int(2);
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("APP_GOES_FOREGROUND");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(APP_USAGE_ID);
+    keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+    keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY);
+    keyValueMatcher->set_eq_int(APP_USAGE_FOREGROUND);
 
-    procEventMatcher = config.add_log_entry_matcher();
-    procEventMatcher->set_name("PHOTO_START");
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("APP_GET_WL");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(WAKE_LOCK_TAG_ID);
+    keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+    keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY);
+    keyValueMatcher->set_eq_int(WAKE_LOCK_ACQUIRE_VALUE);
 
-    simpleLogMatcher2 = procEventMatcher->mutable_simple_log_entry_matcher();
-    simpleLogMatcher2->add_tag(1112 /*PROCESS_STATE_CHANGE*/);
-    keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
-    keyValueMatcher->mutable_key_matcher()->set_key(1002 /*pkg*/);
-    keyValueMatcher->set_eq_string(
-            "com.google.android.apps.photos" /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("APP_RELEASE_WL");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(WAKE_LOCK_TAG_ID);
+    keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+    keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY);
+    keyValueMatcher->set_eq_int(WAKE_LOCK_RELEASE_VALUE);
 
-    keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
-    keyValueMatcher->mutable_key_matcher()->set_key(1 /*STATE*/);
-    keyValueMatcher->set_eq_int(1);
-
-    procEventMatcher = config.add_log_entry_matcher();
-    procEventMatcher->set_name("PHOTO_PROCESS_STATE_CHANGE");
-    LogEntryMatcher_Combination* combinationMatcher = procEventMatcher->mutable_combination();
-    combinationMatcher->set_operation(LogicalOperation::OR);
-    combinationMatcher->add_matcher("PHOTO_START");
-    combinationMatcher->add_matcher("PHOTO_CRASH");
-
-    procEventMatcher = config.add_log_entry_matcher();
-    procEventMatcher->set_name("CHROME_CRASH");
-
-    simpleLogMatcher2 = procEventMatcher->mutable_simple_log_entry_matcher();
-    simpleLogMatcher2->add_tag(1112 /*PROCESS_STATE_CHANGE*/);
-    keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
-    keyValueMatcher->mutable_key_matcher()->set_key(1002 /*pkg*/);
-    keyValueMatcher->set_eq_string(
-            "com.android.chrome" /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
-
-    keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
-    keyValueMatcher->mutable_key_matcher()->set_key(1 /*STATE*/);
-    keyValueMatcher->set_eq_int(2);
-
-    procEventMatcher = config.add_log_entry_matcher();
-    procEventMatcher->set_name("PHOTO_CHANGE_OR_CHROME_CRASH");
-    combinationMatcher = procEventMatcher->mutable_combination();
-    combinationMatcher->set_operation(LogicalOperation::OR);
-    combinationMatcher->add_matcher("PHOTO_PROCESS_STATE_CHANGE");
-    combinationMatcher->add_matcher("CHROME_CRASH");
-
+    // Conditions.............
     Condition* condition = config.add_condition();
     condition->set_name("SCREEN_IS_ON");
     SimpleCondition* simpleCondition = condition->mutable_simple_condition();
-    simpleCondition->set_start("SCREEN_IS_ON");
-    simpleCondition->set_stop("SCREEN_IS_OFF");
-
-    condition = config.add_condition();
-    condition->set_name("PHOTO_STARTED");
-
-    simpleCondition = condition->mutable_simple_condition();
-    simpleCondition->set_start("PHOTO_START");
-    simpleCondition->set_stop("PHOTO_CRASH");
+    simpleCondition->set_start("SCREEN_TURNED_ON");
+    simpleCondition->set_stop("SCREEN_TURNED_OFF");
 
     condition = config.add_condition();
     condition->set_name("SCREEN_IS_OFF");
-
     simpleCondition = condition->mutable_simple_condition();
-    simpleCondition->set_start("SCREEN_IS_OFF");
-    simpleCondition->set_stop("SCREEN_IS_ON");
+    simpleCondition->set_start("SCREEN_TURNED_OFF");
+    simpleCondition->set_stop("SCREEN_TURNED_ON");
 
     condition = config.add_condition();
-    condition->set_name("SCREEN_IS_EITHER_ON_OFF");
-
-    Condition_Combination* combination = condition->mutable_combination();
-    combination->set_operation(LogicalOperation::OR);
-    combination->add_condition("SCREEN_IS_ON");
-    combination->add_condition("SCREEN_IS_OFF");
+    condition->set_name("APP_IS_BACKGROUND");
+    simpleCondition = condition->mutable_simple_condition();
+    simpleCondition->set_start("APP_GOES_BACKGROUND");
+    simpleCondition->set_stop("APP_GOES_FOREGROUND");
 
     condition = config.add_condition();
-    condition->set_name("SCREEN_IS_NEITHER_ON_OFF");
-
-    combination = condition->mutable_combination();
-    combination->set_operation(LogicalOperation::NOR);
-    combination->add_condition("SCREEN_IS_ON");
-    combination->add_condition("SCREEN_IS_OFF");
+    condition->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    Condition_Combination* combination_condition = condition->mutable_combination();
+    combination_condition->set_operation(LogicalOperation::AND);
+    combination_condition->add_condition("APP_IS_BACKGROUND");
+    combination_condition->add_condition("SCREEN_IS_ON");
 
     return config;
 }
diff --git a/cmds/statsd/src/external/KernelWakelockPuller.cpp b/cmds/statsd/src/external/KernelWakelockPuller.cpp
index b9abee0..ee072f8 100644
--- a/cmds/statsd/src/external/KernelWakelockPuller.cpp
+++ b/cmds/statsd/src/external/KernelWakelockPuller.cpp
@@ -37,9 +37,9 @@
 
 // The reading and parsing are implemented in Java. It is not difficult to port over. But for now
 // let StatsCompanionService handle that and send the data back.
-String16 KernelWakelockPuller::pull() {
+vector<StatsLogEventWrapper> KernelWakelockPuller::pull() {
     sp<IStatsCompanionService> statsCompanion = StatsService::getStatsCompanionService();
-    String16 returned_value("");
+    vector<StatsLogEventWrapper> returned_value;
     if (statsCompanion != NULL) {
         Status status = statsCompanion->pullData(KernelWakelockPuller::PULL_CODE_KERNEL_WAKELOCKS,
                                                  &returned_value);
@@ -47,12 +47,10 @@
             ALOGW("error pulling kernel wakelock");
         }
         ALOGD("KernelWakelockPuller::pull succeeded!");
-        // TODO: remove this when we integrate into aggregation chain.
-        ALOGD("%s", String8(returned_value).string());
         return returned_value;
     } else {
         ALOGW("statsCompanion not found!");
-        return String16();
+        return returned_value;
     }
 }
 
diff --git a/cmds/statsd/src/external/KernelWakelockPuller.h b/cmds/statsd/src/external/KernelWakelockPuller.h
index 1ec3376..c12806c 100644
--- a/cmds/statsd/src/external/KernelWakelockPuller.h
+++ b/cmds/statsd/src/external/KernelWakelockPuller.h
@@ -29,7 +29,7 @@
     // a number of stats need to be pulled from StatsCompanionService
     //
     const static int PULL_CODE_KERNEL_WAKELOCKS;
-    String16 pull() override;
+    vector<StatsLogEventWrapper> pull() override;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
index 5e556b8..6655629 100644
--- a/cmds/statsd/src/external/StatsPuller.h
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -17,7 +17,12 @@
 #ifndef STATSD_STATSPULLER_H
 #define STATSD_STATSPULLER_H
 
+#include <android/os/StatsLogEventWrapper.h>
 #include <utils/String16.h>
+#include <vector>
+
+using android::os::StatsLogEventWrapper;
+using std::vector;
 
 namespace android {
 namespace os {
@@ -26,8 +31,8 @@
 class StatsPuller {
 public:
     virtual ~StatsPuller(){};
-    // use string for now, until we figure out how to integrate into the aggregation path
-    virtual String16 pull() = 0;
+
+    virtual vector<StatsLogEventWrapper> pull() = 0;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 6e8d58bc..7f554d3 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -21,6 +21,11 @@
 #include "KernelWakelockPuller.h"
 #include "StatsService.h"
 #include "external/StatsPullerManager.h"
+#include "logd/LogEvent.h"
+#include <cutils/log.h>
+#include <algorithm>
+
+#include <iostream>
 
 using namespace android;
 
@@ -35,13 +40,27 @@
             {static_cast<int>(KERNEL_WAKELOCKS), std::make_unique<KernelWakelockPuller>()});
 }
 
-String16 StatsPullerManager::pull(int pullCode) {
+vector<std::shared_ptr<LogEvent>> StatsPullerManager::Pull(int pullCode) {
     if (DEBUG) ALOGD("Initiating pulling %d", pullCode);
+
+    vector<std::shared_ptr<LogEvent>> ret;
     if (mStatsPullers.find(pullCode) != mStatsPullers.end()) {
-        return (mStatsPullers.find(pullCode)->second)->pull();
+        vector<StatsLogEventWrapper> outputs = (mStatsPullers.find(pullCode)->second)->pull();
+        for (const StatsLogEventWrapper& it : outputs) {
+            log_msg tmp;
+            tmp.entry_v1.len = it.bytes.size();
+            // Manually set the header size to 28 bytes to match the pushed log events.
+            tmp.entry.hdr_size = 28;
+            // And set the received bytes starting after the 28 bytes reserved for header.
+            std::copy(it.bytes.begin(), it.bytes.end(), tmp.buf + 28);
+            std::shared_ptr<LogEvent> evt = std::make_shared<LogEvent>(tmp);
+            ret.push_back(evt);
+            // ret.emplace_back(tmp);
+        }
+        return ret;
     } else {
         ALOGD("Unknown pull code %d", pullCode);
-        return String16();
+        return ret;  // Return early since we don't know what to pull.
     }
 }
 
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index f143424..e46aec1 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -20,6 +20,8 @@
 #include <utils/String16.h>
 #include <unordered_map>
 #include "external/StatsPuller.h"
+#include "logd/LogEvent.h"
+#include "matchers/matcher_util.h"
 
 namespace android {
 namespace os {
@@ -27,7 +29,7 @@
 
 const static int KERNEL_WAKELOCKS = 1;
 
-class StatsPullerManager {
+class StatsPullerManager : public virtual RefBase {
 public:
     // Enums of pulled data types (pullCodes)
     // These values must be kept in sync with com/android/server/stats/StatsCompanionService.java.
@@ -35,7 +37,8 @@
     const static int KERNEL_WAKELOCKS;
     StatsPullerManager();
 
-    String16 pull(const int pullCode);
+    // We return a vector of shared_ptr since LogEvent's copy constructor is not available.
+    vector<std::shared_ptr<LogEvent>> Pull(const int pullCode);
 
 private:
     std::unordered_map<int, std::unique_ptr<StatsPuller>> mStatsPullers;
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 9fa2baf..fb992c1 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -23,29 +23,30 @@
 namespace statsd {
 
 using std::ostringstream;
+using std::string;
 
-LogEvent::LogEvent(const log_msg& msg) {
-    init(msg);
+// We need to keep a copy of the android_log_event_list owned by this instance so that the char*
+// for strings is not cleared before we can read them.
+LogEvent::LogEvent(log_msg msg) : mList(msg) {
+    init(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec, &mList);
 }
 
-LogEvent::LogEvent(int64_t timestampNs, android_log_event_list* reader) {
-    init(timestampNs, reader);
+LogEvent::LogEvent(int tag) : mList(tag) {
 }
 
 LogEvent::~LogEvent() {
 }
 
+void LogEvent::init() {
+    mList.convert_to_reader();
+    init(mTimestampNs, &mList);
+}
+
 /**
  * The elements of each log event are stored as a vector of android_log_list_elements.
  * The goal is to do as little preprocessing as possible, because we read a tiny fraction
  * of the elements that are written to the log.
  */
-void LogEvent::init(const log_msg& msg) {
-
-    android_log_event_list list(const_cast<log_msg&>(msg));
-    init(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec, &list);
-}
-
 void LogEvent::init(int64_t timestampNs, android_log_event_list* reader) {
     mTimestampNs = timestampNs;
     mTagId = reader->tag();
@@ -79,6 +80,10 @@
     } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete);
 }
 
+android_log_event_list* LogEvent::GetAndroidLogEventList() {
+    return &mList;
+}
+
 int64_t LogEvent::GetLong(size_t key, status_t* err) const {
     if (key < 1 || (key - 1)  >= mElements.size()) {
         *err = BAD_INDEX;
@@ -109,7 +114,8 @@
         *err = BAD_TYPE;
         return NULL;
     }
-    return elem.data.string;
+    // Need to add the '/0' at the end by specifying the length of the string.
+    return string(elem.data.string, elem.len).c_str();
 }
 
 bool LogEvent::GetBool(size_t key, status_t* err) const {
@@ -150,6 +156,29 @@
     }
 }
 
+KeyValuePair LogEvent::GetKeyValueProto(size_t key) const {
+    KeyValuePair pair;
+    pair.set_key(key);
+    // If the value is not valid, return the KeyValuePair without assigning the value.
+    // Caller can detect the error by checking the enum for "one of" proto type.
+    if (key < 1 || (key - 1) >= mElements.size()) {
+        return pair;
+    }
+    key--;
+
+    const android_log_list_element& elem = mElements[key];
+    if (elem.type == EVENT_TYPE_INT) {
+        pair.set_value_int(elem.data.int32);
+    } else if (elem.type == EVENT_TYPE_LONG) {
+        pair.set_value_int(elem.data.int64);
+    } else if (elem.type == EVENT_TYPE_STRING) {
+        pair.set_value_str(elem.data.string);
+    } else if (elem.type == EVENT_TYPE_FLOAT) {
+        pair.set_value_float(elem.data.float32);
+    }
+    return pair;
+}
+
 string LogEvent::ToString() const {
     ostringstream result;
     result << "{ " << mTimestampNs << " (" << mTagId << ")";
@@ -166,7 +195,8 @@
         } else if (elem.type == EVENT_TYPE_FLOAT) {
             result << elem.data.float32;
         } else if (elem.type == EVENT_TYPE_STRING) {
-            result << elem.data.string;
+            // Need to add the '/0' at the end by specifying the length of the string.
+            result << string(elem.data.string, elem.len).c_str();
         }
     }
     result << " }";
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index b523201..4102675 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -22,6 +22,7 @@
 #include <log/log_event_list.h>
 #include <log/log_read.h>
 
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -40,12 +41,16 @@
     /**
      * Read a LogEvent from a log_msg.
      */
-    explicit LogEvent(const log_msg& msg);
+    explicit LogEvent(log_msg msg);
 
     /**
-     * Read a LogEvent from an android_log_context.
+     * Constructs a LogEvent with the specified tag and creates an android_log_event_list in write
+     * mode. Obtain this list with the getter. Make sure to call init() before attempting to read
+     * any of the values. This constructor is useful for unit-testing since we can't pass in an
+     * android_log_event_list since there is no copy constructor or assignment operator available.
      */
-    explicit LogEvent(int64_t timestampNs, android_log_event_list* reader);
+    explicit LogEvent(int tag);
+
     ~LogEvent();
 
     /**
@@ -80,6 +85,24 @@
      */
     void ToProto(EventMetricData* out) const;
 
+    /*
+     * Get a KeyValuePair proto object.
+     */
+    KeyValuePair GetKeyValueProto(size_t key) const;
+
+    /**
+     * A pointer to the contained log_event_list.
+     *
+     * @return The android_log_event_list contained within.
+     */
+    android_log_event_list* GetAndroidLogEventList();
+
+    /**
+     * Used with the constructor where tag is passed in. Converts the log_event_list to read mode
+     * and prepares the list for reading.
+     */
+    void init();
+
 private:
     /**
      * Don't copy, it's slower. If we really need this we can add it but let's try to
@@ -98,6 +121,8 @@
     void init(int64_t timestampNs, android_log_event_list* reader);
 
     vector<android_log_list_element> mElements;
+    // Need a copy of the android_log_event_list so the strings are not cleared.
+    android_log_event_list mList;
     long mTimestampNs;
     int mTagId;
 };
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
index 71078ea..b2c88a0 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define DEBUG true  // STOPSHIP if true
+#define DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
 #include "SimpleLogMatchingTracker.h"
@@ -34,10 +34,12 @@
 SimpleLogMatchingTracker::SimpleLogMatchingTracker(const string& name, const int index,
                                                    const SimpleLogEntryMatcher& matcher)
     : LogMatchingTracker(name, index), mMatcher(matcher) {
-    for (int i = 0; i < matcher.tag_size(); i++) {
-        mTagIds.insert(matcher.tag(i));
+    if (!matcher.has_tag()) {
+        mInitialized = false;
+    } else {
+        mTagIds.insert(matcher.tag());
+        mInitialized = true;
     }
-    mInitialized = true;
 }
 
 SimpleLogMatchingTracker::~SimpleLogMatchingTracker() {
@@ -48,7 +50,7 @@
                                     const unordered_map<string, int>& matcherMap,
                                     vector<bool>& stack) {
     // no need to do anything.
-    return true;
+    return mInitialized;
 }
 
 void SimpleLogMatchingTracker::onLogEvent(const LogEvent& event,
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 1c1e3c7..6aa2211 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -30,9 +30,9 @@
 #include <sstream>
 #include <unordered_map>
 
+using std::ostringstream;
 using std::set;
 using std::string;
-using std::ostringstream;
 using std::unordered_map;
 using std::vector;
 
@@ -91,109 +91,103 @@
 
 bool matchesSimple(const SimpleLogEntryMatcher& simpleMatcher, const LogEvent& event) {
     const int tagId = event.GetTagId();
-    /*
-    const unordered_map<int, long>& intMap = event.intMap;
-    const unordered_map<int, string>& strMap = event.strMap;
-    const unordered_map<int, float>& floatMap = event.floatMap;
-    const unordered_map<int, bool>& boolMap = event.boolMap;
-    */
 
-    for (int i = 0; i < simpleMatcher.tag_size(); i++) {
-        if (simpleMatcher.tag(i) != tagId) {
-            continue;
-        }
+    if (simpleMatcher.tag() != tagId) {
+        return false;
+    }
+    // now see if this event is interesting to us -- matches ALL the matchers
+    // defined in the metrics.
+    bool allMatched = true;
+    for (int j = 0; allMatched && j < simpleMatcher.key_value_matcher_size(); j++) {
+        auto cur = simpleMatcher.key_value_matcher(j);
 
-        // TODO Is this right? Shouldn't this second loop be outside the outer loop?
-        // If I understand correctly, the event matches if one of the tags match,
-        // and ALL of the key-value matchers match. --joeo
+        // TODO: Check if this key is a magic key (eg package name).
+        // TODO: Maybe make packages a different type in the config?
+        int key = cur.key_matcher().key();
 
-        // now see if this event is interesting to us -- matches ALL the matchers
-        // defined in the metrics.
-        bool allMatched = true;
-        for (int j = 0; allMatched && j < simpleMatcher.key_value_matcher_size(); j++) {
-            auto cur = simpleMatcher.key_value_matcher(j);
-
-            // TODO: Check if this key is a magic key (eg package name).
-            // TODO: Maybe make packages a different type in the config?
-            int key = cur.key_matcher().key();
-
-            const KeyValueMatcher::ValueMatcherCase matcherCase = cur.value_matcher_case();
-            if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqString) {
-                // String fields
-                status_t err = NO_ERROR;
-                const char* val = event.GetString(key, &err);
-                if (err == NO_ERROR && val != NULL) {
-                    if (!(cur.eq_string() == val)) {
-                        allMatched = false;
-                    }
+        const KeyValueMatcher::ValueMatcherCase matcherCase = cur.value_matcher_case();
+        if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqString) {
+            // String fields
+            status_t err = NO_ERROR;
+            const char* val = event.GetString(key, &err);
+            if (err == NO_ERROR && val != NULL) {
+                if (!(cur.eq_string() == val)) {
+                    allMatched = false;
                 }
-            } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt
-                    || matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt
-                    || matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt
-                    || matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt
-                    || matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
-                // Integer fields
-                status_t err = NO_ERROR;
-                int64_t val = event.GetLong(key, &err);
-                if (err == NO_ERROR) {
-                    if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt) {
-                        if (!(val == cur.eq_int())) {
-                            allMatched = false;
-                        }
-                    } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt) {
-                        if (!(val < cur.lt_int())) {
-                            allMatched = false;
-                        }
-                    } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt) {
-                        if (!(val > cur.gt_int())) {
-                            allMatched = false;
-                        }
-                    } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt) {
-                        if (!(val <= cur.lte_int())) {
-                            allMatched = false;
-                        }
-                    } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
-                        if (!(val >= cur.gte_int())) {
-                            allMatched = false;
-                        }
-                    }
-                }
-                break;
-            } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqBool) {
-                // Boolean fields
-                status_t err = NO_ERROR;
-                bool val = event.GetBool(key, &err);
-                if (err == NO_ERROR) {
-                    if (!(cur.eq_bool() == val)) {
-                        allMatched = false;
-                    }
-                }
-            } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat
-                    || matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
-                // Float fields
-                status_t err = NO_ERROR;
-                bool val = event.GetFloat(key, &err);
-                if (err == NO_ERROR) {
-                    if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat) {
-                        if (!(cur.lt_float() <= val)) {
-                            allMatched = false;
-                        }
-                    } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
-                        if (!(cur.gt_float() >= val)) {
-                            allMatched = false;
-                        }
-                    }
-                }
-            } else {
-                // If value matcher is not present, assume that we match.
             }
-        }
-
-        if (allMatched) {
-            return true;
+        } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt ||
+                   matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt ||
+                   matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt ||
+                   matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt ||
+                   matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
+            // Integer fields
+            status_t err = NO_ERROR;
+            int64_t val = event.GetLong(key, &err);
+            if (err == NO_ERROR) {
+                if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt) {
+                    if (!(val == cur.eq_int())) {
+                        allMatched = false;
+                    }
+                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt) {
+                    if (!(val < cur.lt_int())) {
+                        allMatched = false;
+                    }
+                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt) {
+                    if (!(val > cur.gt_int())) {
+                        allMatched = false;
+                    }
+                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt) {
+                    if (!(val <= cur.lte_int())) {
+                        allMatched = false;
+                    }
+                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
+                    if (!(val >= cur.gte_int())) {
+                        allMatched = false;
+                    }
+                }
+            }
+            break;
+        } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqBool) {
+            // Boolean fields
+            status_t err = NO_ERROR;
+            bool val = event.GetBool(key, &err);
+            if (err == NO_ERROR) {
+                if (!(cur.eq_bool() == val)) {
+                    allMatched = false;
+                }
+            }
+        } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat ||
+                   matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
+            // Float fields
+            status_t err = NO_ERROR;
+            bool val = event.GetFloat(key, &err);
+            if (err == NO_ERROR) {
+                if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat) {
+                    if (!(cur.lt_float() <= val)) {
+                        allMatched = false;
+                    }
+                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
+                    if (!(cur.gt_float() >= val)) {
+                        allMatched = false;
+                    }
+                }
+            }
+        } else {
+            // If value matcher is not present, assume that we match.
         }
     }
-    return false;
+    return allMatched;
+}
+
+vector<KeyValuePair> getDimensionKey(const LogEvent& event,
+                                     const std::vector<KeyMatcher>& dimensions) {
+    vector<KeyValuePair> key;
+    key.reserve(dimensions.size());
+    for (const KeyMatcher& dimension : dimensions) {
+        KeyValuePair k = event.GetKeyValueProto(dimension.key());
+        key.push_back(k);
+    }
+    return key;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h
index 3a5925c..4ea6f0b 100644
--- a/cmds/statsd/src/matchers/matcher_util.h
+++ b/cmds/statsd/src/matchers/matcher_util.h
@@ -27,6 +27,7 @@
 #include <vector>
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
 
 namespace android {
 namespace os {
@@ -43,6 +44,9 @@
 
 bool matchesSimple(const SimpleLogEntryMatcher& simpleMatcher, const LogEvent& wrapper);
 
+std::vector<KeyValuePair> getDimensionKey(const LogEvent& event,
+                                          const std::vector<KeyMatcher>& dimensions);
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 7df62fb..1f07914 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -17,35 +17,49 @@
 #define DEBUG true  // STOPSHIP if true
 #include "Log.h"
 
-#include "CountMetricProducer.h"
 #include "CountAnomalyTracker.h"
+#include "CountMetricProducer.h"
+#include "stats_util.h"
 
 #include <cutils/log.h>
 #include <limits.h>
 #include <stdlib.h>
 
+using std::map;
+using std::string;
 using std::unordered_map;
+using std::vector;
 
 namespace android {
 namespace os {
 namespace statsd {
 
-CountMetricProducer::CountMetricProducer(const CountMetric& metric, const bool hasCondition)
-    : mMetric(metric),
-      mStartTime(time(nullptr)),
-      mCounter(0),
-      mCurrentBucketStartTime(mStartTime),
+// TODO: add back AnomalyTracker.
+CountMetricProducer::CountMetricProducer(const CountMetric& metric, const int conditionIndex,
+                                         const sp<ConditionWizard>& wizard)
+    // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
+    : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
+      mMetric(metric),
       // TODO: read mAnomalyTracker parameters from config file.
-      mAnomalyTracker(6, 10),
-      mCondition(hasCondition ? ConditionState::kUnknown : ConditionState::kTrue) {
+      mAnomalyTracker(6, 10) {
     // TODO: evaluate initial conditions. and set mConditionMet.
     if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
-        mBucketSize_sec = metric.bucket().bucket_size_millis() / 1000;
+        mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
     } else {
-        mBucketSize_sec = LONG_MAX;
+        mBucketSizeNs = LLONG_MAX;
     }
 
-    VLOG("created. bucket size %lu start_time: %lu", mBucketSize_sec, mStartTime);
+    // TODO: use UidMap if uid->pkg_name is required
+    mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+
+    if (metric.links().size() > 0) {
+        mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+                               metric.links().end());
+        mConditionSliced = true;
+    }
+
+    VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+         (long long)mBucketSizeNs, (long long)mStartTimeNs);
 }
 
 CountMetricProducer::~CountMetricProducer() {
@@ -57,54 +71,146 @@
     // DropboxWriter.
 }
 
-void CountMetricProducer::onDumpReport() {
-    VLOG("dump report now...");
+static void addSlicedCounterToReport(StatsLogReport_CountMetricDataWrapper& wrapper,
+                                     const vector<KeyValuePair>& key,
+                                     const vector<CountBucketInfo>& buckets) {
+    CountMetricData* data = wrapper.add_data();
+    for (const auto& kv : key) {
+        data->add_dimension()->CopyFrom(kv);
+    }
+    for (const auto& bucket : buckets) {
+        data->add_bucket_info()->CopyFrom(bucket);
+        VLOG("\t bucket [%lld - %lld] count: %lld", bucket.start_bucket_nanos(),
+             bucket.end_bucket_nanos(), bucket.count());
+    }
+}
+
+void CountMetricProducer::onSlicedConditionMayChange() {
+    VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+}
+
+StatsLogReport CountMetricProducer::onDumpReport() {
+    VLOG("metric %lld dump report now...", mMetric.metric_id());
+
+    StatsLogReport report;
+    report.set_metric_id(mMetric.metric_id());
+    report.set_start_report_nanos(mStartTimeNs);
+
+    // Dump current bucket if it's stale.
+    // If current bucket is still on-going, don't force dump current bucket.
+    // In finish(), We can force dump current bucket.
+    flushCounterIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+    report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+
+    StatsLogReport_CountMetricDataWrapper* wrapper = report.mutable_count_metrics();
+
+    for (const auto& pair : mPastBuckets) {
+        const HashableDimensionKey& hashableKey = pair.first;
+        auto it = mDimensionKeyMap.find(hashableKey);
+        if (it == mDimensionKeyMap.end()) {
+            ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+            continue;
+        }
+
+        VLOG("  dimension key %s", hashableKey.c_str());
+        addSlicedCounterToReport(*wrapper, it->second, pair.second);
+    }
+    return report;
+    // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
 }
 
 void CountMetricProducer::onConditionChanged(const bool conditionMet) {
-    VLOG("onConditionChanged");
+    VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
     mCondition = conditionMet;
 }
 
-void CountMetricProducer::onMatchedLogEvent(const LogEvent& event) {
-    time_t eventTime = event.GetTimestampNs() / 1000000000;
-
+void CountMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
+    uint64_t eventTimeNs = event.GetTimestampNs();
     // this is old event, maybe statsd restarted?
-    if (eventTime < mStartTime) {
+    if (eventTimeNs < mStartTimeNs) {
         return;
     }
 
-    if (mCondition == ConditionState::kTrue) {
-        flushCounterIfNeeded(eventTime);
-        mCounter++;
-        mAnomalyTracker.checkAnomaly(mCounter);
-        VLOG("metric %lld count %d", mMetric.metric_id(), mCounter);
+    flushCounterIfNeeded(eventTimeNs);
+
+    if (mConditionSliced) {
+        map<string, HashableDimensionKey> conditionKeys;
+        for (const auto& link : mConditionLinks) {
+            VLOG("Condition link key_in_main size %d", link.key_in_main_size());
+            HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
+            conditionKeys[link.condition()] = conditionKey;
+        }
+        if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) {
+            VLOG("metric %lld sliced condition not met", mMetric.metric_id());
+            return;
+        }
+    } else {
+        if (!mCondition) {
+            VLOG("metric %lld condition not met", mMetric.metric_id());
+            return;
+        }
     }
+
+    HashableDimensionKey hashableKey;
+
+    if (mDimension.size() > 0) {
+        vector<KeyValuePair> key = getDimensionKey(event, mDimension);
+        hashableKey = getHashableKey(key);
+        // Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport
+        // expects vector<KeyValuePair>.
+        if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) {
+            mDimensionKeyMap[hashableKey] = key;
+        }
+    } else {
+        hashableKey = DEFAULT_DIMENSION_KEY;
+    }
+
+    auto it = mCurrentSlicedCounter.find(hashableKey);
+
+    if (it == mCurrentSlicedCounter.end()) {
+        // create a counter for the new key
+        mCurrentSlicedCounter[hashableKey] = 1;
+
+    } else {
+        // increment the existing value
+        auto& count = it->second;
+        count++;
+    }
+
+    VLOG("metric %lld %s->%d", mMetric.metric_id(), hashableKey.c_str(),
+         mCurrentSlicedCounter[hashableKey]);
 }
 
-// When a new matched event comes in, we check if it falls into the current
-// bucket. And flush the counter to the StatsLogReport and adjust the bucket if
-// needed.
-void CountMetricProducer::flushCounterIfNeeded(const time_t& eventTime) {
-    if (mCurrentBucketStartTime + mBucketSize_sec > eventTime) {
+// When a new matched event comes in, we check if event falls into the current
+// bucket. If not, flush the old counter to past buckets and initialize the new bucket.
+void CountMetricProducer::flushCounterIfNeeded(const uint64_t eventTimeNs) {
+    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
         return;
     }
 
-    // TODO: add a KeyValuePair to StatsLogReport.
-    ALOGD("%lld:  dump counter %d", mMetric.metric_id(), mCounter);
-
     // adjust the bucket start time
-    time_t numBucketsForward = (eventTime - mCurrentBucketStartTime)
-            / mBucketSize_sec;
+    int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
 
-    mCurrentBucketStartTime = mCurrentBucketStartTime +
-            (numBucketsForward) * mBucketSize_sec;
+    CountBucketInfo info;
+    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+    info.set_end_bucket_nanos(mCurrentBucketStartTimeNs + mBucketSizeNs);
 
-    // reset counter
-    mAnomalyTracker.addPastBucket(mCounter, numBucketsForward);
-    mCounter = 0;
+    for (const auto& counter : mCurrentSlicedCounter) {
+        info.set_count(counter.second);
+        // it will auto create new vector of CountbucketInfo if the key is not found.
+        auto& bucketList = mPastBuckets[counter.first];
+        bucketList.push_back(info);
 
-    VLOG("%lld: new bucket start time: %lu", mMetric.metric_id(), mCurrentBucketStartTime);
+        VLOG("metric %lld, dump key value: %s -> %d", mMetric.metric_id(), counter.first.c_str(),
+             counter.second);
+    }
+
+    // Reset counters
+    mCurrentSlicedCounter.clear();
+
+    mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+    VLOG("metric %lld: new bucket start time: %lld", mMetric.metric_id(),
+         (long long)mCurrentBucketStartTimeNs);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 94abd62..f0d6025 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -19,12 +19,13 @@
 
 #include <unordered_map>
 
-#include "CountAnomalyTracker.h"
 #include "../condition/ConditionTracker.h"
 #include "../matchers/matcher_util.h"
+#include "CountAnomalyTracker.h"
 #include "MetricProducer.h"
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
 
 using namespace std;
 
@@ -34,38 +35,37 @@
 
 class CountMetricProducer : public MetricProducer {
 public:
-    CountMetricProducer(const CountMetric& countMetric, const bool hasCondition);
+    // TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
+    CountMetricProducer(const CountMetric& countMetric, const int conditionIndex,
+                        const sp<ConditionWizard>& wizard);
 
     virtual ~CountMetricProducer();
 
-    void onMatchedLogEvent(const LogEvent& event) override;
+    void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) override;
 
     void onConditionChanged(const bool conditionMet) override;
 
     void finish() override;
 
-    void onDumpReport() override;
+    StatsLogReport onDumpReport() override;
+
+    void onSlicedConditionMayChange() override;
 
     // TODO: Implement this later.
-    virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override {};
+    virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
 
 private:
     const CountMetric mMetric;
 
-    const time_t mStartTime;
-    // TODO: Add dimensions.
-    // Counter value for the current bucket.
-    int mCounter;
-
-    time_t mCurrentBucketStartTime;
-
-    long mBucketSize_sec;
-
     CountAnomalyTracker mAnomalyTracker;
 
-    bool mCondition;
+    // Save the past buckets and we can clear when the StatsLogReport is dumped.
+    std::unordered_map<HashableDimensionKey, std::vector<CountBucketInfo>> mPastBuckets;
 
-    void flushCounterIfNeeded(const time_t& newEventTime);
+    // The current bucket.
+    std::unordered_map<HashableDimensionKey, int> mCurrentSlicedCounter;
+
+    void flushCounterIfNeeded(const uint64_t newEventTime);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
new file mode 100644
index 0000000..aa597f4
--- /dev/null
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true
+#include "DurationMetricProducer.h"
+#include "Log.h"
+#include "stats_util.h"
+
+#include <cutils/log.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric,
+                                               const int conditionIndex, const size_t startIndex,
+                                               const size_t stopIndex, const size_t stopAllIndex,
+                                               const sp<ConditionWizard>& wizard)
+    // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
+    : MetricProducer(time(nullptr) * NANO_SECONDS_IN_A_SECOND, conditionIndex, wizard),
+      mMetric(metric),
+      mStartIndex(startIndex),
+      mStopIndex(stopIndex),
+      mStopAllIndex(stopAllIndex) {
+    // TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract
+    // them in the base class, because the proto generated CountMetric, and DurationMetric are
+    // not related. Maybe we should add a template in the future??
+    if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
+        mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000000;
+    } else {
+        mBucketSizeNs = LLONG_MAX;
+    }
+
+    // TODO: use UidMap if uid->pkg_name is required
+    mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+
+    if (metric.links().size() > 0) {
+        mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+                               metric.links().end());
+        mConditionSliced = true;
+    }
+
+    VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+         (long long)mBucketSizeNs, (long long)mStartTimeNs);
+}
+
+DurationMetricProducer::~DurationMetricProducer() {
+    VLOG("~DurationMetric() called");
+}
+
+void DurationMetricProducer::finish() {
+    // TODO: write the StatsLogReport to dropbox using
+    // DropboxWriter.
+}
+
+void DurationMetricProducer::onSlicedConditionMayChange() {
+    VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+    // Now for each of the on-going event, check if the condition has changed for them.
+    for (auto& pair : mCurrentSlicedDuration) {
+        VLOG("Metric %lld current %s state: %d", mMetric.metric_id(), pair.first.c_str(),
+             pair.second.state);
+        if (pair.second.state == kStopped) {
+            continue;
+        }
+        bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
+                            ConditionState::kTrue;
+        VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
+        noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+    }
+}
+
+void DurationMetricProducer::onConditionChanged(const bool conditionMet) {
+    VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
+    mCondition = conditionMet;
+    // TODO: need to populate the condition change time from the event which triggers the condition
+    // change, instead of using current time.
+    for (auto& pair : mCurrentSlicedDuration) {
+        noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+    }
+}
+
+static void addDurationBucketsToReport(StatsLogReport_DurationMetricDataWrapper& wrapper,
+                                       const vector<KeyValuePair>& key,
+                                       const vector<DurationBucketInfo>& buckets) {
+    DurationMetricData* data = wrapper.add_data();
+    for (const auto& kv : key) {
+        data->add_dimension()->CopyFrom(kv);
+    }
+    for (const auto& bucket : buckets) {
+        data->add_bucket_info()->CopyFrom(bucket);
+        VLOG("\t bucket [%lld - %lld] count: %lld", bucket.start_bucket_nanos(),
+             bucket.end_bucket_nanos(), bucket.duration_nanos());
+    }
+}
+
+StatsLogReport DurationMetricProducer::onDumpReport() {
+    VLOG("metric %lld dump report now...", mMetric.metric_id());
+    StatsLogReport report;
+    report.set_metric_id(mMetric.metric_id());
+    report.set_start_report_nanos(mStartTimeNs);
+    // Dump current bucket if it's stale.
+    // If current bucket is still on-going, don't force dump current bucket.
+    // In finish(), We can force dump current bucket.
+    flushDurationIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+    report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+
+    StatsLogReport_DurationMetricDataWrapper* wrapper = report.mutable_duration_metrics();
+    for (const auto& pair : mPastBuckets) {
+        const HashableDimensionKey& hashableKey = pair.first;
+        auto it = mDimensionKeyMap.find(hashableKey);
+        if (it == mDimensionKeyMap.end()) {
+            ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+            continue;
+        }
+        VLOG("  dimension key %s", hashableKey.c_str());
+        addDurationBucketsToReport(*wrapper, it->second, pair.second);
+    }
+    return report;
+};
+
+void DurationMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
+    if (event.GetTimestampNs() < mStartTimeNs) {
+        return;
+    }
+
+    flushDurationIfNeeded(event.GetTimestampNs());
+
+    if (matcherIndex == mStopAllIndex) {
+        noteStopAll(event.GetTimestampNs());
+        return;
+    }
+
+    HashableDimensionKey hashableKey;
+    if (mDimension.size() > 0) {
+        // hook up sliced counter with AnomalyMonitor.
+        vector<KeyValuePair> key = getDimensionKey(event, mDimension);
+        hashableKey = getHashableKey(key);
+        // Add the HashableDimensionKey->DimensionKey to the map, because StatsLogReport expects
+        // vector<KeyValuePair>.
+        if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) {
+            mDimensionKeyMap[hashableKey] = key;
+        }
+    } else {
+        hashableKey = DEFAULT_DIMENSION_KEY;
+    }
+
+    if (mCurrentSlicedDuration.find(hashableKey) == mCurrentSlicedDuration.end() &&
+        mConditionSliced) {
+        // add the durationInfo for the current bucket.
+        auto& durationInfo = mCurrentSlicedDuration[hashableKey];
+        auto& conditionKeys = durationInfo.conditionKeys;
+        // get and cache the keys for query condition.
+        for (const auto& link : mConditionLinks) {
+            HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
+            conditionKeys[link.condition()] = conditionKey;
+        }
+    }
+
+    bool conditionMet;
+    if (mConditionSliced) {
+        const auto& conditionKeys = mCurrentSlicedDuration[hashableKey].conditionKeys;
+        conditionMet =
+                mWizard->query(mConditionTrackerIndex, conditionKeys) == ConditionState::kTrue;
+    } else {
+        conditionMet = mCondition;
+    }
+
+    if (matcherIndex == mStartIndex) {
+        VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), hashableKey.c_str(),
+             conditionMet);
+        noteStart(hashableKey, conditionMet, event.GetTimestampNs());
+    } else if (matcherIndex == mStopIndex) {
+        VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), hashableKey.c_str(),
+             conditionMet);
+        noteStop(hashableKey, event.GetTimestampNs());
+    }
+}
+
+void DurationMetricProducer::noteConditionChanged(const HashableDimensionKey& key,
+                                                  const bool conditionMet,
+                                                  const uint64_t eventTime) {
+    flushDurationIfNeeded(eventTime);
+
+    auto it = mCurrentSlicedDuration.find(key);
+    if (it == mCurrentSlicedDuration.end()) {
+        return;
+    }
+
+    switch (it->second.state) {
+        case kStarted:
+            // if condition becomes false, kStarted -> kPaused. Record the current duration.
+            if (!conditionMet) {
+                it->second.state = DurationState::kPaused;
+                it->second.lastDuration =
+                        updateDuration(it->second.lastDuration,
+                                       eventTime - it->second.lastStartTime, mMetric.type());
+                VLOG("Metric %lld Key: %s Paused because condition is false ", mMetric.metric_id(),
+                     key.c_str());
+            }
+            break;
+        case kStopped:
+            // nothing to do if it's stopped.
+            break;
+        case kPaused:
+            // if condition becomes true, kPaused -> kStarted. and the start time is the condition
+            // change time.
+            if (conditionMet) {
+                it->second.state = DurationState::kStarted;
+                it->second.lastStartTime = eventTime;
+                VLOG("Metric %lld Key: %s Paused->Started", mMetric.metric_id(), key.c_str());
+            }
+            break;
+    }
+}
+
+void DurationMetricProducer::noteStart(const HashableDimensionKey& key, const bool conditionMet,
+                                       const uint64_t eventTime) {
+    // this will add an empty bucket for this key if it didn't exist before.
+    DurationInfo& duration = mCurrentSlicedDuration[key];
+
+    switch (duration.state) {
+        case kStarted:
+            // It's safe to do nothing here. even if condition is not true, it means we are about
+            // to receive the condition change event.
+            break;
+        case kPaused:
+            // Safe to do nothing here. kPaused is waiting for the condition change.
+            break;
+        case kStopped:
+            if (!conditionMet) {
+                // event started, but we need to wait for the condition to become true.
+                duration.state = DurationState::kPaused;
+                break;
+            }
+            duration.state = DurationState::kStarted;
+            duration.lastStartTime = eventTime;
+            break;
+    }
+}
+
+void DurationMetricProducer::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
+    if (mCurrentSlicedDuration.find(key) == mCurrentSlicedDuration.end()) {
+        // we didn't see a start event before. do nothing.
+        return;
+    }
+    DurationInfo& duration = mCurrentSlicedDuration[key];
+
+    switch (duration.state) {
+        case DurationState::kStopped:
+            // already stopped, do nothing.
+            break;
+        case DurationState::kStarted: {
+            duration.state = DurationState::kStopped;
+            int64_t durationTime = eventTime - duration.lastStartTime;
+            VLOG("Metric %lld, key %s, Stop %lld %lld %lld", mMetric.metric_id(), key.c_str(),
+                 (long long)duration.lastStartTime, (long long)eventTime, (long long)durationTime);
+            duration.lastDuration =
+                    updateDuration(duration.lastDuration, durationTime, mMetric.type());
+            VLOG("  record duration: %lld ", (long long)duration.lastDuration);
+            break;
+        }
+        case DurationState::kPaused: {
+            duration.state = DurationState::kStopped;
+            break;
+        }
+    }
+}
+
+int64_t DurationMetricProducer::updateDuration(const int64_t lastDuration,
+                                               const int64_t durationTime,
+                                               const DurationMetric_AggregationType type) {
+    int64_t result = lastDuration;
+    switch (type) {
+        case DurationMetric_AggregationType_DURATION_SUM:
+            result += durationTime;
+            break;
+        case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
+            if (lastDuration < durationTime) {
+                result = durationTime;
+            }
+            break;
+        case DurationMetric_AggregationType_DURATION_MIN_SPARSE:
+            if (lastDuration > durationTime) {
+                result = durationTime;
+            }
+            break;
+    }
+    return result;
+}
+
+void DurationMetricProducer::noteStopAll(const uint64_t eventTime) {
+    for (auto& duration : mCurrentSlicedDuration) {
+        noteStop(duration.first, eventTime);
+    }
+}
+
+// When a new matched event comes in, we check if event falls into the current
+// bucket. If not, flush the old counter to past buckets and initialize the current buckt.
+void DurationMetricProducer::flushDurationIfNeeded(const uint64_t eventTime) {
+    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+        return;
+    }
+
+    // adjust the bucket start time
+    int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+
+    DurationBucketInfo info;
+    uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
+    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+    info.set_end_bucket_nanos(endTime);
+
+    uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
+    mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
+    VLOG("Metric %lld: new bucket start time: %lld", mMetric.metric_id(),
+         (long long)mCurrentBucketStartTimeNs);
+
+    for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end(); ++it) {
+        int64_t finalDuration = it->second.lastDuration;
+        if (it->second.state == kStarted) {
+            // the event is still on-going, duration needs to be updated.
+            int64_t durationTime = endTime - it->second.lastStartTime;
+            finalDuration = updateDuration(it->second.lastDuration, durationTime, mMetric.type());
+        }
+
+        VLOG("  final duration for last bucket: %lld", (long long)finalDuration);
+
+        // Don't record empty bucket.
+        if (finalDuration != 0) {
+            info.set_duration_nanos(finalDuration);
+            // it will auto create new vector of CountbucketInfo if the key is not found.
+            auto& bucketList = mPastBuckets[it->first];
+            bucketList.push_back(info);
+        }
+
+        // if the event is still on-going, add the buckets between previous bucket and now. Because
+        // the event has been going on across all the buckets in between.
+        // |prev_bucket|...|..|...|now_bucket|
+        if (it->second.state == kStarted) {
+            for (int i = 1; i < numBucketsForward; i++) {
+                DurationBucketInfo info;
+                info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
+                info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
+                info.set_duration_nanos(mBucketSizeNs);
+                auto& bucketList = mPastBuckets[it->first];
+                bucketList.push_back(info);
+                VLOG("  add filling bucket with duration %lld", (long long)mBucketSizeNs);
+            }
+        }
+
+        if (it->second.state == DurationState::kStopped) {
+            // No need to keep buckets for events that were stopped before. If the event starts
+            // again, we will add it back.
+            mCurrentSlicedDuration.erase(it);
+        } else {
+            // for kPaused, and kStarted event, we will keep the buckets, and reset the start time
+            // and duration.
+            it->second.lastStartTime = mCurrentBucketStartTimeNs;
+            it->second.lastDuration = 0;
+        }
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
new file mode 100644
index 0000000..44c3254
--- /dev/null
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef DURATION_METRIC_PRODUCER_H
+#define DURATION_METRIC_PRODUCER_H
+
+#include <unordered_map>
+
+#include "../condition/ConditionTracker.h"
+#include "../matchers/matcher_util.h"
+#include "MetricProducer.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+enum DurationState {
+    kStopped = 0,  // The event is stopped.
+    kStarted = 1,  // The event is on going.
+    kPaused = 2,   // The event is started, but condition is false, clock is paused. When condition
+                   // turns to true, kPaused will become kStarted.
+};
+
+// Hold duration information for current on-going bucket.
+struct DurationInfo {
+    DurationState state;
+    // most recent start time.
+    int64_t lastStartTime;
+    // existing duration in current bucket. Eventually, the duration will be aggregated in
+    // the way specified in AggregateType (Sum, Max, or Min).
+    int64_t lastDuration;
+    // cache the HashableDimensionKeys we need to query the condition for this duration event.
+    std::map<string, HashableDimensionKey> conditionKeys;
+
+    DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
+};
+
+class DurationMetricProducer : public MetricProducer {
+public:
+    DurationMetricProducer(const DurationMetric& durationMetric, const int conditionIndex,
+                           const size_t startIndex, const size_t stopIndex,
+                           const size_t stopAllIndex, const sp<ConditionWizard>& wizard);
+
+    virtual ~DurationMetricProducer();
+
+    void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) override;
+
+    void onConditionChanged(const bool conditionMet) override;
+
+    void finish() override;
+
+    StatsLogReport onDumpReport() override;
+
+    void onSlicedConditionMayChange() override;
+
+    // TODO: Implement this later.
+    virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+
+private:
+    const DurationMetric mMetric;
+
+    // Index of the SimpleLogEntryMatcher which defines the start.
+    const size_t mStartIndex;
+
+    // Index of the SimpleLogEntryMatcher which defines the stop.
+    const size_t mStopIndex;
+
+    // Index of the SimpleLogEntryMatcher which defines the stop all for all dimensions.
+    const size_t mStopAllIndex;
+
+    // Save the past buckets and we can clear when the StatsLogReport is dumped.
+    std::unordered_map<HashableDimensionKey, std::vector<DurationBucketInfo>> mPastBuckets;
+
+    // The current bucket.
+    std::unordered_map<HashableDimensionKey, DurationInfo> mCurrentSlicedDuration;
+
+    void flushDurationIfNeeded(const uint64_t newEventTime);
+
+    void noteStart(const HashableDimensionKey& key, const bool conditionMet,
+                   const uint64_t eventTime);
+
+    void noteStop(const HashableDimensionKey& key, const uint64_t eventTime);
+
+    void noteStopAll(const uint64_t eventTime);
+
+    static int64_t updateDuration(const int64_t lastDuration, const int64_t durationTime,
+                                  const DurationMetric_AggregationType type);
+
+    void noteConditionChanged(const HashableDimensionKey& key, const bool conditionMet,
+                              const uint64_t eventTime);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // DURATION_METRIC_PRODUCER_H
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 589df84..afaab64 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -17,11 +17,13 @@
 #ifndef METRIC_PRODUCER_H
 #define METRIC_PRODUCER_H
 
+#include "condition/ConditionWizard.h"
 #include "matchers/matcher_util.h"
 #include "packages/PackageInfoListener.h"
 
 #include <log/logprint.h>
 #include <utils/RefBase.h>
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 
 namespace android {
 namespace os {
@@ -33,18 +35,57 @@
 // be a no-op.
 class MetricProducer : public virtual PackageInfoListener {
 public:
+    MetricProducer(const int64_t startTimeNs, const int conditionIndex,
+                   const sp<ConditionWizard>& wizard)
+        : mStartTimeNs(startTimeNs),
+          mCurrentBucketStartTimeNs(startTimeNs),
+          mCondition(conditionIndex >= 0 ? false : true),
+          mWizard(wizard),
+          mConditionTrackerIndex(conditionIndex) {
+        // reuse the same map for non-sliced metrics too. this way, we avoid too many if-else.
+        mDimensionKeyMap[DEFAULT_DIMENSION_KEY] = std::vector<KeyValuePair>();
+    };
     virtual ~MetricProducer(){};
 
     // Consume the parsed stats log entry that already matched the "what" of the metric.
-    virtual void onMatchedLogEvent(const LogEvent& event) = 0;
+    virtual void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) = 0;
 
     virtual void onConditionChanged(const bool condition) = 0;
 
+    virtual void onSlicedConditionMayChange() = 0;
+
     // This is called when the metric collecting is done, e.g., when there is a new configuration
     // coming. MetricProducer should do the clean up, and dump existing data to dropbox.
     virtual void finish() = 0;
 
-    virtual void onDumpReport() = 0;
+    virtual StatsLogReport onDumpReport() = 0;
+
+    virtual bool isConditionSliced() const {
+        return mConditionSliced;
+    };
+
+protected:
+    const uint64_t mStartTimeNs;
+
+    uint64_t mCurrentBucketStartTimeNs;
+
+    int64_t mBucketSizeNs;
+
+    bool mCondition;
+
+    bool mConditionSliced;
+
+    sp<ConditionWizard> mWizard;
+
+    int mConditionTrackerIndex;
+
+    std::vector<KeyMatcher> mDimension;  // The dimension defined in statsd_config
+
+    // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
+    // that StatsLogReport wants.
+    std::unordered_map<HashableDimensionKey, std::vector<KeyValuePair>> mDimensionKeyMap;
+
+    std::vector<EventConditionLink> mConditionLinks;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 5b4ca80..c19d462 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -15,8 +15,6 @@
  */
 #define DEBUG true  // STOPSHIP if true
 #include "Log.h"
-#define VLOG(...) \
-    if (DEBUG) ALOGD(__VA_ARGS__);
 
 #include "MetricsManager.h"
 #include <log/logprint.h>
@@ -58,6 +56,17 @@
     }
 }
 
+vector<StatsLogReport> MetricsManager::onDumpReport() {
+    VLOG("=========================Metric Reports Start==========================");
+    // one StatsLogReport per MetricProduer
+    vector<StatsLogReport> reportList;
+    for (auto& metric : mAllMetricProducers) {
+        reportList.push_back(metric->onDumpReport());
+    }
+    VLOG("=========================Metric Reports End==========================");
+    return reportList;
+}
+
 // Consume the stats log if it's interesting to this metric.
 void MetricsManager::onLogEvent(const LogEvent& event) {
     if (!mConfigValid) {
@@ -71,6 +80,7 @@
     }
 
     // Since at least one of the metrics is interested in this event, we parse it now.
+    ALOGD("%s", event.ToString().c_str());
     vector<MatchingState> matcherCache(mAllLogEntryMatchers.size(), MatchingState::kNotComputed);
 
     for (auto& matcher : mAllLogEntryMatchers) {
@@ -93,20 +103,34 @@
                                           ConditionState::kNotEvaluated);
     // A bitmap to track if a condition has changed value.
     vector<bool> changedCache(mAllConditionTrackers.size(), false);
+    vector<bool> slicedChangedCache(mAllConditionTrackers.size(), false);
     for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
         if (conditionToBeEvaluated[i] == false) {
             continue;
         }
-
         sp<ConditionTracker>& condition = mAllConditionTrackers[i];
         condition->evaluateCondition(event, matcherCache, mAllConditionTrackers, conditionCache,
-                                     changedCache);
-        if (changedCache[i]) {
-            auto pair = mConditionToMetricMap.find(i);
-            if (pair != mConditionToMetricMap.end()) {
-                auto& metricList = pair->second;
-                for (auto metricIndex : metricList) {
+                                     changedCache, slicedChangedCache);
+    }
+
+    for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
+        if (changedCache[i] == false && slicedChangedCache[i] == false) {
+            continue;
+        }
+        auto pair = mConditionToMetricMap.find(i);
+        if (pair != mConditionToMetricMap.end()) {
+            auto& metricList = pair->second;
+            for (auto metricIndex : metricList) {
+                // metric cares about non sliced condition, and it's changed.
+                // Push the new condition to it directly.
+                if (!mAllMetricProducers[metricIndex]->isConditionSliced() && changedCache[i]) {
                     mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i]);
+                    // metric cares about sliced conditions, and it may have changed. Send
+                    // notification, and the metric can query the sliced conditions that are
+                    // interesting to it.
+                } else if (mAllMetricProducers[metricIndex]->isConditionSliced() &&
+                           slicedChangedCache[i]) {
+                    mAllMetricProducers[metricIndex]->onSlicedConditionMayChange();
                 }
             }
         }
@@ -119,7 +143,7 @@
             if (pair != mTrackerToMetricMap.end()) {
                 auto& metricList = pair->second;
                 for (const int metricIndex : metricList) {
-                    mAllMetricProducers[metricIndex]->onMatchedLogEvent(event);
+                    mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event);
                 }
             }
         }
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 7aca0b5..56f57d3 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -43,17 +43,19 @@
     // Called when everything should wrap up. We are about to finish (e.g., new config comes).
     void finish();
 
+    // Config source owner can call onDumpReport() to get all the metrics collected.
+    std::vector<StatsLogReport> onDumpReport();
+
 private:
     // All event tags that are interesting to my metrics.
     std::set<int> mTagIds;
 
     // We only store the sp of LogMatchingTracker, MetricProducer, and ConditionTracker in
     // MetricManager. There are relationship between them, and the relationship are denoted by index
-    // instead of poiters. The reasons for this are: (1) the relationship between them are
+    // instead of pointers. The reasons for this are: (1) the relationship between them are
     // complicated, store index instead of pointers reduce the risk of A holds B's sp, and B holds
     // A's sp. (2) When we evaluate matcher results, or condition results, we can quickly get the
     // related results from a cache using the index.
-    // TODO: using unique_ptr may be more appriopreate?
 
     // Hold all the log entry matchers from the config.
     std::vector<sp<LogMatchingTracker>> mAllLogEntryMatchers;
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 6fdd228..23071aa 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -19,6 +19,7 @@
 #include "../matchers/CombinationLogMatchingTracker.h"
 #include "../matchers/SimpleLogMatchingTracker.h"
 #include "CountMetricProducer.h"
+#include "DurationMetricProducer.h"
 #include "stats_util.h"
 
 using std::set;
@@ -30,11 +31,23 @@
 namespace os {
 namespace statsd {
 
+int getTrackerIndex(const string& name, const unordered_map<string, int>& logTrackerMap) {
+    auto logTrackerIt = logTrackerMap.find(name);
+    if (logTrackerIt == logTrackerMap.end()) {
+        ALOGW("cannot find the LogEventMatcher %s in config", name.c_str());
+        return MATCHER_NOT_FOUND;
+    }
+    return logTrackerIt->second;
+}
+
 bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& logTrackerMap,
                      vector<sp<LogMatchingTracker>>& allLogEntryMatchers, set<int>& allTagIds) {
     vector<LogEntryMatcher> matcherConfigs;
+    const int logEntryMatcherCount = config.log_entry_matcher_size();
+    matcherConfigs.reserve(logEntryMatcherCount);
+    allLogEntryMatchers.reserve(logEntryMatcherCount);
 
-    for (int i = 0; i < config.log_entry_matcher_size(); i++) {
+    for (int i = 0; i < logEntryMatcherCount; i++) {
         const LogEntryMatcher& logMatcher = config.log_entry_matcher(i);
 
         int index = allLogEntryMatchers.size();
@@ -77,8 +90,11 @@
                     vector<sp<ConditionTracker>>& allConditionTrackers,
                     unordered_map<int, std::vector<int>>& trackerToConditionMap) {
     vector<Condition> conditionConfigs;
+    const int conditionTrackerCount = config.condition_size();
+    conditionConfigs.reserve(conditionTrackerCount);
+    allConditionTrackers.reserve(conditionTrackerCount);
 
-    for (int i = 0; i < config.condition_size(); i++) {
+    for (int i = 0; i < conditionTrackerCount; i++) {
         const Condition& condition = config.condition(i);
         int index = allConditionTrackers.size();
         switch (condition.contents_case()) {
@@ -121,9 +137,14 @@
 
 bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& logTrackerMap,
                  const unordered_map<string, int>& conditionTrackerMap,
+                 vector<sp<ConditionTracker>>& allConditionTrackers,
                  vector<sp<MetricProducer>>& allMetricProducers,
                  unordered_map<int, std::vector<int>>& conditionToMetricMap,
                  unordered_map<int, std::vector<int>>& trackerToMetricMap) {
+    sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
+    const int allMetricsCount = config.count_metric_size() + config.duration_metric_size();
+    allMetricProducers.reserve(allMetricsCount);
+
     // Build MetricProducers for each metric defined in config.
     // (1) build CountMetricProducer
     for (int i = 0; i < config.count_metric_size(); i++) {
@@ -133,36 +154,109 @@
             return false;
         }
 
+        int metricIndex = allMetricProducers.size();
+
         auto logTrackerIt = logTrackerMap.find(metric.what());
         if (logTrackerIt == logTrackerMap.end()) {
             ALOGW("cannot find the LogEntryMatcher %s in config", metric.what().c_str());
             return false;
         }
+        int logTrackerIndex = logTrackerIt->second;
+        auto& metric_list = trackerToMetricMap[logTrackerIndex];
+        metric_list.push_back(metricIndex);
 
         sp<MetricProducer> countProducer;
-        int metricIndex = allMetricProducers.size();
+
         if (metric.has_condition()) {
             auto condition_it = conditionTrackerMap.find(metric.condition());
             if (condition_it == conditionTrackerMap.end()) {
                 ALOGW("cannot find the Condition %s in the config", metric.condition().c_str());
                 return false;
             }
-            countProducer = new CountMetricProducer(metric, true /*has condition*/);
+
+            for (const auto& link : metric.links()) {
+                auto it = conditionTrackerMap.find(link.condition());
+                if (it == conditionTrackerMap.end()) {
+                    ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
+                    return false;
+                }
+                allConditionTrackers[condition_it->second]->setSliced(true);
+                allConditionTrackers[it->second]->setSliced(true);
+                allConditionTrackers[it->second]->addDimensions(vector<KeyMatcher>(
+                        link.key_in_condition().begin(), link.key_in_condition().end()));
+            }
+
+            countProducer = new CountMetricProducer(metric, condition_it->second, wizard);
             // will create new vector if not exist before.
             auto& metricList = conditionToMetricMap[condition_it->second];
             metricList.push_back(metricIndex);
         } else {
-            countProducer = new CountMetricProducer(metric, false /*no condition*/);
+            countProducer = new CountMetricProducer(metric, -1 /*no condition*/, wizard);
         }
-
-        int logTrackerIndex = logTrackerIt->second;
-        auto& metric_list = trackerToMetricMap[logTrackerIndex];
-        metric_list.push_back(metricIndex);
         allMetricProducers.push_back(countProducer);
     }
 
-    // TODO: build other types of metrics too.
+    for (int i = 0; i < config.duration_metric_size(); i++) {
+        int metricIndex = allMetricProducers.size();
+        const DurationMetric metric = config.duration_metric(i);
+        if (!metric.has_start()) {
+            ALOGW("cannot find start in DurationMetric %lld", metric.metric_id());
+            return false;
+        }
 
+        int trackerIndices[] = {-1, -1, -1};
+        trackerIndices[0] = getTrackerIndex(metric.start(), logTrackerMap);
+
+        if (metric.has_stop()) {
+            trackerIndices[1] = getTrackerIndex(metric.stop(), logTrackerMap);
+        }
+
+        if (metric.has_stop_all()) {
+            trackerIndices[2] = getTrackerIndex(metric.stop_all(), logTrackerMap);
+        }
+
+        for (const int& index : trackerIndices) {
+            if (index == MATCHER_NOT_FOUND) {
+                return false;
+            }
+            if (index >= 0) {
+                auto& metric_list = trackerToMetricMap[index];
+                metric_list.push_back(metricIndex);
+            }
+        }
+
+        int conditionIndex = -1;
+
+        if (metric.has_predicate()) {
+            auto condition_it = conditionTrackerMap.find(metric.predicate());
+            if (condition_it == conditionTrackerMap.end()) {
+                ALOGW("cannot find the Condition %s in the config", metric.predicate().c_str());
+                return false;
+            }
+            conditionIndex = condition_it->second;
+
+            for (const auto& link : metric.links()) {
+                auto it = conditionTrackerMap.find(link.condition());
+                if (it == conditionTrackerMap.end()) {
+                    ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
+                    return false;
+                }
+                allConditionTrackers[condition_it->second]->setSliced(true);
+                allConditionTrackers[it->second]->setSliced(true);
+                allConditionTrackers[it->second]->addDimensions(vector<KeyMatcher>(
+                        link.key_in_condition().begin(), link.key_in_condition().end()));
+            }
+
+            auto& metricList = conditionToMetricMap[conditionIndex];
+            metricList.push_back(metricIndex);
+        }
+
+        sp<MetricProducer> durationMetric =
+                new DurationMetricProducer(metric, conditionIndex, trackerIndices[0],
+                                           trackerIndices[1], trackerIndices[2], wizard);
+
+        allMetricProducers.push_back(durationMetric);
+    }
     return true;
 }
 
@@ -187,8 +281,8 @@
         return false;
     }
 
-    if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allMetricProducers,
-                     conditionToMetricMap, trackerToMetricMap)) {
+    if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
+                     allMetricProducers, conditionToMetricMap, trackerToMetricMap)) {
         ALOGE("initMetricProducers failed");
         return false;
     }
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 5f1f295..38149a6 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -59,7 +59,8 @@
                     const std::unordered_map<std::string, int>& logTrackerMap,
                     std::unordered_map<std::string, int>& conditionTrackerMap,
                     std::vector<sp<ConditionTracker>>& allConditionTrackers,
-                    std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
+                    std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
+                    std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks);
 
 // Initialize MetricProducers.
 // input:
@@ -71,12 +72,14 @@
 // [conditionToMetricMap]: contains the mapping from condition tracker index to
 //                          the list of MetricProducer index
 // [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
-bool initMetrics(const StatsdConfig& config,
-                 const std::unordered_map<std::string, int>& logTrackerMap,
-                 const std::unordered_map<std::string, int>& conditionTrackerMap,
-                 std::vector<sp<MetricProducer>>& allMetricProducers,
-                 std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
-                 std::unordered_map<int, std::vector<int>>& trackerToMetricMap);
+bool initMetrics(
+        const StatsdConfig& config, const std::unordered_map<std::string, int>& logTrackerMap,
+        const std::unordered_map<std::string, int>& conditionTrackerMap,
+        const std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks,
+        vector<sp<ConditionTracker>>& allConditionTrackers,
+        std::vector<sp<MetricProducer>>& allMetricProducers,
+        std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+        std::unordered_map<int, std::vector<int>>& trackerToMetricMap);
 
 // Initialize MetricManager from StatsdConfig.
 // Parameters are the members of MetricsManager. See MetricsManager for declaration.
@@ -88,6 +91,8 @@
                       std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
                       std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
 
+int getTrackerIndex(const std::string& name, const std::unordered_map<string, int>& logTrackerMap);
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 4ca06fa..29cd94b 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -106,7 +106,7 @@
     repeated CountMetricData data = 1;
   }
   message DurationMetricDataWrapper {
-    repeated CountMetricData data = 1;
+    repeated DurationMetricData data = 1;
   }
   oneof data {
     EventMetricDataWrapper event_metrics = 4;
diff --git a/cmds/statsd/src/stats_util.cpp b/cmds/statsd/src/stats_util.cpp
index 5157adf..fcce2ff 100644
--- a/cmds/statsd/src/stats_util.cpp
+++ b/cmds/statsd/src/stats_util.cpp
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#include <log/log_event_list.h>
 #include "stats_util.h"
+#include <log/log_event_list.h>
 
 namespace android {
 namespace os {
@@ -128,6 +128,35 @@
     return eventMetricData;
 }
 
+// There is no existing hash function for the dimension key ("repeated KeyValuePair").
+// Temporarily use a string concatenation as the hashable key.
+// TODO: Find a better hash function for std::vector<KeyValuePair>.
+HashableDimensionKey getHashableKey(std::vector<KeyValuePair> keys) {
+    std::string flattened;
+    for (const KeyValuePair& pair : keys) {
+        flattened += std::to_string(pair.key());
+        flattened += ":";
+        switch (pair.value_case()) {
+            case KeyValuePair::ValueCase::kValueStr:
+                flattened += pair.value_str();
+                break;
+            case KeyValuePair::ValueCase::kValueInt:
+                flattened += std::to_string(pair.value_int());
+                break;
+            case KeyValuePair::ValueCase::kValueBool:
+                flattened += std::to_string(pair.value_bool());
+                break;
+            case KeyValuePair::ValueCase::kValueFloat:
+                flattened += std::to_string(pair.value_float());
+                break;
+            default:
+                break;
+        }
+        flattened += "|";
+    }
+    return flattened;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 38174bf..575588b 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -13,8 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef PARSE_UTIL_H
-#define PARSE_UTIL_H
+#ifndef STATS_UTIL_H
+#define STATS_UTIL_H
 
 #include "logd/LogReader.h"
 #include "storage/DropboxWriter.h"
@@ -26,12 +26,19 @@
 namespace os {
 namespace statsd {
 
+#define DEFAULT_DIMENSION_KEY ""
+#define MATCHER_NOT_FOUND -2
+#define NANO_SECONDS_IN_A_SECOND (1000 * 1000 * 1000)
+
+typedef std::string HashableDimensionKey;
+
 EventMetricData parse(log_msg msg);
 
 int getTagId(log_msg msg);
 
+std::string getHashableKey(std::vector<KeyValuePair> key);
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
 
-#endif  // PARSE_UTIL_H
+#endif  // STATS_UTIL_H
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index d7702cd..afb3f2b 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -55,7 +55,7 @@
 }
 
 message SimpleLogEntryMatcher {
-  repeated int32 tag = 1;
+  optional int32 tag = 1;
 
   repeated KeyValueMatcher key_value_matcher = 2;
 }
@@ -103,12 +103,28 @@
   optional int64 bucket_size_millis = 1;
 }
 
+message Alert {
+  message IncidentdDetails {
+    optional string alert_name = 1;
+    repeated int32 incidentd_sections = 2;
+  }
+  optional IncidentdDetails incidentd_details = 1;
+
+  optional int32 number_of_buckets = 3;
+
+  optional int32 refractory_period_secs = 4;
+
+  optional int64 trigger_if_gt = 5;
+}
+
 message EventMetric {
   optional int64 metric_id = 1;
 
   optional string what = 2;
 
   optional string condition = 3;
+
+  repeated EventConditionLink links = 4;
 }
 
 message CountMetric {
@@ -121,26 +137,73 @@
   repeated KeyMatcher dimension = 4;
 
   optional Bucket bucket = 5;
+
+  repeated Alert alerts = 6;
+
+  optional bool include_in_output = 7;
+
+  repeated EventConditionLink links = 8;
 }
 
 message DurationMetric {
   optional int64 metric_id = 1;
 
+  optional string start = 2;
+
+  optional string stop = 3;
+
+  optional string stop_all = 4;
+
   enum AggregationType {
     DURATION_SUM = 1;
 
     DURATION_MAX_SPARSE = 2;
     DURATION_MIN_SPARSE = 3;
   }
-  optional AggregationType type = 2;
+  optional AggregationType type = 5;
 
-  optional string predicate = 3;
+  optional string predicate = 6;
 
-  repeated KeyMatcher dimension = 4;
+  repeated KeyMatcher dimension = 7;
 
-  optional Bucket bucket = 5;
+  optional Bucket bucket = 8;
+
+  repeated EventConditionLink links = 9;
 }
 
+message ValueMetric {
+  optional int64 metric_id = 1;
+
+  optional string what = 2;
+
+  optional int32 value_field = 3;
+
+  optional string condition = 4;
+
+  repeated KeyMatcher dimension = 5;
+
+  optional Bucket bucket = 6;
+
+  enum Operation {
+    SUM_DIFF = 1;
+    MIN_DIFF = 2;
+    MAX_DIFF = 3;
+    SUM = 4;
+    MIN = 5;
+    MAX = 6;
+    FIRST = 7;
+    LAST = 8;
+  }
+  optional Operation operation = 7;
+}
+
+message EventConditionLink {
+    optional string condition = 1;
+
+    repeated KeyMatcher key_in_main = 2;
+    repeated KeyMatcher key_in_condition = 3;
+};
+
 message StatsdConfig {
   optional int64 config_id = 1;
 
@@ -148,7 +211,11 @@
 
   repeated CountMetric count_metric = 3;
 
-  repeated LogEntryMatcher log_entry_matcher = 4;
+  repeated ValueMetric value_metric = 4;
 
-  repeated Condition condition = 5;
+  repeated DurationMetric duration_metric = 5;
+
+  repeated LogEntryMatcher log_entry_matcher = 6;
+
+  repeated Condition condition = 7;
 }
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 9d8ba92..fdfe8ef 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -40,14 +40,12 @@
     // Set up the matcher
     LogEntryMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
-    simpleMatcher->add_tag(TAG_ID);
+    simpleMatcher->set_tag(TAG_ID);
 
-    // Set up the event
-    android_log_event_list list(TAG_ID);
+    LogEvent event(TAG_ID);
 
     // Convert to a LogEvent
-    list.convert_to_reader();
-    LogEvent event(999, &list);
+    event.init();
 
     // Test
     EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
@@ -57,20 +55,20 @@
     // Set up the matcher
     LogEntryMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
-    simpleMatcher->add_tag(TAG_ID);
+    simpleMatcher->set_tag(TAG_ID);
     auto keyValue1 = simpleMatcher->add_key_value_matcher();
     keyValue1->mutable_key_matcher()->set_key(FIELD_ID_1);
     auto keyValue2 = simpleMatcher->add_key_value_matcher();
     keyValue2->mutable_key_matcher()->set_key(FIELD_ID_2);
 
     // Set up the event
-    android_log_event_list list(TAG_ID);
-    list << true;
-    list << false;
+    LogEvent event(TAG_ID);
+    auto list = event.GetAndroidLogEventList();
+    *list << true;
+    *list << false;
 
     // Convert to a LogEvent
-    list.convert_to_reader();
-    LogEvent event(999, &list);
+    event.init();
 
     // Test
     keyValue1->set_eq_bool(true);
@@ -94,18 +92,18 @@
     // Set up the matcher
     LogEntryMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
-    simpleMatcher->add_tag(TAG_ID);
+    simpleMatcher->set_tag(TAG_ID);
     auto keyValue = simpleMatcher->add_key_value_matcher();
     keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
     keyValue->set_eq_string("some value");
 
     // Set up the event
-    android_log_event_list list(TAG_ID);
-    list << "some value";
+    LogEvent event(TAG_ID);
+    auto list = event.GetAndroidLogEventList();
+    *list << "some value";
 
     // Convert to a LogEvent
-    list.convert_to_reader();
-    LogEvent event(999, &list);
+    event.init();
 
     // Test
     EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
@@ -115,17 +113,17 @@
     // Set up the matcher
     LogEntryMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
-    simpleMatcher->add_tag(TAG_ID);
+
+    simpleMatcher->set_tag(TAG_ID);
     auto keyValue = simpleMatcher->add_key_value_matcher();
     keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
 
     // Set up the event
-    android_log_event_list list(TAG_ID);
-    list << 11;
+    LogEvent event(TAG_ID);
+    auto list = event.GetAndroidLogEventList();
+    *list << 11;
 
-    // Convert to a LogEvent
-    list.convert_to_reader();
-    LogEvent event(999, &list);
+    event.init();
 
     // Test
 
@@ -176,7 +174,8 @@
     // Set up the matcher
     LogEntryMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
-    simpleMatcher->add_tag(TAG_ID);
+    simpleMatcher->set_tag(TAG_ID);
+
     auto keyValue = simpleMatcher->add_key_value_matcher();
     keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
 
@@ -199,7 +198,7 @@
 
 // Helper for the composite matchers.
 void addSimpleMatcher(SimpleLogEntryMatcher* simpleMatcher, int tag, int key, int val) {
-    simpleMatcher->add_tag(tag);
+    simpleMatcher->set_tag(tag);
     auto keyValue = simpleMatcher->add_key_value_matcher();
     keyValue->mutable_key_matcher()->set_key(key);
     keyValue->set_eq_int(val);
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 32661dc..b000e13 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -45,7 +45,7 @@
     eventMatcher->set_name("SCREEN_IS_ON");
 
     SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
-    simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
+    simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
     simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
     simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
@@ -55,7 +55,7 @@
     eventMatcher->set_name("SCREEN_IS_OFF");
 
     simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
-    simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
+    simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
     simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
     simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
@@ -80,7 +80,7 @@
     eventMatcher->set_name("SCREEN_IS_ON");
 
     SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
-    simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
+    simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
     simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
     simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
@@ -106,7 +106,7 @@
     eventMatcher->set_name("SCREEN_IS_ON");
 
     SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
-    simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
+    simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
     simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
     simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
@@ -132,7 +132,7 @@
     eventMatcher->set_name("SCREEN_IS_ON");
 
     SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
-    simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
+    simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
     simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
     simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
@@ -142,7 +142,7 @@
     eventMatcher->set_name("SCREEN_IS_OFF");
 
     simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
-    simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
+    simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
     simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
     simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 969b19e..37bb6b0 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -20,17 +20,19 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
-import android.annotation.SystemService;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
+import android.app.IServiceConnection;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.ServiceConnection;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
-import android.os.IBinder;
+import android.os.Handler;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -1051,43 +1053,23 @@
      * The appWidgetId specified must already be bound to the calling AppWidgetHost via
      * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
      *
-     * @param packageName   The package from which the binding is requested.
      * @param appWidgetId   The AppWidget instance for which to bind the RemoteViewsService.
      * @param intent        The intent of the service which will be providing the data to the
      *                      RemoteViewsAdapter.
      * @param connection    The callback interface to be notified when a connection is made or lost.
+     * @param flags         Flags used for binding to the service
+     *
+     * @see Context#getServiceDispatcher(ServiceConnection, Handler, int)
      * @hide
      */
-    public void bindRemoteViewsService(String packageName, int appWidgetId, Intent intent,
-            IBinder connection) {
+    public boolean bindRemoteViewsService(Context context, int appWidgetId, Intent intent,
+            IServiceConnection connection, @Context.BindServiceFlags int flags) {
         if (mService == null) {
-            return;
+            return false;
         }
         try {
-            mService.bindRemoteViewsService(packageName, appWidgetId, intent, connection);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Unbinds the RemoteViewsService for a given appWidgetId and intent.
-     *
-     * The appWidgetId specified muse already be bound to the calling AppWidgetHost via
-     * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
-     *
-     * @param packageName   The package from which the binding is requested.
-     * @param appWidgetId   The AppWidget instance for which to bind the RemoteViewsService.
-     * @param intent        The intent of the service which will be providing the data to the
-     *                      RemoteViewsAdapter.
-     * @hide
-     */
-    public void unbindRemoteViewsService(String packageName, int appWidgetId, Intent intent) {
-        if (mService == null) {
-            return;
-        }
-        try {
-            mService.unbindRemoteViewsService(packageName, appWidgetId, intent);
+            return mService.bindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent,
+                    context.getIApplicationThread(), context.getActivityToken(), connection, flags);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 20fbf04..c165fb3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3604,7 +3604,6 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a
-     * {@link android.text.ClipboardManager} for accessing and modifying
      * {@link android.content.ClipboardManager} for accessing and modifying
      * the contents of the global clipboard.
      *
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index 79310e2..16b1452 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -31,7 +31,6 @@
  * RFC 4301.
  */
 public final class IpSecAlgorithm implements Parcelable {
-
     /**
      * AES-CBC Encryption/Ciphering Algorithm.
      *
@@ -68,6 +67,7 @@
      * <p>Valid truncation lengths are multiples of 8 bits from 192 to (default) 384.
      */
     public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
+
     /**
      * SHA512 HMAC Authentication/Integrity Algorithm
      *
@@ -75,8 +75,24 @@
      */
     public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
 
+    /**
+     * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
+     *
+     * <p>Valid lengths for this key are {128, 192, 256}.
+     *
+     * <p>Valid ICV (truncation) lengths are {64, 96, 128}.
+     */
+    public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+
     /** @hide */
-    @StringDef({CRYPT_AES_CBC, AUTH_HMAC_MD5, AUTH_HMAC_SHA1, AUTH_HMAC_SHA256, AUTH_HMAC_SHA512})
+    @StringDef({
+        CRYPT_AES_CBC,
+        AUTH_HMAC_MD5,
+        AUTH_HMAC_SHA1,
+        AUTH_HMAC_SHA256,
+        AUTH_HMAC_SHA512,
+        AUTH_CRYPT_AES_GCM
+    })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AlgorithmName {}
 
@@ -102,7 +118,7 @@
      * @param algoName precise name of the algorithm to be used.
      * @param key non-null Key padded to a multiple of 8 bits.
      * @param truncLenBits the number of bits of output hash to use; only meaningful for
-     *     Authentication.
+     *     Authentication or Authenticated Encryption (equivalent to ICV length).
      */
     public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) {
         if (!isTruncationLengthValid(algoName, truncLenBits)) {
@@ -175,6 +191,8 @@
                 return (truncLenBits >= 192 && truncLenBits <= 384);
             case AUTH_HMAC_SHA512:
                 return (truncLenBits >= 256 && truncLenBits <= 512);
+            case AUTH_CRYPT_AES_GCM:
+                return (truncLenBits == 64 || truncLenBits == 96 || truncLenBits == 128);
             default:
                 return false;
         }
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index 632b7fc..61b13a9 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -50,6 +50,9 @@
         // Authentication Algorithm
         private IpSecAlgorithm mAuthentication;
 
+        // Authenticated Encryption Algorithm
+        private IpSecAlgorithm mAuthenticatedEncryption;
+
         @Override
         public String toString() {
             return new StringBuilder()
@@ -59,6 +62,8 @@
                     .append(mEncryption)
                     .append(", mAuthentication=")
                     .append(mAuthentication)
+                    .append(", mAuthenticatedEncryption=")
+                    .append(mAuthenticatedEncryption)
                     .append("}")
                     .toString();
         }
@@ -118,6 +123,11 @@
         mFlow[direction].mAuthentication = authentication;
     }
 
+    /** Set the authenticated encryption algorithm for a given direction */
+    public void setAuthenticatedEncryption(int direction, IpSecAlgorithm authenticatedEncryption) {
+        mFlow[direction].mAuthenticatedEncryption = authenticatedEncryption;
+    }
+
     public void setNetwork(Network network) {
         mNetwork = network;
     }
@@ -163,6 +173,10 @@
         return mFlow[direction].mAuthentication;
     }
 
+    public IpSecAlgorithm getAuthenticatedEncryption(int direction) {
+        return mFlow[direction].mAuthenticatedEncryption;
+    }
+
     public Network getNetwork() {
         return mNetwork;
     }
@@ -199,9 +213,11 @@
         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(mEncapType);
         out.writeInt(mEncapSocketResourceId);
         out.writeInt(mEncapRemotePort);
@@ -221,11 +237,15 @@
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         mFlow[IpSecTransform.DIRECTION_IN].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 =
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         mEncapType = in.readInt();
         mEncapSocketResourceId = in.readInt();
         mEncapRemotePort = in.readInt();
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index e15a2c6..48b5bd5 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -281,6 +281,8 @@
          * <p>If encryption is set for a given direction without also providing an SPI for that
          * direction, creation of an IpSecTransform will fail upon calling a build() method.
          *
+         * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+         *
          * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
          */
@@ -296,6 +298,8 @@
          * <p>If authentication is set for a given direction without also providing an SPI for that
          * direction, creation of an IpSecTransform will fail upon calling a build() method.
          *
+         * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+         *
          * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
          */
@@ -306,6 +310,29 @@
         }
 
         /**
+         * Add an authenticated encryption algorithm to the transform for the given direction.
+         *
+         * <p>If an authenticated encryption algorithm is set for a given direction without also
+         * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling
+         * a build() method.
+         *
+         * <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 RFC 4301)
+         *
+         * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+         *
+         * @param direction either {@link #DIRECTION_IN or #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, which uniquely identifies a particular IPsec session from others. Because
          * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
          * given destination address.
diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java
index 84119bd..b7e7b17 100644
--- a/core/java/android/os/BatteryProperty.java
+++ b/core/java/android/os/BatteryProperty.java
@@ -43,6 +43,13 @@
         return mValueLong;
     }
 
+    /**
+     * @hide
+     */
+    public void setLong(long val) {
+        mValueLong = val;
+    }
+
     /*
      * Parcel read/write code must be kept in sync with
      * frameworks/native/services/batteryservice/BatteryProperty.cpp
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 5995696..8682c01 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -447,8 +447,7 @@
 
         /**
          * Returns the max duration if it is being tracked.
-         * Not all Timer subclasses track the max, total, current durations.
-
+         * Not all Timer subclasses track the max, total, and current durations.
          */
         public long getMaxDurationMsLocked(long elapsedRealtimeMs) {
             return -1;
@@ -456,14 +455,14 @@
 
         /**
          * Returns the current time the timer has been active, if it is being tracked.
-         * Not all Timer subclasses track the max, total, current durations.
+         * Not all Timer subclasses track the max, total, and current durations.
          */
         public long getCurrentDurationMsLocked(long elapsedRealtimeMs) {
             return -1;
         }
 
         /**
-         * Returns the current time the timer has been active, if it is being tracked.
+         * Returns the total time the timer has been active, if it is being tracked.
          *
          * Returns the total cumulative duration (i.e. sum of past durations) that this timer has
          * been on since reset.
@@ -471,7 +470,7 @@
          * depending on the Timer, getTotalTimeLocked may represent the total 'blamed' or 'pooled'
          * time, rather than the actual time. By contrast, getTotalDurationMsLocked always gives
          * the actual total time.
-         * Not all Timer subclasses track the max, total, current durations.
+         * Not all Timer subclasses track the max, total, and current durations.
          */
         public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
             return -1;
@@ -600,9 +599,17 @@
         public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which);
         public abstract long getWifiScanTime(long elapsedRealtimeUs, int which);
         public abstract int getWifiScanCount(int which);
+        /**
+         * Returns the timer keeping track of wifi scans.
+         */
+        public abstract Timer getWifiScanTimer();
         public abstract int getWifiScanBackgroundCount(int which);
         public abstract long getWifiScanActualTime(long elapsedRealtimeUs);
         public abstract long getWifiScanBackgroundTime(long elapsedRealtimeUs);
+        /**
+         * Returns the timer keeping track of background wifi scans.
+         */
+        public abstract Timer getWifiScanBackgroundTimer();
         public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which);
         public abstract int getWifiBatchedScanCount(int csphBin, int which);
         public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which);
@@ -2824,6 +2831,10 @@
         }
     }
 
+    private static long roundUsToMs(long timeUs) {
+        return (timeUs + 500) / 1000;
+    }
+
     private static long computeWakeLock(Timer timer, long elapsedRealtimeUs, int which) {
         if (timer != null) {
             // Convert from microseconds to milliseconds with rounding
@@ -3028,8 +3039,7 @@
                                         Timer timer, long rawRealtime, int which) {
         if (timer != null) {
             // Convert from microseconds to milliseconds with rounding
-            final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
-                    / 1000;
+            final long totalTime = roundUsToMs(timer.getTotalTimeLocked(rawRealtime, which));
             final int count = timer.getCountLocked(which);
             if (totalTime != 0 || count != 0) {
                 dumpLine(pw, uid, category, type, totalTime, count);
@@ -3052,12 +3062,26 @@
             return;
         }
         // Convert from microseconds to milliseconds with rounding
-        final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtimeUs, which) + 500) / 1000;
+        final long timeMs = roundUsToMs(timer.getTotalTimeLocked(rawRealtimeUs, which));
         final int count = timer.getCountLocked(which);
-        if (totalTimeMs != 0 || count != 0) {
+        final long maxDurationMs = timer.getMaxDurationMsLocked(rawRealtimeUs / 1000);
+        final long curDurationMs = timer.getCurrentDurationMsLocked(rawRealtimeUs / 1000);
+        final long totalDurationMs = timer.getTotalDurationMsLocked(rawRealtimeUs / 1000);
+        if (timeMs != 0 || count != 0 || maxDurationMs != -1 || curDurationMs != -1
+                || totalDurationMs != -1) {
             final long token = proto.start(fieldId);
-            proto.write(TimerProto.DURATION_MS, totalTimeMs);
+            proto.write(TimerProto.DURATION_MS, timeMs);
             proto.write(TimerProto.COUNT, count);
+            // These values will be -1 for timers that don't implement the functionality.
+            if (maxDurationMs != -1) {
+                proto.write(TimerProto.MAX_DURATION_MS, maxDurationMs);
+            }
+            if (curDurationMs != -1) {
+                proto.write(TimerProto.CURRENT_DURATION_MS, curDurationMs);
+            }
+            if (totalDurationMs != -1) {
+                proto.write(TimerProto.TOTAL_DURATION_MS, totalDurationMs);
+            }
             proto.end(token);
         }
     }
@@ -3770,7 +3794,7 @@
                 linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW),
                         rawRealtime, "w", which, linePrefix);
 
-                // Only log if we had at lease one wakelock...
+                // Only log if we had at least one wakelock...
                 if (sb.length() > 0) {
                     String name = wakelocks.keyAt(iw);
                     if (name.indexOf(',') >= 0) {
@@ -6126,9 +6150,8 @@
             return;
         }
         int count = steps.mNumStepDurations;
-        long token;
         for (int i = 0; i < count; ++i) {
-            token = proto.start(fieldId);
+            long token = proto.start(fieldId);
             proto.write(SystemProto.BatteryLevelStep.DURATION_MS, steps.getDurationAt(i));
             proto.write(SystemProto.BatteryLevelStep.LEVEL, steps.getLevelAt(i));
 
@@ -6621,15 +6644,443 @@
         }
 
         if ((flags & (DUMP_HISTORY_ONLY | DUMP_DAILY_ONLY)) == 0) {
-            // TODO: implement dumpProtoAppsLocked(proto, apps);
-            dumpProtoSystemLocked(context, proto, (flags & DUMP_DEVICE_WIFI_ONLY) != 0);
+            final BatteryStatsHelper helper = new BatteryStatsHelper(context, false,
+                    (flags & DUMP_DEVICE_WIFI_ONLY) != 0);
+            helper.create(this);
+            helper.refreshStats(STATS_SINCE_CHARGED, UserHandle.USER_ALL);
+
+            dumpProtoAppsLocked(proto, helper, apps);
+            dumpProtoSystemLocked(proto, helper);
         }
 
         proto.end(bToken);
         proto.flush();
     }
 
-    private void dumpProtoSystemLocked(Context context, ProtoOutputStream proto, boolean wifiOnly) {
+    private void dumpProtoAppsLocked(ProtoOutputStream proto, BatteryStatsHelper helper,
+            List<ApplicationInfo> apps) {
+        final int which = STATS_SINCE_CHARGED;
+        final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
+        final long rawRealtimeMs = SystemClock.elapsedRealtime();
+        final long rawRealtimeUs = rawRealtimeMs * 1000;
+        final long batteryUptimeUs = getBatteryUptime(rawUptimeUs);
+
+        SparseArray<ArrayList<String>> aidToPackages = new SparseArray<>();
+        if (apps != null) {
+            for (int i = 0; i < apps.size(); ++i) {
+                ApplicationInfo ai = apps.get(i);
+                int aid = UserHandle.getAppId(ai.uid);
+                ArrayList<String> pkgs = aidToPackages.get(aid);
+                if (pkgs == null) {
+                    pkgs = new ArrayList<String>();
+                    aidToPackages.put(aid, pkgs);
+                }
+                pkgs.add(ai.packageName);
+            }
+        }
+
+        SparseArray<BatterySipper> uidToSipper = new SparseArray<>();
+        final List<BatterySipper> sippers = helper.getUsageList();
+        if (sippers != null) {
+            for (int i = 0; i < sippers.size(); ++i) {
+                final BatterySipper bs = sippers.get(i);
+                if (bs.drainType != BatterySipper.DrainType.APP) {
+                    // Others are handled by dumpProtoSystemLocked()
+                    continue;
+                }
+                uidToSipper.put(bs.uidObj.getUid(), bs);
+            }
+        }
+
+        SparseArray<? extends Uid> uidStats = getUidStats();
+        final int n = uidStats.size();
+        for (int iu = 0; iu < n; ++iu) {
+            final long uTkn = proto.start(BatteryStatsProto.UIDS);
+            final Uid u = uidStats.valueAt(iu);
+
+            final int uid = uidStats.keyAt(iu);
+            proto.write(UidProto.UID, uid);
+
+            // Print packages and apk stats (UID_DATA & APK_DATA)
+            ArrayList<String> pkgs = aidToPackages.get(UserHandle.getAppId(uid));
+            if (pkgs == null) {
+                pkgs = new ArrayList<String>();
+            }
+            final ArrayMap<String, ? extends BatteryStats.Uid.Pkg> packageStats =
+                    u.getPackageStats();
+            for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
+                String pkg = packageStats.keyAt(ipkg);
+                final ArrayMap<String, ? extends  Uid.Pkg.Serv> serviceStats =
+                        packageStats.valueAt(ipkg).getServiceStats();
+                if (serviceStats.size() == 0) {
+                    // Due to the way ActivityManagerService logs wakeup alarms, some packages (for
+                    // example, "android") may be included in the packageStats that aren't part of
+                    // the UID. If they don't have any services, then they shouldn't be listed here.
+                    // These packages won't be a part in the pkgs List.
+                    continue;
+                }
+
+                final long pToken = proto.start(UidProto.PACKAGES);
+                proto.write(UidProto.Package.NAME, pkg);
+                // Remove from the packages list since we're logging it here.
+                pkgs.remove(pkg);
+
+                for (int isvc = serviceStats.size() - 1; isvc >= 0; --isvc) {
+                    final BatteryStats.Uid.Pkg.Serv ss = serviceStats.valueAt(isvc);
+                    long sToken = proto.start(UidProto.Package.SERVICES);
+
+                    proto.write(UidProto.Package.Service.NAME, serviceStats.keyAt(isvc));
+                    proto.write(UidProto.Package.Service.START_DURATION_MS,
+                            roundUsToMs(ss.getStartTime(batteryUptimeUs, which)));
+                    proto.write(UidProto.Package.Service.START_COUNT, ss.getStarts(which));
+                    proto.write(UidProto.Package.Service.LAUNCH_COUNT, ss.getLaunches(which));
+
+                    proto.end(sToken);
+                }
+                proto.end(pToken);
+            }
+            // Print any remaining packages that weren't in the packageStats map. pkgs is pulled
+            // from PackageManager data. Packages are only included in packageStats if there was
+            // specific data tracked for them (services and wakeup alarms, etc.).
+            for (String p : pkgs) {
+                final long pToken = proto.start(UidProto.PACKAGES);
+                proto.write(UidProto.Package.NAME, p);
+                proto.end(pToken);
+            }
+
+            // Total wakelock data (AGGREGATED_WAKELOCK_DATA)
+            if (u.getAggregatedPartialWakelockTimer() != null) {
+                final Timer timer = u.getAggregatedPartialWakelockTimer();
+                // Times are since reset (regardless of 'which')
+                final long totTimeMs = timer.getTotalDurationMsLocked(rawRealtimeMs);
+                final Timer bgTimer = timer.getSubTimer();
+                final long bgTimeMs = bgTimer != null
+                        ? bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                final long awToken = proto.start(UidProto.AGGREGATED_WAKELOCK);
+                proto.write(UidProto.AggregatedWakelock.PARTIAL_DURATION_MS, totTimeMs);
+                proto.write(UidProto.AggregatedWakelock.BACKGROUND_PARTIAL_DURATION_MS, bgTimeMs);
+                proto.end(awToken);
+            }
+
+            // Audio (AUDIO_DATA)
+            dumpTimer(proto, UidProto.AUDIO, u.getAudioTurnedOnTimer(), rawRealtimeUs, which);
+
+            // Bluetooth Controller (BLUETOOTH_CONTROLLER_DATA)
+            dumpControllerActivityProto(proto, UidProto.BLUETOOTH_CONTROLLER,
+                    u.getBluetoothControllerActivity(), which);
+
+            // BLE scans (BLUETOOTH_MISC_DATA) (uses totalDurationMsLocked and MaxDurationMsLocked)
+            final Timer bleTimer = u.getBluetoothScanTimer();
+            if (bleTimer != null) {
+                final long bmToken = proto.start(UidProto.BLUETOOTH_MISC);
+
+                dumpTimer(proto, UidProto.BluetoothMisc.APPORTIONED_BLE_SCAN, bleTimer,
+                        rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.BluetoothMisc.BACKGROUND_BLE_SCAN,
+                        u.getBluetoothScanBackgroundTimer(), rawRealtimeUs, which);
+                // Unoptimized scan timer. Unpooled and since reset (regardless of 'which').
+                dumpTimer(proto, UidProto.BluetoothMisc.UNOPTIMIZED_BLE_SCAN,
+                        u.getBluetoothUnoptimizedScanTimer(), rawRealtimeUs, which);
+                // Unoptimized bg scan timer. Unpooled and since reset (regardless of 'which').
+                dumpTimer(proto, UidProto.BluetoothMisc.BACKGROUND_UNOPTIMIZED_BLE_SCAN,
+                        u.getBluetoothUnoptimizedScanBackgroundTimer(), rawRealtimeUs, which);
+                // Result counters
+                proto.write(UidProto.BluetoothMisc.BLE_SCAN_RESULT_COUNT,
+                        u.getBluetoothScanResultCounter() != null
+                            ? u.getBluetoothScanResultCounter().getCountLocked(which) : 0);
+                proto.write(UidProto.BluetoothMisc.BACKGROUND_BLE_SCAN_RESULT_COUNT,
+                        u.getBluetoothScanResultBgCounter() != null
+                            ? u.getBluetoothScanResultBgCounter().getCountLocked(which) : 0);
+
+                proto.end(bmToken);
+            }
+
+            // Camera (CAMERA_DATA)
+            dumpTimer(proto, UidProto.CAMERA, u.getCameraTurnedOnTimer(), rawRealtimeUs, which);
+
+            // CPU stats (CPU_DATA & CPU_TIMES_AT_FREQ_DATA)
+            final long cpuToken = proto.start(UidProto.CPU);
+            proto.write(UidProto.Cpu.USER_DURATION_MS, roundUsToMs(u.getUserCpuTimeUs(which)));
+            proto.write(UidProto.Cpu.SYSTEM_DURATION_MS, roundUsToMs(u.getSystemCpuTimeUs(which)));
+
+            final long[] cpuFreqs = getCpuFreqs();
+            if (cpuFreqs != null) {
+                final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which);
+                // If total cpuFreqTimes is null, then we don't need to check for
+                // screenOffCpuFreqTimes.
+                if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) {
+                    long[] screenOffCpuFreqTimeMs = u.getScreenOffCpuFreqTimes(which);
+                    if (screenOffCpuFreqTimeMs == null) {
+                        screenOffCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
+                    }
+                    for (int ic = 0; ic < cpuFreqTimeMs.length; ++ic) {
+                        long cToken = proto.start(UidProto.Cpu.BY_FREQUENCY);
+                        proto.write(UidProto.Cpu.ByFrequency.FREQUENCY_INDEX, ic + 1);
+                        proto.write(UidProto.Cpu.ByFrequency.TOTAL_DURATION_MS,
+                                cpuFreqTimeMs[ic]);
+                        proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS,
+                                screenOffCpuFreqTimeMs[ic]);
+                        proto.end(cToken);
+                    }
+                }
+            }
+            proto.end(cpuToken);
+
+            // Flashlight (FLASHLIGHT_DATA)
+            dumpTimer(proto, UidProto.FLASHLIGHT, u.getFlashlightTurnedOnTimer(),
+                    rawRealtimeUs, which);
+
+            // Foreground activity (FOREGROUND_ACTIVITY_DATA)
+            dumpTimer(proto, UidProto.FOREGROUND_ACTIVITY, u.getForegroundActivityTimer(),
+                    rawRealtimeUs, which);
+
+            // Foreground service (FOREGROUND_SERVICE_DATA)
+            dumpTimer(proto, UidProto.FOREGROUND_SERVICE, u.getForegroundServiceTimer(),
+                    rawRealtimeUs, which);
+
+            // Job completion (JOB_COMPLETION_DATA)
+            final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+            final int[] reasons = new int[]{
+                JobParameters.REASON_CANCELED,
+                JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
+                JobParameters.REASON_PREEMPT,
+                JobParameters.REASON_TIMEOUT,
+                JobParameters.REASON_DEVICE_IDLE,
+            };
+            for (int ic = 0; ic < completions.size(); ++ic) {
+                SparseIntArray types = completions.valueAt(ic);
+                if (types != null) {
+                    final long jcToken = proto.start(UidProto.JOB_COMPLETION);
+
+                    proto.write(UidProto.JobCompletion.NAME, completions.keyAt(ic));
+
+                    for (int r : reasons) {
+                        long rToken = proto.start(UidProto.JobCompletion.REASON_COUNT);
+                        proto.write(UidProto.JobCompletion.ReasonCount.NAME, r);
+                        proto.write(UidProto.JobCompletion.ReasonCount.COUNT, types.get(r, 0));
+                        proto.end(rToken);
+                    }
+
+                    proto.end(jcToken);
+                }
+            }
+
+            // Scheduled jobs (JOB_DATA)
+            final ArrayMap<String, ? extends Timer> jobs = u.getJobStats();
+            for (int ij = jobs.size() - 1; ij >= 0; --ij) {
+                final Timer timer = jobs.valueAt(ij);
+                final Timer bgTimer = timer.getSubTimer();
+                final long jToken = proto.start(UidProto.JOBS);
+
+                proto.write(UidProto.Job.NAME, jobs.keyAt(ij));
+                // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+                dumpTimer(proto, UidProto.Job.TOTAL, timer, rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.Job.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+                proto.end(jToken);
+            }
+
+            // Modem Controller (MODEM_CONTROLLER_DATA)
+            dumpControllerActivityProto(proto, UidProto.MODEM_CONTROLLER,
+                    u.getModemControllerActivity(), which);
+
+            // Network stats (NETWORK_DATA)
+            final long nToken = proto.start(UidProto.NETWORK);
+            proto.write(UidProto.Network.MOBILE_BYTES_RX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_BYTES_TX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_RX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_TX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which));
+            proto.write(UidProto.Network.BT_BYTES_RX,
+                    u.getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
+            proto.write(UidProto.Network.BT_BYTES_TX,
+                    u.getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_RX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_TX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_RX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_TX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_ACTIVE_DURATION_MS,
+                    roundUsToMs(u.getMobileRadioActiveTime(which)));
+            proto.write(UidProto.Network.MOBILE_ACTIVE_COUNT,
+                    u.getMobileRadioActiveCount(which));
+            proto.write(UidProto.Network.MOBILE_WAKEUP_COUNT,
+                    u.getMobileRadioApWakeupCount(which));
+            proto.write(UidProto.Network.WIFI_WAKEUP_COUNT,
+                    u.getWifiRadioApWakeupCount(which));
+            proto.write(UidProto.Network.MOBILE_BYTES_BG_RX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_BG_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_BYTES_BG_TX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_BG_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_BG_RX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_BG_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_BG_TX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_BG_TX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_BG_RX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_BG_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_BG_TX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_BG_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_BG_RX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_BG_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_BG_TX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_BG_TX_DATA, which));
+            proto.end(nToken);
+
+            // Power use item (POWER_USE_ITEM_DATA)
+            BatterySipper bs = uidToSipper.get(uid);
+            if (bs != null) {
+                final long bsToken = proto.start(UidProto.POWER_USE_ITEM);
+                proto.write(UidProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
+                proto.write(UidProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
+                proto.write(UidProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
+                proto.write(UidProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
+                        bs.proportionalSmearMah);
+                proto.end(bsToken);
+            }
+
+            // Processes (PROCESS_DATA)
+            final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats =
+                    u.getProcessStats();
+            for (int ipr = processStats.size() - 1; ipr >= 0; --ipr) {
+                final Uid.Proc ps = processStats.valueAt(ipr);
+                final long prToken = proto.start(UidProto.PROCESS);
+
+                proto.write(UidProto.Process.NAME, processStats.keyAt(ipr));
+                proto.write(UidProto.Process.USER_DURATION_MS, ps.getUserTime(which));
+                proto.write(UidProto.Process.SYSTEM_DURATION_MS, ps.getSystemTime(which));
+                proto.write(UidProto.Process.FOREGROUND_DURATION_MS, ps.getForegroundTime(which));
+                proto.write(UidProto.Process.START_COUNT, ps.getStarts(which));
+                proto.write(UidProto.Process.ANR_COUNT, ps.getNumAnrs(which));
+                proto.write(UidProto.Process.CRASH_COUNT, ps.getNumCrashes(which));
+
+                proto.end(prToken);
+            }
+
+            // Sensors (SENSOR_DATA)
+            final SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
+            for (int ise = 0; ise < sensors.size(); ++ise) {
+                final Uid.Sensor se = sensors.valueAt(ise);
+                final Timer timer = se.getSensorTime();
+                if (timer == null) {
+                    continue;
+                }
+                final Timer bgTimer = se.getSensorBackgroundTime();
+                final int sensorNumber = sensors.keyAt(ise);
+                final long seToken = proto.start(UidProto.SENSORS);
+
+                proto.write(UidProto.Sensor.ID, sensorNumber);
+                // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+                dumpTimer(proto, UidProto.Sensor.APPORTIONED, timer, rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.Sensor.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+                proto.end(seToken);
+            }
+
+            // State times (STATE_TIME_DATA)
+            for (int ips = 0; ips < Uid.NUM_PROCESS_STATE; ++ips) {
+                long durMs = roundUsToMs(u.getProcessStateTime(ips, rawRealtimeUs, which));
+                if (durMs == 0) {
+                    continue;
+                }
+                final long stToken = proto.start(UidProto.STATES);
+                proto.write(UidProto.StateTime.STATE, ips);
+                proto.write(UidProto.StateTime.DURATION_MS, durMs);
+                proto.end(stToken);
+            }
+
+            // Syncs (SYNC_DATA)
+            final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats();
+            for (int isy = syncs.size() - 1; isy >= 0; --isy) {
+                final Timer timer = syncs.valueAt(isy);
+                final Timer bgTimer = timer.getSubTimer();
+                final long syToken = proto.start(UidProto.SYNCS);
+
+                proto.write(UidProto.Sync.NAME, syncs.keyAt(isy));
+                // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+                dumpTimer(proto, UidProto.Sync.TOTAL, timer, rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.Sync.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+                proto.end(syToken);
+            }
+
+            // User activity (USER_ACTIVITY_DATA)
+            if (u.hasUserActivity()) {
+                for (int i = 0; i < Uid.NUM_USER_ACTIVITY_TYPES; ++i) {
+                    int val = u.getUserActivityCount(i, which);
+                    if (val != 0) {
+                        final long uaToken = proto.start(UidProto.USER_ACTIVITY);
+                        proto.write(UidProto.UserActivity.NAME, i);
+                        proto.write(UidProto.UserActivity.COUNT, val);
+                        proto.end(uaToken);
+                    }
+                }
+            }
+
+            // Vibrator (VIBRATOR_DATA)
+            dumpTimer(proto, UidProto.VIBRATOR, u.getVibratorOnTimer(), rawRealtimeUs, which);
+
+            // Video (VIDEO_DATA)
+            dumpTimer(proto, UidProto.VIDEO, u.getVideoTurnedOnTimer(), rawRealtimeUs, which);
+
+            // Wakelocks (WAKELOCK_DATA)
+            final ArrayMap<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats();
+            for (int iw = wakelocks.size() - 1; iw >= 0; --iw) {
+                final Uid.Wakelock wl = wakelocks.valueAt(iw);
+                final long wToken = proto.start(UidProto.WAKELOCKS);
+                proto.write(UidProto.Wakelock.NAME, wakelocks.keyAt(iw));
+                dumpTimer(proto, UidProto.Wakelock.FULL, wl.getWakeTime(WAKE_TYPE_FULL),
+                        rawRealtimeUs, which);
+                final Timer pTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+                if (pTimer != null) {
+                    dumpTimer(proto, UidProto.Wakelock.PARTIAL, pTimer, rawRealtimeUs, which);
+                    dumpTimer(proto, UidProto.Wakelock.BACKGROUND_PARTIAL, pTimer.getSubTimer(),
+                            rawRealtimeUs, which);
+                }
+                dumpTimer(proto, UidProto.Wakelock.WINDOW, wl.getWakeTime(WAKE_TYPE_WINDOW),
+                        rawRealtimeUs, which);
+                proto.end(wToken);
+            }
+
+            // Wakeup alarms (WAKEUP_ALARM_DATA)
+            for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
+                final Uid.Pkg ps = packageStats.valueAt(ipkg);
+                final ArrayMap<String, ? extends Counter> alarms = ps.getWakeupAlarmStats();
+                for (int iwa = alarms.size() - 1; iwa >= 0; --iwa) {
+                    final long waToken = proto.start(UidProto.WAKEUP_ALARM);
+                    proto.write(UidProto.WakeupAlarm.NAME, alarms.keyAt(iwa));
+                    proto.write(UidProto.WakeupAlarm.COUNT,
+                            alarms.valueAt(iwa).getCountLocked(which));
+                    proto.end(waToken);
+                }
+            }
+
+            // Wifi Controller (WIFI_CONTROLLER_DATA)
+            dumpControllerActivityProto(proto, UidProto.WIFI_CONTROLLER,
+                    u.getWifiControllerActivity(), which);
+
+            // Wifi data (WIFI_DATA)
+            final long wToken = proto.start(UidProto.WIFI);
+            proto.write(UidProto.Wifi.FULL_WIFI_LOCK_DURATION_MS,
+                    roundUsToMs(u.getFullWifiLockTime(rawRealtimeUs, which)));
+            dumpTimer(proto, UidProto.Wifi.APPORTIONED_SCAN, u.getWifiScanTimer(),
+                    rawRealtimeUs, which);
+            proto.write(UidProto.Wifi.RUNNING_DURATION_MS,
+                    roundUsToMs(u.getWifiRunningTime(rawRealtimeUs, which)));
+            dumpTimer(proto, UidProto.Wifi.BACKGROUND_SCAN, u.getWifiScanBackgroundTimer(),
+                    rawRealtimeUs, which);
+            proto.end(wToken);
+
+            proto.end(uTkn);
+        }
+    }
+
+    private void dumpProtoSystemLocked(ProtoOutputStream proto, BatteryStatsHelper helper) {
         final long sToken = proto.start(BatteryStatsProto.SYSTEM);
         final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
         final long rawRealtimeMs = SystemClock.elapsedRealtime();
@@ -6637,7 +7088,7 @@
         final int which = STATS_SINCE_CHARGED;
 
         // Battery data (BATTERY_DATA)
-        long token = proto.start(SystemProto.BATTERY);
+        final long bToken = proto.start(SystemProto.BATTERY);
         proto.write(SystemProto.Battery.START_CLOCK_TIME_MS, getStartClockTime());
         proto.write(SystemProto.Battery.START_COUNT, getStartCount());
         proto.write(SystemProto.Battery.TOTAL_REALTIME_MS,
@@ -6660,10 +7111,10 @@
                 getMinLearnedBatteryCapacity());
         proto.write(SystemProto.Battery.MAX_LEARNED_BATTERY_CAPACITY_UAH,
                 getMaxLearnedBatteryCapacity());
-        proto.end(token);
+        proto.end(bToken);
 
         // Battery discharge (BATTERY_DISCHARGE_DATA)
-        token = proto.start(SystemProto.BATTERY_DISCHARGE);
+        final long bdToken = proto.start(SystemProto.BATTERY_DISCHARGE);
         proto.write(SystemProto.BatteryDischarge.LOWER_BOUND_SINCE_CHARGE,
                 getLowDischargeAmountSinceCharge());
         proto.write(SystemProto.BatteryDischarge.UPPER_BOUND_SINCE_CHARGE,
@@ -6680,10 +7131,11 @@
                 getUahDischargeScreenOff(which) / 1000);
         proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE,
                 getUahDischargeScreenDoze(which) / 1000);
-        proto.end(token);
+        proto.end(bdToken);
 
         // Time remaining
         long timeRemainingUs = computeChargeTimeRemaining(rawRealtimeUs);
+        // These are part of a oneof, so we should only set one of them.
         if (timeRemainingUs >= 0) {
             // Charge time remaining (CHARGE_TIME_REMAIN_DATA)
             proto.write(SystemProto.CHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
@@ -6702,11 +7154,11 @@
 
         // Phone data connection (DATA_CONNECTION_TIME_DATA and DATA_CONNECTION_COUNT_DATA)
         for (int i = 0; i < NUM_DATA_CONNECTION_TYPES; ++i) {
-            token = proto.start(SystemProto.DATA_CONNECTION);
+            final long pdcToken = proto.start(SystemProto.DATA_CONNECTION);
             proto.write(SystemProto.DataConnection.NAME, i);
             dumpTimer(proto, SystemProto.DataConnection.TOTAL, getPhoneDataConnectionTimer(i),
                     rawRealtimeUs, which);
-            proto.end(token);
+            proto.end(pdcToken);
         }
 
         // Discharge step (DISCHARGE_STEP_DATA)
@@ -6729,7 +7181,7 @@
                 getModemControllerActivity(), which);
 
         // Global network data (GLOBAL_NETWORK_DATA)
-        token = proto.start(SystemProto.GLOBAL_NETWORK);
+        final long gnToken = proto.start(SystemProto.GLOBAL_NETWORK);
         proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_RX,
                 getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
         proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_TX,
@@ -6750,7 +7202,7 @@
                 getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
         proto.write(SystemProto.GlobalNetwork.BT_BYTES_TX,
                 getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
-        proto.end(token);
+        proto.end(gnToken);
 
         // Wifi controller (GLOBAL_WIFI_CONTROLLER_DATA)
         dumpControllerActivityProto(proto, SystemProto.GLOBAL_WIFI_CONTROLLER,
@@ -6758,21 +7210,21 @@
 
 
         // Global wifi (GLOBAL_WIFI_DATA)
-        token = proto.start(SystemProto.GLOBAL_WIFI);
+        final long gwToken = proto.start(SystemProto.GLOBAL_WIFI);
         proto.write(SystemProto.GlobalWifi.ON_DURATION_MS,
                 getWifiOnTime(rawRealtimeUs, which) / 1000);
         proto.write(SystemProto.GlobalWifi.RUNNING_DURATION_MS,
                 getGlobalWifiRunningTime(rawRealtimeUs, which) / 1000);
-        proto.end(token);
+        proto.end(gwToken);
 
         // Kernel wakelock (KERNEL_WAKELOCK_DATA)
         final Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats();
         for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) {
-            token = proto.start(SystemProto.KERNEL_WAKELOCK);
+            final long kwToken = proto.start(SystemProto.KERNEL_WAKELOCK);
             proto.write(SystemProto.KernelWakelock.NAME, ent.getKey());
             dumpTimer(proto, SystemProto.KernelWakelock.TOTAL, ent.getValue(),
                     rawRealtimeUs, which);
-            proto.end(token);
+            proto.end(kwToken);
         }
 
         // Misc (MISC_DATA)
@@ -6802,7 +7254,7 @@
                 }
             }
         }
-        token = proto.start(SystemProto.MISC);
+        final long mToken = proto.start(SystemProto.MISC);
         proto.write(SystemProto.Misc.SCREEN_ON_DURATION_MS,
                 getScreenOnTime(rawRealtimeUs, which) / 1000);
         proto.write(SystemProto.Misc.PHONE_ON_DURATION_MS,
@@ -6845,11 +7297,7 @@
                 getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which));
         proto.write(SystemProto.Misc.LONGEST_LIGHT_DOZE_DURATION_MS,
                 getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT));
-        proto.end(token);
-
-        final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly);
-        helper.create(this);
-        helper.refreshStats(which, UserHandle.USER_ALL);
+        proto.end(mToken);
 
         // Power use item (POWER_USE_ITEM_DATA)
         final List<BatterySipper> sippers = helper.getUsageList();
@@ -6881,7 +7329,7 @@
                         n = SystemProto.PowerUseItem.FLASHLIGHT;
                         break;
                     case APP:
-                        // dumpProtoAppLocked will handle this.
+                        // dumpProtoAppsLocked will handle this.
                         continue;
                     case USER:
                         n = SystemProto.PowerUseItem.USER;
@@ -6900,7 +7348,7 @@
                         n = SystemProto.PowerUseItem.MEMORY;
                         break;
                 }
-                token = proto.start(SystemProto.POWER_USE_ITEM);
+                final long puiToken = proto.start(SystemProto.POWER_USE_ITEM);
                 proto.write(SystemProto.PowerUseItem.NAME, n);
                 proto.write(SystemProto.PowerUseItem.UID, uid);
                 proto.write(SystemProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
@@ -6908,39 +7356,39 @@
                 proto.write(SystemProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
                 proto.write(SystemProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
                         bs.proportionalSmearMah);
-                proto.end(token);
+                proto.end(puiToken);
             }
         }
 
         // Power use summary (POWER_USE_SUMMARY_DATA)
-        token = proto.start(SystemProto.POWER_USE_SUMMARY);
+        final long pusToken = proto.start(SystemProto.POWER_USE_SUMMARY);
         proto.write(SystemProto.PowerUseSummary.BATTERY_CAPACITY_MAH,
                 helper.getPowerProfile().getBatteryCapacity());
         proto.write(SystemProto.PowerUseSummary.COMPUTED_POWER_MAH, helper.getComputedPower());
         proto.write(SystemProto.PowerUseSummary.MIN_DRAINED_POWER_MAH, helper.getMinDrainedPower());
         proto.write(SystemProto.PowerUseSummary.MAX_DRAINED_POWER_MAH, helper.getMaxDrainedPower());
-        proto.end(token);
+        proto.end(pusToken);
 
         // RPM stats (RESOURCE_POWER_MANAGER_DATA)
         final Map<String, ? extends Timer> rpmStats = getRpmStats();
         final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
         for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
-            token = proto.start(SystemProto.RESOURCE_POWER_MANAGER);
+            final long rpmToken = proto.start(SystemProto.RESOURCE_POWER_MANAGER);
             proto.write(SystemProto.ResourcePowerManager.NAME, ent.getKey());
             dumpTimer(proto, SystemProto.ResourcePowerManager.TOTAL,
                     ent.getValue(), rawRealtimeUs, which);
             dumpTimer(proto, SystemProto.ResourcePowerManager.SCREEN_OFF,
                     screenOffRpmStats.get(ent.getKey()), rawRealtimeUs, which);
-            proto.end(token);
+            proto.end(rpmToken);
         }
 
         // Screen brightness (SCREEN_BRIGHTNESS_DATA)
         for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; ++i) {
-            token = proto.start(SystemProto.SCREEN_BRIGHTNESS);
+            final long sbToken = proto.start(SystemProto.SCREEN_BRIGHTNESS);
             proto.write(SystemProto.ScreenBrightness.NAME, i);
             dumpTimer(proto, SystemProto.ScreenBrightness.TOTAL, getScreenBrightnessTimer(i),
                     rawRealtimeUs, which);
-            proto.end(token);
+            proto.end(sbToken);
         }
 
         // Signal scanning time (SIGNAL_SCANNING_TIME_DATA)
@@ -6949,47 +7397,47 @@
 
         // Phone signal strength (SIGNAL_STRENGTH_TIME_DATA and SIGNAL_STRENGTH_COUNT_DATA)
         for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; ++i) {
-            token = proto.start(SystemProto.PHONE_SIGNAL_STRENGTH);
+            final long pssToken = proto.start(SystemProto.PHONE_SIGNAL_STRENGTH);
             proto.write(SystemProto.PhoneSignalStrength.NAME, i);
             dumpTimer(proto, SystemProto.PhoneSignalStrength.TOTAL, getPhoneSignalStrengthTimer(i),
                     rawRealtimeUs, which);
-            proto.end(token);
+            proto.end(pssToken);
         }
 
         // Wakeup reasons (WAKEUP_REASON_DATA)
         final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats();
         for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) {
-            token = proto.start(SystemProto.WAKEUP_REASON);
+            final long wrToken = proto.start(SystemProto.WAKEUP_REASON);
             proto.write(SystemProto.WakeupReason.NAME, ent.getKey());
             dumpTimer(proto, SystemProto.WakeupReason.TOTAL, ent.getValue(), rawRealtimeUs, which);
-            proto.end(token);
+            proto.end(wrToken);
         }
 
         // Wifi signal strength (WIFI_SIGNAL_STRENGTH_TIME_DATA and WIFI_SIGNAL_STRENGTH_COUNT_DATA)
         for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; ++i) {
-            token = proto.start(SystemProto.WIFI_SIGNAL_STRENGTH);
+            final long wssToken = proto.start(SystemProto.WIFI_SIGNAL_STRENGTH);
             proto.write(SystemProto.WifiSignalStrength.NAME, i);
             dumpTimer(proto, SystemProto.WifiSignalStrength.TOTAL, getWifiSignalStrengthTimer(i),
                     rawRealtimeUs, which);
-            proto.end(token);
+            proto.end(wssToken);
         }
 
         // Wifi state (WIFI_STATE_TIME_DATA and WIFI_STATE_COUNT_DATA)
         for (int i = 0; i < NUM_WIFI_STATES; ++i) {
-            token = proto.start(SystemProto.WIFI_STATE);
+            final long wsToken = proto.start(SystemProto.WIFI_STATE);
             proto.write(SystemProto.WifiState.NAME, i);
             dumpTimer(proto, SystemProto.WifiState.TOTAL, getWifiStateTimer(i),
                     rawRealtimeUs, which);
-            proto.end(token);
+            proto.end(wsToken);
         }
 
         // Wifi supplicant state (WIFI_SUPPL_STATE_TIME_DATA and WIFI_SUPPL_STATE_COUNT_DATA)
         for (int i = 0; i < NUM_WIFI_SUPPL_STATES; ++i) {
-            token = proto.start(SystemProto.WIFI_SUPPLICANT_STATE);
+            final long wssToken = proto.start(SystemProto.WIFI_SUPPLICANT_STATE);
             proto.write(SystemProto.WifiSupplicantState.NAME, i);
             dumpTimer(proto, SystemProto.WifiSupplicantState.TOTAL, getWifiSupplStateTimer(i),
                     rawRealtimeUs, which);
-            proto.end(token);
+            proto.end(wssToken);
         }
 
         proto.end(sToken);
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index d8f9567..20f6c8e 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.os.StatsLogEventWrapper;
+
 /**
   * Binder interface to communicate with the Java-based statistics service helper.
   * {@hide}
@@ -50,5 +52,5 @@
     oneway void cancelPollingAlarms();
 
     /** Pull the specified data. Results will be sent to statsd when complete. */
-    String pullData(int pullCode);
+    StatsLogEventWrapper[] pullData(int pullCode);
 }
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/os/StatsLogEventWrapper.aidl
similarity index 67%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to core/java/android/os/StatsLogEventWrapper.aidl
index 7294124..766343e 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/os/StatsLogEventWrapper.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 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.
@@ -14,12 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.widget;
+package android.os;
 
-import android.os.IBinder;
-
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
-    void onServiceConnected(IBinder service);
-    void onServiceDisconnected();
-}
+/** @hide */
+parcelable StatsLogEventWrapper cpp_header "android/os/StatsLogEventWrapper.h";
\ No newline at end of file
diff --git a/core/java/android/os/StatsLogEventWrapper.java b/core/java/android/os/StatsLogEventWrapper.java
new file mode 100644
index 0000000..9491bec
--- /dev/null
+++ b/core/java/android/os/StatsLogEventWrapper.java
@@ -0,0 +1,141 @@
+/*
+ * 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.os;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Wrapper class for sending data from Android OS to StatsD.
+ *
+ * @hide
+ */
+public final class StatsLogEventWrapper implements Parcelable {
+    private ByteArrayOutputStream mStorage = new ByteArrayOutputStream();
+
+    // Below are constants copied from log/log.h
+    private static final int EVENT_TYPE_INT = 0;  /* int32_t */
+    private static final int EVENT_TYPE_LONG = 1; /* int64_t */
+    private static final int EVENT_TYPE_STRING = 2;
+    private static final int EVENT_TYPE_LIST = 3;
+    private static final int EVENT_TYPE_FLOAT = 4;
+
+    /**
+     * Creates a log_event that is binary-encoded as implemented in
+     * system/core/liblog/log_event_list.c; this allows us to use the same parsing logic in statsd
+     * for pushed and pulled data. The write* methods must be called in the same order as their
+     * field number. There is no checking that the correct number of write* methods is called.
+     * We also write an END_LIST character before beginning to write to parcel, but this END_LIST
+     * may be unnecessary.
+     *
+     * @param tag    The integer representing the tag for this event.
+     * @param fields The number of fields specified in this event.
+     */
+    public StatsLogEventWrapper(int tag, int fields) {
+        // Write four bytes from tag, starting with least-significant bit.
+        write4Bytes(tag);
+        mStorage.write(EVENT_TYPE_LIST); // This is required to start the log entry.
+        mStorage.write(fields); // Indicate number of elements in this list.
+    }
+
+    /**
+     * Boilerplate for Parcel.
+     */
+    public static final Parcelable.Creator<StatsLogEventWrapper> CREATOR = new
+            Parcelable.Creator<StatsLogEventWrapper>() {
+                public StatsLogEventWrapper createFromParcel(Parcel in) {
+                    return new StatsLogEventWrapper(in);
+                }
+
+                public StatsLogEventWrapper[] newArray(int size) {
+                    return new StatsLogEventWrapper[size];
+                }
+            };
+
+    private void write4Bytes(int val) {
+        mStorage.write(val);
+        mStorage.write(val >>> 8);
+        mStorage.write(val >>> 16);
+        mStorage.write(val >>> 24);
+    }
+
+    private void write8Bytes(long val) {
+        write4Bytes((int) (val & 0xFFFFFFFF)); // keep the lowe 32-bits
+        write4Bytes((int) (val >>> 32)); // Write the high 32-bits.
+    }
+
+    /**
+     * Adds 32-bit integer to output.
+     */
+    public void writeInt(int val) {
+        mStorage.write(EVENT_TYPE_INT);
+        write4Bytes(val);
+    }
+
+    /**
+     * Adds 64-bit long to output.
+     */
+    public void writeLong(long val) {
+        mStorage.write(EVENT_TYPE_LONG);
+        write8Bytes(val);
+    }
+
+    /**
+     * Adds a 4-byte floating point value to output.
+     */
+    public void writeFloat(float val) {
+        int v = Float.floatToIntBits(val);
+        mStorage.write(EVENT_TYPE_FLOAT);
+        write4Bytes(v);
+    }
+
+    /**
+     * Adds a string to the output.
+     */
+    public void writeString(String val) {
+        mStorage.write(EVENT_TYPE_STRING);
+        write4Bytes(val.length());
+        byte[] bytes = val.getBytes(StandardCharsets.UTF_8);
+        mStorage.write(bytes, 0, bytes.length);
+    }
+
+    private StatsLogEventWrapper(Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * Writes the stored fields to a byte array. Will first write a new-line character to denote
+     * END_LIST before writing contents to byte array.
+     */
+    public void writeToParcel(Parcel out, int flags) {
+        mStorage.write(10); // new-line character is same as END_LIST
+        out.writeByteArray(mStorage.toByteArray());
+    }
+
+    /**
+     * Not implemented.
+     */
+    public void readFromParcel(Parcel in) {
+        // Not needed since this java class is for sending to statsd only.
+    }
+
+    /**
+     * Boilerplate for Parcel.
+     */
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b435074..c200ae7 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -442,6 +442,18 @@
             "android.settings.ASSIST_GESTURE_SETTINGS";
 
     /**
+     * Activity Action: Show settings to enroll fingerprints, and setup PIN/Pattern/Pass if
+     * necessary.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_FINGERPRINT_ENROLL =
+            "android.settings.FINGERPRINT_ENROLL";
+
+    /**
      * Activity Action: Show settings to allow configuration of cast endpoints.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -9254,6 +9266,13 @@
          */
         public static final String DEFAULT_DNS_SERVER = "default_dns_server";
 
+        /**
+         * Whether to disable DNS over TLS (boolean)
+         *
+         * @hide
+         */
+        public static final String DNS_TLS_DISABLED = "dns_tls_disabled";
+
         /** {@hide} */
         public static final String
                 BLUETOOTH_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_";
diff --git a/core/java/android/security/net/config/ManifestConfigSource.java b/core/java/android/security/net/config/ManifestConfigSource.java
index 8fcd5ab..79115a5 100644
--- a/core/java/android/security/net/config/ManifestConfigSource.java
+++ b/core/java/android/security/net/config/ManifestConfigSource.java
@@ -20,6 +20,7 @@
 import android.content.pm.ApplicationInfo;
 import android.util.Log;
 import android.util.Pair;
+
 import java.util.Set;
 
 /** @hide */
@@ -29,21 +30,14 @@
 
     private final Object mLock = new Object();
     private final Context mContext;
-    private final int mApplicationInfoFlags;
-    private final int mTargetSdkVersion;
-    private final int mConfigResourceId;
-    private final int mTargetSandboxVesrsion;
+    private final ApplicationInfo mApplicationInfo;
 
     private ConfigSource mConfigSource;
 
     public ManifestConfigSource(Context context) {
         mContext = context;
-        // Cache values because ApplicationInfo is mutable and apps do modify it :(
-        ApplicationInfo info = context.getApplicationInfo();
-        mApplicationInfoFlags = info.flags;
-        mTargetSdkVersion = info.targetSdkVersion;
-        mConfigResourceId = info.networkSecurityConfigRes;
-        mTargetSandboxVesrsion = info.targetSandboxVersion;
+        // Cache the info because ApplicationInfo is mutable and apps do modify it :(
+        mApplicationInfo = new ApplicationInfo(context.getApplicationInfo());
     }
 
     @Override
@@ -61,17 +55,18 @@
             if (mConfigSource != null) {
                 return mConfigSource;
             }
-
+            int configResource = mApplicationInfo.networkSecurityConfigRes;
             ConfigSource source;
-            if (mConfigResourceId != 0) {
-                boolean debugBuild = (mApplicationInfoFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+            if (configResource != 0) {
+                boolean debugBuild =
+                        (mApplicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
                 if (DBG) {
                     Log.d(LOG_TAG, "Using Network Security Config from resource "
-                            + mContext.getResources().getResourceEntryName(mConfigResourceId)
+                            + mContext.getResources()
+                                .getResourceEntryName(configResource)
                             + " debugBuild: " + debugBuild);
                 }
-                source = new XmlConfigSource(mContext, mConfigResourceId, debugBuild,
-                        mTargetSdkVersion, mTargetSandboxVesrsion);
+                source = new XmlConfigSource(mContext, configResource, mApplicationInfo);
             } else {
                 if (DBG) {
                     Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
@@ -79,10 +74,9 @@
                 // the legacy FLAG_USES_CLEARTEXT_TRAFFIC is not supported for Ephemeral apps, they
                 // should use the network security config.
                 boolean usesCleartextTraffic =
-                        (mApplicationInfoFlags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0
-                        && mTargetSandboxVesrsion < 2;
-                source = new DefaultConfigSource(usesCleartextTraffic, mTargetSdkVersion,
-                        mTargetSandboxVesrsion);
+                        (mApplicationInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0
+                        && mApplicationInfo.targetSandboxVersion < 2;
+                source = new DefaultConfigSource(usesCleartextTraffic, mApplicationInfo);
             }
             mConfigSource = source;
             return mConfigSource;
@@ -93,10 +87,8 @@
 
         private final NetworkSecurityConfig mDefaultConfig;
 
-        public DefaultConfigSource(boolean usesCleartextTraffic, int targetSdkVersion,
-                int targetSandboxVesrsion) {
-            mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(targetSdkVersion,
-                    targetSandboxVesrsion)
+        DefaultConfigSource(boolean usesCleartextTraffic, ApplicationInfo info) {
+            mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(info)
                     .setCleartextTrafficPermitted(usesCleartextTraffic)
                     .build();
         }
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 789fc27..b9e5505 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -16,9 +16,11 @@
 
 package android.security.net.config;
 
+import android.content.pm.ApplicationInfo;
 import android.os.Build;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -28,8 +30,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import javax.net.ssl.X509TrustManager;
-
 /**
  * @hide
  */
@@ -170,22 +170,24 @@
      * <li>No certificate pinning is used.</li>
      * <li>The system certificate store is trusted for connections.</li>
      * <li>If the application targets API level 23 (Android M) or lower then the user certificate
-     * store is trusted by default as well.</li>
+     * store is trusted by default as well for non-privileged applications.</li>
+     * <li>Privileged applications do not trust the user certificate store on Android P and higher.
+     * </li>
      * </ol>
      *
      * @hide
      */
-    public static final Builder getDefaultBuilder(int targetSdkVersion, int targetSandboxVesrsion) {
+    public static Builder getDefaultBuilder(ApplicationInfo info) {
         Builder builder = new Builder()
                 .setHstsEnforced(DEFAULT_HSTS_ENFORCED)
                 // System certificate store, does not bypass static pins.
                 .addCertificatesEntryRef(
                         new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
-        final boolean cleartextTrafficPermitted = targetSandboxVesrsion < 2;
+        final boolean cleartextTrafficPermitted = info.targetSandboxVersion < 2;
         builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
         // Applications targeting N and above must opt in into trusting the user added certificate
         // store.
-        if (targetSdkVersion <= Build.VERSION_CODES.M) {
+        if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) {
             // User certificate store, does not bypass static pins.
             builder.addCertificatesEntryRef(
                     new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java
index a111fbce..02be403 100644
--- a/core/java/android/security/net/config/XmlConfigSource.java
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -1,13 +1,13 @@
 package android.security.net.config;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
-import android.os.Build;
 import android.util.ArraySet;
 import android.util.Base64;
 import android.util.Pair;
-import com.android.internal.annotations.VisibleForTesting;
+
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -36,37 +36,19 @@
     private final Object mLock = new Object();
     private final int mResourceId;
     private final boolean mDebugBuild;
-    private final int mTargetSdkVersion;
-    private final int mTargetSandboxVesrsion;
+    private final ApplicationInfo mApplicationInfo;
 
     private boolean mInitialized;
     private NetworkSecurityConfig mDefaultConfig;
     private Set<Pair<Domain, NetworkSecurityConfig>> mDomainMap;
     private Context mContext;
 
-    @VisibleForTesting
-    public XmlConfigSource(Context context, int resourceId) {
-        this(context, resourceId, false);
-    }
-
-    @VisibleForTesting
-    public XmlConfigSource(Context context, int resourceId, boolean debugBuild) {
-        this(context, resourceId, debugBuild, Build.VERSION_CODES.CUR_DEVELOPMENT);
-    }
-
-    @VisibleForTesting
-    public XmlConfigSource(Context context, int resourceId, boolean debugBuild,
-            int targetSdkVersion) {
-        this(context, resourceId, debugBuild, targetSdkVersion, 1 /*targetSandboxVersion*/);
-    }
-
-    public XmlConfigSource(Context context, int resourceId, boolean debugBuild,
-            int targetSdkVersion, int targetSandboxVesrsion) {
-        mResourceId = resourceId;
+    public XmlConfigSource(Context context, int resourceId, ApplicationInfo info) {
         mContext = context;
-        mDebugBuild = debugBuild;
-        mTargetSdkVersion = targetSdkVersion;
-        mTargetSandboxVesrsion = targetSandboxVesrsion;
+        mResourceId = resourceId;
+        mApplicationInfo = new ApplicationInfo(info);
+
+        mDebugBuild = (mApplicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
     }
 
     public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
@@ -365,7 +347,7 @@
         // Use the platform default as the parent of the base config for any values not provided
         // there. If there is no base config use the platform default.
         NetworkSecurityConfig.Builder platformDefaultBuilder =
-                NetworkSecurityConfig.getDefaultBuilder(mTargetSdkVersion, mTargetSandboxVesrsion);
+                NetworkSecurityConfig.getDefaultBuilder(mApplicationInfo);
         addDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder);
         if (baseConfigBuilder != null) {
             baseConfigBuilder.setParent(platformDefaultBuilder);
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index ef9598a..331130e 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -150,8 +150,16 @@
     public String toString() {
         if (!sDebug) return super.toString();
 
-        return new StringBuilder("Dataset " + mId + " [")
-                .append("fieldIds=").append(mFieldIds)
+        final StringBuilder builder = new StringBuilder("Dataset[id=");
+        if (mId == null) {
+            builder.append("null");
+        } else {
+            // Cannot disclose id because it could contain PII.
+            builder.append(mId.length()).append("_chars");
+        }
+
+        return builder
+                .append(", fieldIds=").append(mFieldIds)
                 .append(", fieldValues=").append(mFieldValues)
                 .append(", fieldPresentations=")
                 .append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
@@ -201,7 +209,8 @@
          * Creates a new builder for a dataset where each field will be visualized independently.
          *
          * <p>When using this constructor, fields must be set through
-         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}.
+         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
+         * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}.
          */
         public Builder() {
         }
@@ -280,19 +289,24 @@
         /**
          * Sets the value of a field.
          *
+         * <b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, this method would
+         * throw an {@link IllegalStateException} if this builder was constructed without a
+         * {@link RemoteViews presentation}. Android {@link android.os.Build.VERSION_CODES#P} and
+         * higher removed this restriction because datasets used as an
+         * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT
+         * authentication result} do not need a presentation. But if you don't set the presentation
+         * in the constructor in a dataset that is meant to be shown to the user, the autofill UI
+         * for this field will not be displayed.
+         *
          * @param id id returned by {@link
          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
          * @param value value to be autofilled. Pass {@code null} if you do not have the value
          *        but the target view is a logical part of the dataset. For example, if
          *        the dataset needs authentication and you have no access to the value.
          * @return this builder.
-         * @throws IllegalStateException if the builder was constructed without a
-         * {@link RemoteViews presentation}.
          */
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
             throwIfDestroyed();
-            Preconditions.checkState(mPresentation != null,
-                    "Dataset presentation not set on constructor");
             setLifeTheUniverseAndEverything(id, value, null, null);
             return this;
         }
@@ -334,7 +348,7 @@
          *
          * @return this builder.
          * @throws IllegalStateException if the builder was constructed without a
-         * {@link RemoteViews presentation}.
+         *         {@link RemoteViews presentation}.
          */
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
                 @NonNull Pattern filter) {
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 3b719ac..b1857b3 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -17,19 +17,27 @@
 package android.service.autofill;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Describes what happened after the last
@@ -121,6 +129,11 @@
     }
 
     @Override
+    public String toString() {
+        return mEvents == null ? "no events" : mEvents.toString();
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
@@ -136,9 +149,21 @@
             int numEvents = mEvents.size();
             for (int i = 0; i < numEvents; i++) {
                 Event event = mEvents.get(i);
-                dest.writeInt(event.getType());
-                dest.writeString(event.getDatasetId());
-                dest.writeBundle(event.getClientState());
+                dest.writeInt(event.mEventType);
+                dest.writeString(event.mDatasetId);
+                dest.writeBundle(event.mClientState);
+                dest.writeStringList(event.mSelectedDatasetIds);
+                dest.writeArraySet(event.mIgnoredDatasetIds);
+                dest.writeTypedList(event.mChangedFieldIds);
+                dest.writeStringList(event.mChangedDatasetIds);
+
+                dest.writeTypedList(event.mManuallyFilledFieldIds);
+                if (event.mManuallyFilledFieldIds != null) {
+                    final int size = event.mManuallyFilledFieldIds.size();
+                    for (int j = 0; j < size; j++) {
+                        dest.writeStringList(event.mManuallyFilledDatasetIds.get(j));
+                    }
+                }
             }
         }
     }
@@ -176,12 +201,40 @@
         /** A save UI was shown. */
         public static final int TYPE_SAVE_SHOWN = 3;
 
+        /**
+         * A committed autofill context for which the autofill service provided datasets.
+         *
+         * <p>This event is useful to track:
+         * <ul>
+         *   <li>Which datasets (if any) were selected by the user
+         *       ({@link #getSelectedDatasetIds()}).
+         *   <li>Which datasets (if any) were NOT selected by the user
+         *       ({@link #getIgnoredDatasetIds()}).
+         *   <li>Which fields in the selected datasets were changed by the user after the dataset
+         *       was selected ({@link #getChangedFields()}.
+         * </ul>
+         *
+         * <p><b>Note: </b>This event is only generated when:
+         * <ul>
+         *   <li>The autofill context is committed.
+         *   <li>The service provides at least one dataset in the
+         *       {@link FillResponse fill responses} associated with the context.
+         *   <li>The last {@link FillResponse fill responses} associated with the context has the
+         *       {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} flag.
+         * </ul>
+         *
+         * <p>See {@link android.view.autofill.AutofillManager} for more information about autofill
+         * contexts.
+         */
+        public static final int TYPE_CONTEXT_COMMITTED = 4;
+
         /** @hide */
         @IntDef(
                 value = {TYPE_DATASET_SELECTED,
                         TYPE_DATASET_AUTHENTICATION_SELECTED,
                         TYPE_AUTHENTICATION_SELECTED,
-                        TYPE_SAVE_SHOWN})
+                        TYPE_SAVE_SHOWN,
+                        TYPE_CONTEXT_COMMITTED})
         @Retention(RetentionPolicy.SOURCE)
         @interface EventIds{}
 
@@ -189,6 +242,17 @@
         @Nullable private final String mDatasetId;
         @Nullable private final Bundle mClientState;
 
+        // Note: mSelectedDatasetIds is stored as List<> instead of Set because Session already
+        // stores it as List
+        @Nullable private final List<String> mSelectedDatasetIds;
+        @Nullable private final ArraySet<String> mIgnoredDatasetIds;
+
+        @Nullable private final ArrayList<AutofillId> mChangedFieldIds;
+        @Nullable private final ArrayList<String> mChangedDatasetIds;
+
+        @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds;
+        @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
+
         /**
          * Returns the type of the event.
          *
@@ -220,25 +284,202 @@
         }
 
         /**
+         * Returns which datasets were selected by the user.
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         */
+        @NonNull public Set<String> getSelectedDatasetIds() {
+            return mSelectedDatasetIds == null ? Collections.emptySet()
+                    : new ArraySet<>(mSelectedDatasetIds);
+        }
+
+        /**
+         * Returns which datasets were NOT selected by the user.
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         */
+        @NonNull public Set<String> getIgnoredDatasetIds() {
+            return mIgnoredDatasetIds == null ? Collections.emptySet() : mIgnoredDatasetIds;
+        }
+
+        /**
+         * Returns which fields in the selected datasets were changed by the user after the dataset
+         * was selected.
+         *
+         * <p>For example, server provides:
+         *
+         * <pre class="prettyprint">
+         *  FillResponse response = new FillResponse.Builder()
+         *      .addDataset(new Dataset.Builder(presentation1)
+         *          .setId("4815")
+         *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
+         *          .build())
+         *      .addDataset(new Dataset.Builder(presentation2)
+         *          .setId("162342")
+         *          .setValue(passwordId, AutofillValue.forText("D'OH"))
+         *          .build())
+         *      .build();
+         * </pre>
+         *
+         * <p>User select both datasets (for username and password) but after the fields are
+         * autofilled, user changes them to:
+         *
+         * <pre class="prettyprint">
+         *   username = "ElBarto";
+         *   password = "AyCaramba";
+         * </pre>
+         *
+         * <p>Then the result is the following map:
+         *
+         * <pre class="prettyprint">
+         *   usernameId => "4815"
+         *   passwordId => "162342"
+         * </pre>
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         *
+         * @return map map whose key is the id of the change fields, and value is the id of
+         * dataset that has that field and was selected by the user.
+         */
+        @NonNull public Map<AutofillId, String> getChangedFields() {
+            if (mChangedFieldIds == null || mChangedDatasetIds == null) {
+                return Collections.emptyMap();
+            }
+
+            final int size = mChangedFieldIds.size();
+            final ArrayMap<AutofillId, String> changedFields = new ArrayMap<>(size);
+            for (int i = 0; i < size; i++) {
+                changedFields.put(mChangedFieldIds.get(i), mChangedDatasetIds.get(i));
+            }
+            return changedFields;
+        }
+
+        /**
+         * Returns which fields were available on datasets provided by the service but manually
+         * entered by the user.
+         *
+         * <p>For example, server provides:
+         *
+         * <pre class="prettyprint">
+         *  FillResponse response = new FillResponse.Builder()
+         *      .addDataset(new Dataset.Builder(presentation1)
+         *          .setId("4815")
+         *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
+         *          .setValue(passwordId, AutofillValue.forText("AyCaramba"))
+         *          .build())
+         *      .addDataset(new Dataset.Builder(presentation2)
+         *          .setId("162342")
+         *          .setValue(usernameId, AutofillValue.forText("ElBarto"))
+         *          .setValue(passwordId, AutofillValue.forText("D'OH"))
+         *          .build())
+         *      .addDataset(new Dataset.Builder(presentation3)
+         *          .setId("108")
+         *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
+         *          .setValue(passwordId, AutofillValue.forText("D'OH"))
+         *          .build())
+         *      .build();
+         * </pre>
+         *
+         * <p>User doesn't select a dataset but manually enters:
+         *
+         * <pre class="prettyprint">
+         *   username = "MrPlow";
+         *   password = "D'OH";
+         * </pre>
+         *
+         * <p>Then the result is the following map:
+         *
+         * <pre class="prettyprint">
+         *   usernameId => { "4815", "108"}
+         *   passwordId => { "162342", "108" }
+         * </pre>
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         *
+         * @return map map whose key is the id of the manually-entered field, and value is the
+         * ids of the datasets that have that value but were not selected by the user.
+         */
+        @Nullable public Map<AutofillId, Set<String>> getManuallyEnteredField() {
+            if (mManuallyFilledFieldIds == null || mManuallyFilledDatasetIds == null) {
+                return Collections.emptyMap();
+            }
+
+            final int size = mManuallyFilledFieldIds.size();
+            final Map<AutofillId, Set<String>> manuallyFilledFields = new ArrayMap<>(size);
+            for (int i = 0; i < size; i++) {
+                final AutofillId fieldId = mManuallyFilledFieldIds.get(i);
+                final ArrayList<String> datasetIds = mManuallyFilledDatasetIds.get(i);
+                manuallyFilledFields.put(fieldId, new ArraySet<>(datasetIds));
+            }
+            return manuallyFilledFields;
+        }
+
+        /**
          * Creates a new event.
          *
          * @param eventType The type of the event
          * @param datasetId The dataset the event was on, or {@code null} if the event was on the
          *                  whole response.
          * @param clientState The client state associated with the event.
+         * @param selectedDatasetIds The ids of datasets selected by the user.
+         * @param ignoredDatasetIds The ids of datasets NOT select by the user.
+         * @param changedFieldIds The ids of fields changed by the user.
+         * @param changedDatasetIds The ids of the datasets that havd values matching the
+         * respective entry on {@code changedFieldIds}.
+         * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user
+         * and belonged to datasets.
+         * @param manuallyFilledDatasetIds The ids of datasets that had values matching the
+         * respective entry on {@code manuallyFilledFieldIds}.
+         *
+         * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
+         * {@code changedDatasetIds} doesn't match.
+         * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and
+         * {@code manuallyFilledDatasetIds} doesn't match.
          *
          * @hide
          */
-        public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState) {
-            mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_SAVE_SHOWN,
+        public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState,
+                @Nullable List<String> selectedDatasetIds,
+                @Nullable ArraySet<String> ignoredDatasetIds,
+                @Nullable ArrayList<AutofillId> changedFieldIds,
+                @Nullable ArrayList<String> changedDatasetIds,
+                @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
+                @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) {
+            mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED,
                     "eventType");
             mDatasetId = datasetId;
             mClientState = clientState;
+            mSelectedDatasetIds = selectedDatasetIds;
+            mIgnoredDatasetIds = ignoredDatasetIds;
+            if (changedFieldIds != null) {
+                Preconditions.checkArgument(!ArrayUtils.isEmpty(changedFieldIds)
+                        && changedDatasetIds != null
+                        && changedFieldIds.size() == changedDatasetIds.size(),
+                        "changed ids must have same length and not be empty");
+            }
+            mChangedFieldIds = changedFieldIds;
+            mChangedDatasetIds = changedDatasetIds;
+            if (manuallyFilledFieldIds != null) {
+                Preconditions.checkArgument(!ArrayUtils.isEmpty(manuallyFilledFieldIds)
+                        && manuallyFilledDatasetIds != null
+                        && manuallyFilledFieldIds.size() == manuallyFilledDatasetIds.size(),
+                        "manually filled ids must have same length and not be empty");
+            }
+            mManuallyFilledFieldIds = manuallyFilledFieldIds;
+            mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
         }
 
         @Override
         public String toString() {
-            return "FillEvent [datasetId=" + mDatasetId + ", type=" + mEventType + "]";
+            return "FillEvent [datasetId=" + mDatasetId
+                    + ", type=" + mEventType
+                    + ", selectedDatasets=" + mSelectedDatasetIds
+                    + ", ignoredDatasetIds=" + mIgnoredDatasetIds
+                    + ", changedFieldIds=" + mChangedFieldIds
+                    + ", changedDatasetsIds=" + mChangedDatasetIds
+                    + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
+                    + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
+                    + "]";
         }
     }
 
@@ -248,12 +489,37 @@
                 public FillEventHistory createFromParcel(Parcel parcel) {
                     FillEventHistory selection = new FillEventHistory(0, 0, parcel.readBundle());
 
-                    int numEvents = parcel.readInt();
+                    final int numEvents = parcel.readInt();
                     for (int i = 0; i < numEvents; i++) {
-                        selection.addEvent(new Event(parcel.readInt(), parcel.readString(),
-                                parcel.readBundle()));
-                    }
+                        final int eventType = parcel.readInt();
+                        final String datasetId = parcel.readString();
+                        final Bundle clientState = parcel.readBundle();
+                        final ArrayList<String> selectedDatasetIds = parcel.createStringArrayList();
+                        @SuppressWarnings("unchecked")
+                        final ArraySet<String> ignoredDatasets =
+                                (ArraySet<String>) parcel.readArraySet(null);
+                        final ArrayList<AutofillId> changedFieldIds =
+                                parcel.createTypedArrayList(AutofillId.CREATOR);
+                        final ArrayList<String> changedDatasetIds = parcel.createStringArrayList();
 
+                        final ArrayList<AutofillId> manuallyFilledFieldIds =
+                                parcel.createTypedArrayList(AutofillId.CREATOR);
+                        final ArrayList<ArrayList<String>> manuallyFilledDatasetIds;
+                        if (manuallyFilledFieldIds != null) {
+                            final int size = manuallyFilledFieldIds.size();
+                            manuallyFilledDatasetIds = new ArrayList<>(size);
+                            for (int j = 0; j < size; j++) {
+                                manuallyFilledDatasetIds.add(parcel.createStringArrayList());
+                            }
+                        } else {
+                            manuallyFilledDatasetIds = null;
+                        }
+
+                        selection.addEvent(new Event(eventType, datasetId, clientState,
+                                selectedDatasetIds, ignoredDatasets,
+                                changedFieldIds, changedDatasetIds,
+                                manuallyFilledFieldIds, manuallyFilledDatasetIds));
+                    }
                     return selection;
                 }
 
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 6d8a959..d2033fa 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -19,6 +19,7 @@
 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
 import static android.view.autofill.Helper.sDebug;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -30,6 +31,8 @@
 import android.view.autofill.AutofillId;
 import android.widget.RemoteViews;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -42,6 +45,19 @@
  */
 public final class FillResponse implements Parcelable {
 
+    /**
+     * Must be set in the last response to generate
+     * {@link FillEventHistory.Event#TYPE_CONTEXT_COMMITTED} events.
+     */
+    public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1;
+
+    /** @hide */
+    @IntDef(flag = true, value = {
+            FLAG_TRACK_CONTEXT_COMMITED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface FillResponseFlags {}
+
     private final @Nullable ParceledListSlice<Dataset> mDatasets;
     private final @Nullable SaveInfo mSaveInfo;
     private final @Nullable Bundle mClientState;
@@ -49,16 +65,18 @@
     private final @Nullable IntentSender mAuthentication;
     private final @Nullable AutofillId[] mAuthenticationIds;
     private final @Nullable AutofillId[] mIgnoredIds;
+    private final int mFlags;
     private int mRequestId;
 
     private FillResponse(@NonNull Builder builder) {
         mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
         mSaveInfo = builder.mSaveInfo;
-        mClientState = builder.mCLientState;
+        mClientState = builder.mClientState;
         mPresentation = builder.mPresentation;
         mAuthentication = builder.mAuthentication;
         mAuthenticationIds = builder.mAuthenticationIds;
         mIgnoredIds = builder.mIgnoredIds;
+        mFlags = builder.mFlags;
         mRequestId = INVALID_REQUEST_ID;
     }
 
@@ -97,6 +115,11 @@
         return mIgnoredIds;
     }
 
+    /** @hide */
+    public int getFlags() {
+        return mFlags;
+    }
+
     /**
      * Associates a {@link FillResponse} to a request.
      *
@@ -122,11 +145,12 @@
     public static final class Builder {
         private ArrayList<Dataset> mDatasets;
         private SaveInfo mSaveInfo;
-        private Bundle mCLientState;
+        private Bundle mClientState;
         private RemoteViews mPresentation;
         private IntentSender mAuthentication;
         private AutofillId[] mAuthenticationIds;
         private AutofillId[] mIgnoredIds;
+        private int mFlags;
         private boolean mDestroyed;
 
         /**
@@ -264,7 +288,20 @@
          */
         public Builder setClientState(@Nullable Bundle clientState) {
             throwIfDestroyed();
-            mCLientState = clientState;
+            mClientState = clientState;
+            return this;
+        }
+
+        /**
+         * Sets flags changing the response behavior.
+         *
+         * @param flags {@link #FLAG_TRACK_CONTEXT_COMMITED}, or {@code 0}.
+         *
+         * @return This builder.
+         */
+        public Builder setFlags(@FillResponseFlags int flags) {
+            throwIfDestroyed();
+            mFlags = flags;
             return this;
         }
 
@@ -311,6 +348,7 @@
                 .append(", hasAuthentication=").append(mAuthentication != null)
                 .append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds))
                 .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
+                .append(", flags=").append(mFlags)
                 .append("]")
                 .toString();
     }
@@ -333,6 +371,7 @@
         parcel.writeParcelable(mAuthentication, flags);
         parcel.writeParcelable(mPresentation, flags);
         parcel.writeParcelableArray(mIgnoredIds, flags);
+        parcel.writeInt(mFlags);
         parcel.writeInt(mRequestId);
     }
 
@@ -363,8 +402,9 @@
             }
 
             builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
-            final FillResponse response = builder.build();
+            builder.setFlags(parcel.readInt());
 
+            final FillResponse response = builder.build();
             response.setRequestId(parcel.readInt());
 
             return response;
diff --git a/core/java/android/service/autofill/LuhnChecksumValidator.java b/core/java/android/service/autofill/LuhnChecksumValidator.java
index 0b5930d..c56ae84 100644
--- a/core/java/android/service/autofill/LuhnChecksumValidator.java
+++ b/core/java/android/service/autofill/LuhnChecksumValidator.java
@@ -27,6 +27,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Arrays;
+
 /**
  * Validator that returns {@code true} if the number created by concatenating all given fields
  * pass a Luhn algorithm checksum. All non-digits are ignored.
@@ -86,17 +88,27 @@
     public boolean isValid(@NonNull ValueFinder finder) {
         if (mIds == null || mIds.length == 0) return false;
 
-        final StringBuilder number = new StringBuilder();
+        final StringBuilder builder = new StringBuilder();
         for (AutofillId id : mIds) {
             final String partialNumber = finder.findByAutofillId(id);
             if (partialNumber == null) {
                 if (sDebug) Log.d(TAG, "No partial number for id " + id);
                 return false;
             }
-            number.append(partialNumber);
+            builder.append(partialNumber);
         }
 
-        return isLuhnChecksumValid(number.toString());
+        final String number = builder.toString();
+        boolean valid = isLuhnChecksumValid(number);
+        if (sDebug) Log.d(TAG, "isValid(" + number.length() + " chars): " + valid);
+        return valid;
+    }
+
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "LuhnChecksumValidator: [ids=" + Arrays.toString(mIds) + "]";
     }
 
     /////////////////////////////////////
diff --git a/core/java/android/service/autofill/OptionalValidators.java b/core/java/android/service/autofill/OptionalValidators.java
index f7edd6e..7aec59f 100644
--- a/core/java/android/service/autofill/OptionalValidators.java
+++ b/core/java/android/service/autofill/OptionalValidators.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
@@ -34,6 +35,8 @@
  */
 final class OptionalValidators extends InternalValidator {
 
+    private static final String TAG = "OptionalValidators";
+
     @NonNull private final InternalValidator[] mValidators;
 
     OptionalValidators(@NonNull InternalValidator[] validators) {
@@ -44,6 +47,7 @@
     public boolean isValid(@NonNull ValueFinder finder) {
         for (InternalValidator validator : mValidators) {
             final boolean valid = validator.isValid(finder);
+            if (sDebug) Log.d(TAG, "isValid(" + validator + "): " + valid);
             if (valid) return true;
         }
 
diff --git a/core/java/android/service/autofill/RequiredValidators.java b/core/java/android/service/autofill/RequiredValidators.java
index ac85c28..9e1db2b 100644
--- a/core/java/android/service/autofill/RequiredValidators.java
+++ b/core/java/android/service/autofill/RequiredValidators.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
@@ -34,6 +35,8 @@
  */
 final class RequiredValidators extends InternalValidator {
 
+    private static final String TAG = "RequiredValidators";
+
     @NonNull private final InternalValidator[] mValidators;
 
     RequiredValidators(@NonNull InternalValidator[] validators) {
@@ -44,6 +47,7 @@
     public boolean isValid(@NonNull ValueFinder finder) {
         for (InternalValidator validator : mValidators) {
             final boolean valid = validator.isValid(finder);
+            if (sDebug) Log.d(TAG, "isValid(" + validator + "): " + valid);
             if (!valid) return false;
         }
         return true;
diff --git a/core/java/android/service/settings/suggestions/ISuggestionService.aidl b/core/java/android/service/settings/suggestions/ISuggestionService.aidl
index 423c507..8dfa9c3 100644
--- a/core/java/android/service/settings/suggestions/ISuggestionService.aidl
+++ b/core/java/android/service/settings/suggestions/ISuggestionService.aidl
@@ -17,4 +17,10 @@
      * calls.
      */
     void dismissSuggestion(in Suggestion suggestion) = 2;
+
+    /**
+     * This is the opposite signal to {@link #dismissSuggestion}, indicating a suggestion has been
+     * launched.
+     */
+    void launchSuggestion(in Suggestion suggestion) = 3;
 }
\ No newline at end of file
diff --git a/core/java/android/service/settings/suggestions/SuggestionService.java b/core/java/android/service/settings/suggestions/SuggestionService.java
index 2a4c84c..ce9501d 100644
--- a/core/java/android/service/settings/suggestions/SuggestionService.java
+++ b/core/java/android/service/settings/suggestions/SuggestionService.java
@@ -48,12 +48,20 @@
             }
 
             @Override
-            public  void dismissSuggestion(Suggestion suggestion) {
+            public void dismissSuggestion(Suggestion suggestion) {
                 if (DEBUG) {
                     Log.d(TAG, "dismissSuggestion() " + getPackageName());
                 }
                 onSuggestionDismissed(suggestion);
             }
+
+            @Override
+            public void launchSuggestion(Suggestion suggestion) {
+                if (DEBUG) {
+                    Log.d(TAG, "launchSuggestion() " + getPackageName());
+                }
+                onSuggestionLaunched(suggestion);
+            }
         };
     }
 
@@ -65,7 +73,12 @@
     /**
      * Dismiss a suggestion. The suggestion will not be included in future
      * {@link #onGetSuggestions()} calls.
-     * @param suggestion
      */
     public abstract void onSuggestionDismissed(Suggestion suggestion);
+
+    /**
+     * This is the opposite signal to {@link #onSuggestionDismissed}, indicating a suggestion has
+     * been launched.
+     */
+    public abstract void onSuggestionLaunched(Suggestion suggestion);
 }
diff --git a/core/java/android/text/AndroidBidi.java b/core/java/android/text/AndroidBidi.java
index bbe1523..179d545 100644
--- a/core/java/android/text/AndroidBidi.java
+++ b/core/java/android/text/AndroidBidi.java
@@ -16,6 +16,11 @@
 
 package android.text;
 
+import android.icu.lang.UCharacter;
+import android.icu.lang.UCharacterDirection;
+import android.icu.lang.UProperty;
+import android.icu.text.Bidi;
+import android.icu.text.BidiClassifier;
 import android.text.Layout.Directions;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -27,26 +32,57 @@
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class AndroidBidi {
 
-    public static int bidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo) {
+    private static class EmojiBidiOverride extends BidiClassifier {
+        EmojiBidiOverride() {
+            super(null /* No persisting object needed */);
+        }
+
+        // Tells ICU to use the standard Unicode value.
+        private static final int NO_OVERRIDE =
+                UCharacter.getIntPropertyMaxValue(UProperty.BIDI_CLASS) + 1;
+
+        @Override
+        public int classify(int c) {
+            if (Emoji.isNewEmoji(c)) {
+                // All new emoji characters in Unicode 10.0 are of the bidi class ON.
+                return UCharacterDirection.OTHER_NEUTRAL;
+            } else {
+                return NO_OVERRIDE;
+            }
+        }
+    }
+
+    private static final EmojiBidiOverride sEmojiBidiOverride = new EmojiBidiOverride();
+
+    /**
+     * Runs the bidi algorithm on input text.
+     */
+    public static int bidi(int dir, char[] chs, byte[] chInfo) {
         if (chs == null || chInfo == null) {
             throw new NullPointerException();
         }
 
-        if (n < 0 || chs.length < n || chInfo.length < n) {
+        final int length = chs.length;
+        if (chInfo.length < length) {
             throw new IndexOutOfBoundsException();
         }
 
-        switch(dir) {
-            case Layout.DIR_REQUEST_LTR: dir = 0; break;
-            case Layout.DIR_REQUEST_RTL: dir = 1; break;
-            case Layout.DIR_REQUEST_DEFAULT_LTR: dir = -2; break;
-            case Layout.DIR_REQUEST_DEFAULT_RTL: dir = -1; break;
-            default: dir = 0; break;
+        final byte paraLevel;
+        switch (dir) {
+            case Layout.DIR_REQUEST_LTR: paraLevel = Bidi.LTR; break;
+            case Layout.DIR_REQUEST_RTL: paraLevel = Bidi.RTL; break;
+            case Layout.DIR_REQUEST_DEFAULT_LTR: paraLevel = Bidi.LEVEL_DEFAULT_LTR; break;
+            case Layout.DIR_REQUEST_DEFAULT_RTL: paraLevel = Bidi.LEVEL_DEFAULT_RTL; break;
+            default: paraLevel = Bidi.LTR; break;
         }
-
-        int result = runBidi(dir, chs, chInfo, n, haveInfo);
-        result = (result & 0x1) == 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
-        return result;
+        final Bidi icuBidi = new Bidi(length /* maxLength */, 0 /* maxRunCount */);
+        icuBidi.setCustomClassifier(sEmojiBidiOverride);
+        icuBidi.setPara(chs, paraLevel, null /* embeddingLevels */);
+        for (int i = 0; i < length; i++) {
+            chInfo[i] = icuBidi.getLevelAt(i);
+        }
+        final byte result = icuBidi.getParaLevel();
+        return (result & 0x1) == 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
     }
 
     /**
@@ -178,6 +214,4 @@
         }
         return new Directions(ld);
     }
-
-    private native static int runBidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo);
 }
\ No newline at end of file
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index b09ccc2..ffc44a7 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -106,8 +106,8 @@
         if (mWidths == null || mWidths.length < len) {
             mWidths = ArrayUtils.newUnpaddedFloatArray(len);
         }
-        if (mChars == null || mChars.length < len) {
-            mChars = ArrayUtils.newUnpaddedCharArray(len);
+        if (mChars == null || mChars.length != len) {
+            mChars = new char[len];
         }
         TextUtils.getChars(text, start, end, mChars, 0);
 
@@ -151,7 +151,7 @@
                 boolean isRtl = textDir.isRtl(mChars, 0, len);
                 bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
             }
-            mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
+            mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels);
             mEasy = false;
         }
     }
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/util/MutableBoolean.java
similarity index 74%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to core/java/android/util/MutableBoolean.java
index 7294124..ed837ab 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/util/MutableBoolean.java
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.internal.widget;
+package android.util;
 
-import android.os.IBinder;
+/**
+ */
+public final class MutableBoolean {
+    public boolean value;
 
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
-    void onServiceConnected(IBinder service);
-    void onServiceDisconnected();
+    public MutableBoolean(boolean value) {
+        this.value = value;
+    }
 }
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/util/MutableByte.java
similarity index 74%
rename from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
rename to core/java/android/util/MutableByte.java
index 7294124..cc6b00a 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/util/MutableByte.java
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.internal.widget;
+package android.util;
 
-import android.os.IBinder;
+/**
+ */
+public final class MutableByte {
+    public byte value;
 
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
-    void onServiceConnected(IBinder service);
-    void onServiceDisconnected();
+    public MutableByte(byte value) {
+        this.value = value;
+    }
 }
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/util/MutableChar.java
similarity index 74%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to core/java/android/util/MutableChar.java
index 7294124..9a2e2bc 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/util/MutableChar.java
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.internal.widget;
+package android.util;
 
-import android.os.IBinder;
+/**
+ */
+public final class MutableChar {
+    public char value;
 
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
-    void onServiceConnected(IBinder service);
-    void onServiceDisconnected();
+    public MutableChar(char value) {
+        this.value = value;
+    }
 }
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/util/MutableDouble.java
similarity index 74%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to core/java/android/util/MutableDouble.java
index 7294124..bd7329a 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/util/MutableDouble.java
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.internal.widget;
+package android.util;
 
-import android.os.IBinder;
+/**
+ */
+public final class MutableDouble {
+    public double value;
 
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
-    void onServiceConnected(IBinder service);
-    void onServiceDisconnected();
+    public MutableDouble(double value) {
+        this.value = value;
+    }
 }
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/util/MutableFloat.java
similarity index 74%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to core/java/android/util/MutableFloat.java
index 7294124..e6f2d7d 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/util/MutableFloat.java
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.internal.widget;
+package android.util;
 
-import android.os.IBinder;
+/**
+ */
+public final class MutableFloat {
+    public float value;
 
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
-    void onServiceConnected(IBinder service);
-    void onServiceDisconnected();
+    public MutableFloat(float value) {
+        this.value = value;
+    }
 }
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/util/MutableShort.java
similarity index 74%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to core/java/android/util/MutableShort.java
index 7294124..48fb232 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/util/MutableShort.java
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.internal.widget;
+package android.util;
 
-import android.os.IBinder;
+/**
+ */
+public final class MutableShort {
+    public short value;
 
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
-    void onServiceConnected(IBinder service);
-    void onServiceDisconnected();
+    public MutableShort(short value) {
+        this.value = value;
+    }
 }
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index 88b9c0d..ad160cb 100644
--- a/core/java/android/view/MenuItem.java
+++ b/core/java/android/view/MenuItem.java
@@ -72,11 +72,6 @@
     public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
 
     /**
-     * @hide
-     */
-    int SHOW_AS_OVERFLOW_ALWAYS = 1 << 31;
-
-    /**
      * Interface definition for a callback to be invoked when a menu item is
      * clicked.
      *
@@ -806,12 +801,22 @@
     }
 
     /**
-     * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_OVERFLOW_ALWAYS}.
-     * Default value if {@code false}.
+     * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_ACTION_ALWAYS}.
+     * Default value is {@code false}.
+     *
+     * @hide
+     */
+    default boolean requiresActionButton() {
+        return false;
+    }
+
+    /**
+     * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_ACTION_NEVER}.
+     * Default value is {@code true}.
      *
      * @hide
      */
     default boolean requiresOverflow() {
-        return false;
+        return true;
     }
 }
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 574137b..4500862 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -84,12 +84,17 @@
 
     /**
      * Defines the duration in milliseconds a user needs to hold down the
-     * appropriate button to bring up the accessibility shortcut (first time) or enable it
-     * (once shortcut is configured).
+     * appropriate button to bring up the accessibility shortcut for the first time
      */
     private static final int A11Y_SHORTCUT_KEY_TIMEOUT = 3000;
 
     /**
+     * Defines the duration in milliseconds a user needs to hold down the
+     * appropriate button to enable the accessibility shortcut once it's configured.
+     */
+    private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1500;
+
+    /**
      * Defines the duration in milliseconds we will wait to see if a touch event
      * is a tap or a scroll. If the user does not move within this interval, it is
      * considered to be a tap.
@@ -851,6 +856,15 @@
     }
 
     /**
+     * @return The amount of time a user needs to press the relevant keys to activate the
+     *   accessibility shortcut after it's confirmed that accessibility shortcut is used.
+     * @hide
+     */
+    public long getAccessibilityShortcutKeyTimeoutAfterConfirmation() {
+        return A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION;
+    }
+
+    /**
      * The amount of friction applied to scrolls and flings.
      *
      * @return A scalar dimensionless value representing the coefficient of
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 309366c..d665dde 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -370,7 +370,7 @@
      * <p>Should only be set when the node is used for autofill purposes - it will be ignored
      * when used for Assist.
      */
-    public abstract void setMinTextEms(int minEms);
+    public void setMinTextEms(@SuppressWarnings("unused") int minEms) {}
 
     /**
      * Sets the maximum width in ems of the text associated with this view, when supported.
@@ -378,7 +378,7 @@
      * <p>Should only be set when the node is used for autofill purposes - it will be ignored
      * when used for Assist.
      */
-    public abstract void setMaxTextEms(int maxEms);
+    public void setMaxTextEms(@SuppressWarnings("unused") int maxEms) {}
 
     /**
      * Sets the maximum length of the text associated with this view, when supported.
@@ -386,7 +386,7 @@
      * <p>Should only be set when the node is used for autofill purposes - it will be ignored
      * when used for Assist.
      */
-    public abstract void setMaxTextLength(int maxLength);
+    public void setMaxTextLength(@SuppressWarnings("unused") int maxLength) {}
 
     /**
      * Call when done populating a {@link ViewStructure} returned by
diff --git a/core/java/android/view/textclassifier/Log.java b/core/java/android/view/textclassifier/Log.java
new file mode 100644
index 0000000..83ca15d
--- /dev/null
+++ b/core/java/android/view/textclassifier/Log.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.util.Slog;
+
+/**
+ * Logging for android.view.textclassifier package.
+ */
+final class Log {
+
+    /**
+     * true: Enables full logging.
+     * false: Limits logging to debug level.
+     */
+    private static final boolean ENABLE_FULL_LOGGING = false;
+
+    private Log() {}
+
+    public static void d(String tag, String msg) {
+        Slog.d(tag, msg);
+    }
+
+    public static void e(String tag, String msg, Throwable tr) {
+        if (ENABLE_FULL_LOGGING) {
+            Slog.e(tag, msg, tr);
+        } else {
+            final String trString = (tr != null) ? tr.getClass().getSimpleName() : "??";
+            Slog.d(tag, String.format("%s (%s)", msg, trString));
+        }
+    }
+}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index ef08747..1c07be4 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -35,7 +35,6 @@
 import android.text.method.WordIterator;
 import android.text.style.ClickableSpan;
 import android.text.util.Linkify;
-import android.util.Log;
 import android.util.Patterns;
 import android.view.View;
 import android.widget.TextViewMetrics;
@@ -163,7 +162,7 @@
             }
         } catch (Throwable t) {
             // Avoid throwing from this method. Log the error.
-            Log.e(LOG_TAG, "Error getting assist info.", t);
+            Log.e(LOG_TAG, "Error getting text classification info.", t);
         }
         // Getting here means something went wrong, return a NO_OP result.
         return TextClassifier.NO_OP.classifyText(
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 4cb49a3..384f4f8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3888,7 +3888,7 @@
                 if (selected == null || selected.isEmpty()) {
                     menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL,
                             com.android.internal.R.string.autofill)
-                            .setShowAsAction(MenuItem.SHOW_AS_OVERFLOW_ALWAYS);
+                            .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
                 }
             }
 
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 199b596..631f388 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -131,7 +131,7 @@
      *
      * @hide
      */
-    private ApplicationInfo mApplication;
+    public ApplicationInfo mApplication;
 
     /**
      * The resource ID of the layout file. (Added to the parcel)
@@ -1519,8 +1519,7 @@
 
         @Override
         public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
-            return mNestedViews.mApplication.packageName.equals(parentInfo.packageName)
-                    && mNestedViews.mApplication.uid == parentInfo.uid;
+            return mNestedViews.hasSameAppInfo(parentInfo);
         }
 
         @Override
@@ -2138,8 +2137,7 @@
         if (landscape == null || portrait == null) {
             throw new RuntimeException("Both RemoteViews must be non-null");
         }
-        if (landscape.mApplication.uid != portrait.mApplication.uid
-                || !landscape.mApplication.packageName.equals(portrait.mApplication.packageName)) {
+        if (!landscape.hasSameAppInfo(portrait.mApplication)) {
             throw new RuntimeException("Both RemoteViews must share the same package and user");
         }
         mApplication = portrait.mApplication;
@@ -3555,6 +3553,15 @@
     }
 
     /**
+     * Returns true if the {@link #mApplication} is same as the provided info.
+     *
+     * @hide
+     */
+    public boolean hasSameAppInfo(ApplicationInfo info) {
+        return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
+    }
+
+    /**
      * Parcelable.Creator that instantiates RemoteViews objects
      */
     public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 0968652..e5ae0ca 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -16,11 +16,15 @@
 
 package android.widget;
 
-import android.Manifest;
+import android.annotation.WorkerThread;
+import android.app.IServiceConnection;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -28,7 +32,6 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
@@ -38,7 +41,6 @@
 import android.view.ViewGroup;
 import android.widget.RemoteViews.OnClickHandler;
 
-import com.android.internal.widget.IRemoteViewsAdapterConnection;
 import com.android.internal.widget.IRemoteViewsFactory;
 
 import java.lang.ref.WeakReference;
@@ -48,52 +50,33 @@
 import java.util.concurrent.Executor;
 
 /**
- * An adapter to a RemoteViewsService which fetches and caches RemoteViews
- * to be later inflated as child views.
+ * An adapter to a RemoteViewsService which fetches and caches RemoteViews to be later inflated as
+ * child views.
+ *
+ * The adapter runs in the host process, typically a Launcher app.
+ *
+ * It makes a service connection to the {@link RemoteViewsService} running in the
+ * AppWidgetsProvider's process. This connection is made on a background thread (and proxied via
+ * the platform to get the bind permissions) and all interaction with the service is done on the
+ * background thread.
+ *
+ * On first bind, the adapter will load can cache the RemoteViews locally. Afterwards the
+ * connection is only made when new RemoteViews are required.
+ * @hide
  */
-/** @hide */
 public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
-    private static final String MULTI_USER_PERM = Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 
     private static final String TAG = "RemoteViewsAdapter";
 
     // The max number of items in the cache
-    private static final int sDefaultCacheSize = 40;
+    private static final int DEFAULT_CACHE_SIZE = 40;
     // The delay (in millis) to wait until attempting to unbind from a service after a request.
     // This ensures that we don't stay continually bound to the service and that it can be destroyed
     // if we need the memory elsewhere in the system.
-    private static final int sUnbindServiceDelay = 5000;
+    private static final int UNBIND_SERVICE_DELAY = 5000;
 
     // Default height for the default loading view, in case we cannot get inflate the first view
-    private static final int sDefaultLoadingViewHeight = 50;
-
-    // Type defs for controlling different messages across the main and worker message queues
-    private static final int sDefaultMessageType = 0;
-    private static final int sUnbindServiceMessageType = 1;
-
-    private final Context mContext;
-    private final Intent mIntent;
-    private final int mAppWidgetId;
-    private final Executor mAsyncViewLoadExecutor;
-
-    private RemoteViewsAdapterServiceConnection mServiceConnection;
-    private WeakReference<RemoteAdapterConnectionCallback> mCallback;
-    private OnClickHandler mRemoteViewsOnClickHandler;
-    private final FixedSizeRemoteViewsCache mCache;
-    private int mVisibleWindowLowerBound;
-    private int mVisibleWindowUpperBound;
-
-    // A flag to determine whether we should notify data set changed after we connect
-    private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
-
-    // The set of requested views that are to be notified when the associated RemoteViews are
-    // loaded.
-    private RemoteViewsFrameLayoutRefSet mRequestedViews;
-
-    private HandlerThread mWorkerThread;
-    // items may be interrupted within the normally processed queues
-    private Handler mWorkerQueue;
-    private Handler mMainQueue;
+    private static final int DEFAULT_LOADING_VIEW_HEIGHT = 50;
 
     // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
     // structures;
@@ -110,11 +93,37 @@
     // duration, the cache is dropped.
     private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
 
+    private final Context mContext;
+    private final Intent mIntent;
+    private final int mAppWidgetId;
+    private final Executor mAsyncViewLoadExecutor;
+
+    private OnClickHandler mRemoteViewsOnClickHandler;
+    private final FixedSizeRemoteViewsCache mCache;
+    private int mVisibleWindowLowerBound;
+    private int mVisibleWindowUpperBound;
+
+    // The set of requested views that are to be notified when the associated RemoteViews are
+    // loaded.
+    private RemoteViewsFrameLayoutRefSet mRequestedViews;
+
+    private final HandlerThread mWorkerThread;
+    // items may be interrupted within the normally processed queues
+    private final Handler mMainHandler;
+    private final RemoteServiceHandler mServiceHandler;
+    private final RemoteAdapterConnectionCallback mCallback;
+
     // Used to indicate to the AdapterView that it can use this Adapter immediately after
     // construction (happens when we have a cached FixedSizeRemoteViewsCache).
     private boolean mDataReady = false;
 
     /**
+     * USed to dedupe {@link RemoteViews#mApplication} so that we do not hold on to
+     * multiple copies of the same ApplicationInfo object.
+     */
+    private ApplicationInfo mLastRemoteViewAppInfo;
+
+    /**
      * An interface for the RemoteAdapter to notify other classes when adapters
      * are actually connected to/disconnected from their actual services.
      */
@@ -151,154 +160,192 @@
         }
     }
 
+    static final int MSG_REQUEST_BIND = 1;
+    static final int MSG_NOTIFY_DATA_SET_CHANGED = 2;
+    static final int MSG_LOAD_NEXT_ITEM = 3;
+    static final int MSG_UNBIND_SERVICE = 4;
+
+    private static final int MSG_MAIN_HANDLER_COMMIT_METADATA = 1;
+    private static final int MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED = 2;
+    private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED = 3;
+    private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED = 4;
+    private static final int MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED = 5;
+
     /**
-     * The service connection that gets populated when the RemoteViewsService is
-     * bound.  This must be a static inner class to ensure that no references to the outer
-     * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
-     * garbage collected, and would cause us to leak activities due to the caching mechanism for
-     * FrameLayouts in the adapter).
+     * Handler for various interactions with the {@link RemoteViewsService}.
      */
-    private static class RemoteViewsAdapterServiceConnection extends
-            IRemoteViewsAdapterConnection.Stub {
-        private boolean mIsConnected;
-        private boolean mIsConnecting;
-        private WeakReference<RemoteViewsAdapter> mAdapter;
+    private static class RemoteServiceHandler extends Handler implements ServiceConnection {
+
+        private final WeakReference<RemoteViewsAdapter> mAdapter;
+        private final Context mContext;
+
         private IRemoteViewsFactory mRemoteViewsFactory;
 
-        public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
-            mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
+        // The last call to notifyDataSetChanged didn't succeed, try again on next service bind.
+        private boolean mNotifyDataSetChangedPending = false;
+        private boolean mBindRequested = false;
+
+        RemoteServiceHandler(Looper workerLooper, RemoteViewsAdapter adapter, Context context) {
+            super(workerLooper);
+            mAdapter = new WeakReference<>(adapter);
+            mContext = context;
         }
 
-        public synchronized void bind(Context context, int appWidgetId, Intent intent) {
-            if (!mIsConnecting) {
-                try {
-                    RemoteViewsAdapter adapter;
-                    final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
-                    if ((adapter = mAdapter.get()) != null) {
-                        mgr.bindRemoteViewsService(context.getOpPackageName(), appWidgetId,
-                                intent, asBinder());
-                    } else {
-                        Slog.w(TAG, "bind: adapter was null");
-                    }
-                    mIsConnecting = true;
-                } catch (Exception e) {
-                    Log.e("RVAServiceConnection", "bind(): " + e.getMessage());
-                    mIsConnecting = false;
-                    mIsConnected = false;
-                }
-            }
-        }
-
-        public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
-            try {
-                RemoteViewsAdapter adapter;
-                final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
-                if ((adapter = mAdapter.get()) != null) {
-                    mgr.unbindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent);
-                } else {
-                    Slog.w(TAG, "unbind: adapter was null");
-                }
-                mIsConnecting = false;
-            } catch (Exception e) {
-                Log.e("RVAServiceConnection", "unbind(): " + e.getMessage());
-                mIsConnecting = false;
-                mIsConnected = false;
-            }
-        }
-
-        public synchronized void onServiceConnected(IBinder service) {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            // This is called on the same thread.
             mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
+            enqueueDeferredUnbindServiceMessage();
 
-            // Remove any deferred unbind messages
-            final RemoteViewsAdapter adapter = mAdapter.get();
-            if (adapter == null) return;
+            RemoteViewsAdapter adapter = mAdapter.get();
+            if (adapter == null) {
+                return;
+            }
 
-            // Queue up work that we need to do for the callback to run
-            adapter.mWorkerQueue.post(new Runnable() {
-                @Override
-                public void run() {
-                    if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
-                        // Handle queued notifyDataSetChanged() if necessary
-                        adapter.onNotifyDataSetChanged();
-                    } else {
-                        IRemoteViewsFactory factory =
-                            adapter.mServiceConnection.getRemoteViewsFactory();
-                        try {
-                            if (!factory.isCreated()) {
-                                // We only call onDataSetChanged() if this is the factory was just
-                                // create in response to this bind
-                                factory.onDataSetChanged();
-                            }
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "Error notifying factory of data set changed in " +
-                                        "onServiceConnected(): " + e.getMessage());
-
-                            // Return early to prevent anything further from being notified
-                            // (effectively nothing has changed)
-                            return;
-                        } catch (RuntimeException e) {
-                            Log.e(TAG, "Error notifying factory of data set changed in " +
-                                    "onServiceConnected(): " + e.getMessage());
-                        }
-
-                        // Request meta data so that we have up to date data when calling back to
-                        // the remote adapter callback
-                        adapter.updateTemporaryMetaData();
-
-                        // Notify the host that we've connected
-                        adapter.mMainQueue.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                synchronized (adapter.mCache) {
-                                    adapter.mCache.commitTemporaryMetaData();
-                                }
-
-                                final RemoteAdapterConnectionCallback callback =
-                                    adapter.mCallback.get();
-                                if (callback != null) {
-                                    callback.onRemoteAdapterConnected();
-                                }
-                            }
-                        });
-                    }
-
-                    // Enqueue unbind message
-                    adapter.enqueueDeferredUnbindServiceMessage();
-                    mIsConnected = true;
-                    mIsConnecting = false;
+            if (mNotifyDataSetChangedPending) {
+                mNotifyDataSetChangedPending = false;
+                Message msg = Message.obtain(this, MSG_NOTIFY_DATA_SET_CHANGED);
+                handleMessage(msg);
+                msg.recycle();
+            } else {
+                if (!sendNotifyDataSetChange(false)) {
+                    return;
                 }
-            });
+
+                // Request meta data so that we have up to date data when calling back to
+                // the remote adapter callback
+                adapter.updateTemporaryMetaData(mRemoteViewsFactory);
+                adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
+                adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED);
+            }
         }
 
-        public synchronized void onServiceDisconnected() {
-            mIsConnected = false;
-            mIsConnecting = false;
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
             mRemoteViewsFactory = null;
+            RemoteViewsAdapter adapter = mAdapter.get();
+            if (adapter != null) {
+                adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED);
+            }
+        }
 
-            // Clear the main/worker queues
-            final RemoteViewsAdapter adapter = mAdapter.get();
-            if (adapter == null) return;
+        @Override
+        public void handleMessage(Message msg) {
+            RemoteViewsAdapter adapter = mAdapter.get();
 
-            adapter.mMainQueue.post(new Runnable() {
-                @Override
-                public void run() {
-                    // Dequeue any unbind messages
-                    adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
-
-                    final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
-                    if (callback != null) {
-                        callback.onRemoteAdapterDisconnected();
+            switch (msg.what) {
+                case MSG_REQUEST_BIND: {
+                    if (adapter == null || mRemoteViewsFactory != null) {
+                        enqueueDeferredUnbindServiceMessage();
                     }
+                    if (mBindRequested) {
+                        return;
+                    }
+                    int flags = Context.BIND_AUTO_CREATE
+                            | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE;
+                    final IServiceConnection sd = mContext.getServiceDispatcher(this, this, flags);
+                    Intent intent = (Intent) msg.obj;
+                    int appWidgetId = msg.arg1;
+                    mBindRequested = AppWidgetManager.getInstance(mContext)
+                            .bindRemoteViewsService(mContext, appWidgetId, intent, sd, flags);
+                    return;
                 }
-            });
+                case MSG_NOTIFY_DATA_SET_CHANGED: {
+                    enqueueDeferredUnbindServiceMessage();
+                    if (adapter == null) {
+                        return;
+                    }
+                    if (mRemoteViewsFactory == null) {
+                        mNotifyDataSetChangedPending = true;
+                        adapter.requestBindService();
+                        return;
+                    }
+                    if (!sendNotifyDataSetChange(true)) {
+                        return;
+                    }
+
+                    // Flush the cache so that we can reload new items from the service
+                    synchronized (adapter.mCache) {
+                        adapter.mCache.reset();
+                    }
+
+                    // Re-request the new metadata (only after the notification to the factory)
+                    adapter.updateTemporaryMetaData(mRemoteViewsFactory);
+                    int newCount;
+                    int[] visibleWindow;
+                    synchronized (adapter.mCache.getTemporaryMetaData()) {
+                        newCount = adapter.mCache.getTemporaryMetaData().count;
+                        visibleWindow = adapter.getVisibleWindow(newCount);
+                    }
+
+                    // Pre-load (our best guess of) the views which are currently visible in the
+                    // AdapterView. This mitigates flashing and flickering of loading views when a
+                    // widget notifies that its data has changed.
+                    for (int position : visibleWindow) {
+                        // Because temporary meta data is only ever modified from this thread
+                        // (ie. mWorkerThread), it is safe to assume that count is a valid
+                        // representation.
+                        if (position < newCount) {
+                            adapter.updateRemoteViews(mRemoteViewsFactory, position, false);
+                        }
+                    }
+
+                    // Propagate the notification back to the base adapter
+                    adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
+                    adapter.mMainHandler.sendEmptyMessage(
+                            MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
+                    return;
+                }
+
+                case MSG_LOAD_NEXT_ITEM: {
+                    if (adapter == null || mRemoteViewsFactory == null) {
+                        return;
+                    }
+                    removeMessages(MSG_UNBIND_SERVICE);
+                    // Get the next index to load
+                    final int position = adapter.mCache.getNextIndexToLoad();
+                    if (position > -1) {
+                        // Load the item, and notify any existing RemoteViewsFrameLayouts
+                        adapter.updateRemoteViews(mRemoteViewsFactory, position, true);
+
+                        // Queue up for the next one to load
+                        sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
+                    } else {
+                        // No more items to load, so queue unbind
+                        enqueueDeferredUnbindServiceMessage();
+                    }
+                    return;
+                }
+                case MSG_UNBIND_SERVICE: {
+                    unbindNow();
+                    return;
+                }
+            }
         }
 
-        public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
-            return mRemoteViewsFactory;
+        protected void unbindNow() {
+            if (mBindRequested) {
+                mBindRequested = false;
+                mContext.unbindService(this);
+            }
+            mRemoteViewsFactory = null;
         }
 
-        public synchronized boolean isConnected() {
-            return mIsConnected;
+        private boolean sendNotifyDataSetChange(boolean always) {
+            try {
+                if (always || !mRemoteViewsFactory.isCreated()) {
+                    mRemoteViewsFactory.onDataSetChanged();
+                }
+                return true;
+            } catch (RemoteException | RuntimeException e) {
+                Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
+                return false;
+            }
+        }
+
+        private void enqueueDeferredUnbindServiceMessage() {
+            removeMessages(MSG_UNBIND_SERVICE);
+            sendEmptyMessageDelayed(MSG_UNBIND_SERVICE, UNBIND_SERVICE_DELAY);
         }
     }
 
@@ -309,6 +356,8 @@
     static class RemoteViewsFrameLayout extends AppWidgetHostView {
         private final FixedSizeRemoteViewsCache mCache;
 
+        public int cacheIndex = -1;
+
         public RemoteViewsFrameLayout(Context context, FixedSizeRemoteViewsCache cache) {
             super(context);
             mCache = cache;
@@ -359,26 +408,23 @@
      * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
      * adapter that have not yet had their RemoteViews loaded.
      */
-    private class RemoteViewsFrameLayoutRefSet {
-        private final SparseArray<LinkedList<RemoteViewsFrameLayout>> mReferences =
-                new SparseArray<>();
-        private final HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>
-                mViewToLinkedList = new HashMap<>();
+    private class RemoteViewsFrameLayoutRefSet
+            extends SparseArray<LinkedList<RemoteViewsFrameLayout>> {
 
         /**
          * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
          */
         public void add(int position, RemoteViewsFrameLayout layout) {
-            LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(position);
+            LinkedList<RemoteViewsFrameLayout> refs = get(position);
 
             // Create the list if necessary
             if (refs == null) {
-                refs = new LinkedList<RemoteViewsFrameLayout>();
-                mReferences.put(position, refs);
+                refs = new LinkedList<>();
+                put(position, refs);
             }
-            mViewToLinkedList.put(layout, refs);
 
             // Add the references to the list
+            layout.cacheIndex = position;
             refs.add(layout);
         }
 
@@ -389,18 +435,13 @@
         public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
             if (view == null) return;
 
-            final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(position);
+            // Remove this set from the original mapping
+            final LinkedList<RemoteViewsFrameLayout> refs = removeReturnOld(position);
             if (refs != null) {
                 // Notify all the references for that position of the newly loaded RemoteViews
                 for (final RemoteViewsFrameLayout ref : refs) {
                     ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler, true);
-                    if (mViewToLinkedList.containsKey(ref)) {
-                        mViewToLinkedList.remove(ref);
-                    }
                 }
-                refs.clear();
-                // Remove this set from the original mapping
-                mReferences.remove(position);
             }
         }
 
@@ -408,20 +449,14 @@
          * We need to remove views from this set if they have been recycled by the AdapterView.
          */
         public void removeView(RemoteViewsFrameLayout rvfl) {
-            if (mViewToLinkedList.containsKey(rvfl)) {
-                mViewToLinkedList.get(rvfl).remove(rvfl);
-                mViewToLinkedList.remove(rvfl);
+            if (rvfl.cacheIndex < 0) {
+                return;
             }
-        }
-
-        /**
-         * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
-         */
-        public void clear() {
-            // We currently just clear the references, and leave all the previous layouts returned
-            // in their default state of the loading view.
-            mReferences.clear();
-            mViewToLinkedList.clear();
+            final LinkedList<RemoteViewsFrameLayout> refs = get(rvfl.cacheIndex);
+            if (refs != null) {
+                refs.remove(rvfl);
+            }
+            rvfl.cacheIndex = -1;
         }
     }
 
@@ -512,7 +547,6 @@
      *
      */
     private static class FixedSizeRemoteViewsCache {
-        private static final String TAG = "FixedSizeRemoteViewsCache";
 
         // The meta data related to all the RemoteViews, ie. count, is stable, etc.
         // The meta data objects are made final so that they can be locked on independently
@@ -534,7 +568,7 @@
         // too much memory.
         private final SparseArray<RemoteViews> mIndexRemoteViews = new SparseArray<>();
 
-        // An array of indices to load, Indices which are explicitely requested are set to true,
+        // An array of indices to load, Indices which are explicitly requested are set to true,
         // and those determined by the preloading algorithm to prefetch are set to false.
         private final SparseBooleanArray mIndicesToLoad = new SparseBooleanArray();
 
@@ -676,7 +710,7 @@
                 }
             }
 
-            int count = 0;
+            int count;
             synchronized (mMetaData) {
                 count = mMetaData.count;
             }
@@ -791,9 +825,11 @@
         // Initialize the worker thread
         mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
         mWorkerThread.start();
-        mWorkerQueue = new Handler(mWorkerThread.getLooper());
-        mMainQueue = new Handler(Looper.myLooper(), this);
+        mMainHandler = new Handler(Looper.myLooper(), this);
+        mServiceHandler = new RemoteServiceHandler(mWorkerThread.getLooper(), this,
+                context.getApplicationContext());
         mAsyncViewLoadExecutor = useAsyncLoader ? new HandlerThreadExecutor(mWorkerThread) : null;
+        mCallback = callback;
 
         if (sCacheRemovalThread == null) {
             sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
@@ -801,10 +837,6 @@
             sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
         }
 
-        // Initialize the cache and the service connection on startup
-        mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
-        mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
-
         RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent),
                 mAppWidgetId);
 
@@ -819,7 +851,7 @@
                     }
                 }
             } else {
-                mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
+                mCache = new FixedSizeRemoteViewsCache(DEFAULT_CACHE_SIZE);
             }
             if (!mDataReady) {
                 requestBindService();
@@ -830,9 +862,8 @@
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mWorkerThread != null) {
-                mWorkerThread.quit();
-            }
+            mServiceHandler.unbindNow();
+            mWorkerThread.quit();
         } finally {
             super.finalize();
         }
@@ -869,16 +900,13 @@
                 sCachedRemoteViewsCaches.put(key, mCache);
             }
 
-            Runnable r = new Runnable() {
-                @Override
-                public void run() {
-                    synchronized (sCachedRemoteViewsCaches) {
-                        if (sCachedRemoteViewsCaches.containsKey(key)) {
-                            sCachedRemoteViewsCaches.remove(key);
-                        }
-                        if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
-                            sRemoteViewsCacheRemoveRunnables.remove(key);
-                        }
+            Runnable r = () -> {
+                synchronized (sCachedRemoteViewsCaches) {
+                    if (sCachedRemoteViewsCaches.containsKey(key)) {
+                        sCachedRemoteViewsCaches.remove(key);
+                    }
+                    if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
+                        sRemoteViewsCacheRemoveRunnables.remove(key);
                     }
                 }
             };
@@ -887,54 +915,8 @@
         }
     }
 
-    private void loadNextIndexInBackground() {
-        mWorkerQueue.post(new Runnable() {
-            @Override
-            public void run() {
-                if (mServiceConnection.isConnected()) {
-                    // Get the next index to load
-                    int position = -1;
-                    synchronized (mCache) {
-                        position = mCache.getNextIndexToLoad();
-                    }
-                    if (position > -1) {
-                        // Load the item, and notify any existing RemoteViewsFrameLayouts
-                        updateRemoteViews(position, true);
-
-                        // Queue up for the next one to load
-                        loadNextIndexInBackground();
-                    } else {
-                        // No more items to load, so queue unbind
-                        enqueueDeferredUnbindServiceMessage();
-                    }
-                }
-            }
-        });
-    }
-
-    private void processException(String method, Exception e) {
-        Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
-
-        // If we encounter a crash when updating, we should reset the metadata & cache and trigger
-        // a notifyDataSetChanged to update the widget accordingly
-        final RemoteViewsMetaData metaData = mCache.getMetaData();
-        synchronized (metaData) {
-            metaData.reset();
-        }
-        synchronized (mCache) {
-            mCache.reset();
-        }
-        mMainQueue.post(new Runnable() {
-            @Override
-            public void run() {
-                superNotifyDataSetChanged();
-            }
-        });
-    }
-
-    private void updateTemporaryMetaData() {
-        IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
+    @WorkerThread
+    private void updateTemporaryMetaData(IRemoteViewsFactory factory) {
         try {
             // get the properties/first view (so that we can use it to
             // measure our dummy views)
@@ -958,40 +940,54 @@
                 tmpMetaData.count = count;
                 tmpMetaData.loadingTemplate = loadingTemplate;
             }
-        } catch(RemoteException e) {
-            processException("updateMetaData", e);
-        } catch(RuntimeException e) {
-            processException("updateMetaData", e);
+        } catch (RemoteException | RuntimeException e) {
+            Log.e("RemoteViewsAdapter", "Error in updateMetaData: " + e.getMessage());
+
+            // If we encounter a crash when updating, we should reset the metadata & cache
+            // and trigger a notifyDataSetChanged to update the widget accordingly
+            synchronized (mCache.getMetaData()) {
+                mCache.getMetaData().reset();
+            }
+            synchronized (mCache) {
+                mCache.reset();
+            }
+            mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
         }
     }
 
-    private void updateRemoteViews(final int position, boolean notifyWhenLoaded) {
-        IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
+    @WorkerThread
+    private void updateRemoteViews(IRemoteViewsFactory factory, int position,
+            boolean notifyWhenLoaded) {
         // Load the item information from the remote service
-        RemoteViews remoteViews = null;
-        long itemId = 0;
+        final RemoteViews remoteViews;
+        final long itemId;
         try {
             remoteViews = factory.getViewAt(position);
             itemId = factory.getItemId(position);
-        } catch (RemoteException e) {
+
+            if (remoteViews == null) {
+                throw new RuntimeException("Null remoteViews");
+            }
+        } catch (RemoteException | RuntimeException e) {
             Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
 
             // Return early to prevent additional work in re-centering the view cache, and
             // swapping from the loading view
             return;
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
-            return;
         }
 
-        if (remoteViews == null) {
-            // If a null view was returned, we break early to prevent it from getting
-            // into our cache and causing problems later. The effect is that the child  at this
-            // position will remain as a loading view until it is updated.
-            Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
-                    "returned from RemoteViewsFactory.");
-            return;
+        if (remoteViews.mApplication != null) {
+            // We keep track of last application info. This helps when all the remoteViews have
+            // same applicationInfo, which should be the case for a typical adapter. But if every
+            // view has different application info, there will not be any optimization.
+            if (mLastRemoteViewAppInfo != null
+                    && remoteViews.hasSameAppInfo(mLastRemoteViewAppInfo)) {
+                // We should probably also update the remoteViews for nested ViewActions.
+                // Hopefully, RemoteViews in an adapter would be less complicated.
+                remoteViews.mApplication = mLastRemoteViewAppInfo;
+            } else {
+                mLastRemoteViewAppInfo = remoteViews.mApplication;
+            }
         }
 
         int layoutId = remoteViews.getLayoutId();
@@ -1004,21 +1000,15 @@
         }
         synchronized (mCache) {
             if (viewTypeInRange) {
-                int[] visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
-                        mVisibleWindowUpperBound, cacheCount);
+                int[] visibleWindow = getVisibleWindow(cacheCount);
                 // Cache the RemoteViews we loaded
                 mCache.insert(position, remoteViews, itemId, visibleWindow);
 
-                // Notify all the views that we have previously returned for this index that
-                // there is new data for it.
-                final RemoteViews rv = remoteViews;
                 if (notifyWhenLoaded) {
-                    mMainQueue.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            mRequestedViews.notifyOnRemoteViewsLoaded(position, rv);
-                        }
-                    });
+                    // Notify all the views that we have previously returned for this index that
+                    // there is new data for it.
+                    Message.obtain(mMainHandler, MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED, position, 0,
+                            remoteViews).sendToTarget();
                 }
             } else {
                 // We need to log an error here, as the the view type count specified by the
@@ -1057,7 +1047,7 @@
     }
 
     public int getItemViewType(int position) {
-        int typeId = 0;
+        final int typeId;
         synchronized (mCache) {
             if (mCache.containsMetaDataAt(position)) {
                 typeId = mCache.getMetaDataAt(position).typeId;
@@ -1088,14 +1078,13 @@
         synchronized (mCache) {
             RemoteViews rv = mCache.getRemoteViewsAt(position);
             boolean isInCache = (rv != null);
-            boolean isConnected = mServiceConnection.isConnected();
             boolean hasNewItems = false;
 
             if (convertView != null && convertView instanceof RemoteViewsFrameLayout) {
                 mRequestedViews.removeView((RemoteViewsFrameLayout) convertView);
             }
 
-            if (!isInCache && !isConnected) {
+            if (!isInCache) {
                 // Requesting bind service will trigger a super.notifyDataSetChanged(), which will
                 // in turn trigger another request to getView()
                 requestBindService();
@@ -1115,7 +1104,9 @@
             if (isInCache) {
                 // Apply the view synchronously if possible, to avoid flickering
                 layout.onRemoteViewsLoaded(rv, mRemoteViewsOnClickHandler, false);
-                if (hasNewItems) loadNextIndexInBackground();
+                if (hasNewItems) {
+                    mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
+                }
             } else {
                 // If the views is not loaded, apply the loading view. If the loading view doesn't
                 // exist, the layout will create a default view based on the firstView height.
@@ -1125,7 +1116,7 @@
                         false);
                 mRequestedViews.add(position, layout);
                 mCache.queueRequestedPositionToLoad(position);
-                loadNextIndexInBackground();
+                mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
             }
             return layout;
         }
@@ -1149,69 +1140,12 @@
         return getCount() <= 0;
     }
 
-    private void onNotifyDataSetChanged() {
-        // Complete the actual notifyDataSetChanged() call initiated earlier
-        IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-        try {
-            factory.onDataSetChanged();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
-
-            // Return early to prevent from further being notified (since nothing has
-            // changed)
-            return;
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
-            return;
-        }
-
-        // Flush the cache so that we can reload new items from the service
-        synchronized (mCache) {
-            mCache.reset();
-        }
-
-        // Re-request the new metadata (only after the notification to the factory)
-        updateTemporaryMetaData();
-        int newCount;
-        int[] visibleWindow;
-        synchronized(mCache.getTemporaryMetaData()) {
-            newCount = mCache.getTemporaryMetaData().count;
-            visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
-                    mVisibleWindowUpperBound, newCount);
-        }
-
-        // Pre-load (our best guess of) the views which are currently visible in the AdapterView.
-        // This mitigates flashing and flickering of loading views when a widget notifies that
-        // its data has changed.
-        for (int i: visibleWindow) {
-            // Because temporary meta data is only ever modified from this thread (ie.
-            // mWorkerThread), it is safe to assume that count is a valid representation.
-            if (i < newCount) {
-                updateRemoteViews(i, false);
-            }
-        }
-
-        // Propagate the notification back to the base adapter
-        mMainQueue.post(new Runnable() {
-            @Override
-            public void run() {
-                synchronized (mCache) {
-                    mCache.commitTemporaryMetaData();
-                }
-
-                superNotifyDataSetChanged();
-                enqueueDeferredUnbindServiceMessage();
-            }
-        });
-
-        // Reset the notify flagflag
-        mNotifyDataSetChangedAfterOnServiceConnected = false;
-    }
-
     /**
      * Returns a sorted array of all integers between lower and upper.
      */
-    private int[] getVisibleWindow(int lower, int upper, int count) {
+    private int[] getVisibleWindow(int count) {
+        int lower = mVisibleWindowLowerBound;
+        int upper = mVisibleWindowUpperBound;
         // In the case that the window is invalid or uninitialized, return an empty window.
         if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
             return new int[0];
@@ -1241,23 +1175,8 @@
     }
 
     public void notifyDataSetChanged() {
-        // Dequeue any unbind messages
-        mMainQueue.removeMessages(sUnbindServiceMessageType);
-
-        // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
-        // connect
-        if (!mServiceConnection.isConnected()) {
-            mNotifyDataSetChangedAfterOnServiceConnected = true;
-            requestBindService();
-            return;
-        }
-
-        mWorkerQueue.post(new Runnable() {
-            @Override
-            public void run() {
-                onNotifyDataSetChanged();
-            }
-        });
+        mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
+        mServiceHandler.sendEmptyMessage(MSG_NOTIFY_DATA_SET_CHANGED);
     }
 
     void superNotifyDataSetChanged() {
@@ -1266,35 +1185,38 @@
 
     @Override
     public boolean handleMessage(Message msg) {
-        boolean result = false;
         switch (msg.what) {
-        case sUnbindServiceMessageType:
-            if (mServiceConnection.isConnected()) {
-                mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
+            case MSG_MAIN_HANDLER_COMMIT_METADATA: {
+                mCache.commitTemporaryMetaData();
+                return true;
             }
-            result = true;
-            break;
-        default:
-            break;
+            case MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED: {
+                superNotifyDataSetChanged();
+                return true;
+            }
+            case MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED: {
+                if (mCallback != null) {
+                    mCallback.onRemoteAdapterConnected();
+                }
+                return true;
+            }
+            case MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED: {
+                if (mCallback != null) {
+                    mCallback.onRemoteAdapterDisconnected();
+                }
+                return true;
+            }
+            case MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED: {
+                mRequestedViews.notifyOnRemoteViewsLoaded(msg.arg1, (RemoteViews) msg.obj);
+                return true;
+            }
         }
-        return result;
+        return false;
     }
 
-    private void enqueueDeferredUnbindServiceMessage() {
-        // Remove any existing deferred-unbind messages
-        mMainQueue.removeMessages(sUnbindServiceMessageType);
-        mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
-    }
-
-    private boolean requestBindService() {
-        // Try binding the service (which will start it if it's not already running)
-        if (!mServiceConnection.isConnected()) {
-            mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
-        }
-
-        // Remove any existing deferred-unbind messages
-        mMainQueue.removeMessages(sUnbindServiceMessageType);
-        return mServiceConnection.isConnected();
+    private void requestBindService() {
+        mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
+        Message.obtain(mServiceHandler, MSG_REQUEST_BIND, mAppWidgetId, 0, mIntent).sendToTarget();
     }
 
     private static class HandlerThreadExecutor implements Executor {
@@ -1322,7 +1244,7 @@
             remoteViews = views;
 
             float density = context.getResources().getDisplayMetrics().density;
-            defaultHeight = Math.round(sDefaultLoadingViewHeight * density);
+            defaultHeight = Math.round(DEFAULT_LOADING_VIEW_HEIGHT * density);
         }
 
         public void loadFirstViewHeight(
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 3d5cd0f..d0719ee 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -238,7 +238,7 @@
             mSearchView.setOnQueryTextListener(this);
 
             // Restore previous search status
-            if (mPreviousSearch != null) {
+            if (!TextUtils.isEmpty(mPreviousSearch)) {
                 searchMenuItem.expandActionView();
                 mSearchView.setIconified(false);
                 mSearchView.setActivated(true);
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index caf35b3..a4da6b9c 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -26,6 +26,8 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.widget.RemoteViews;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
 
 /** {@hide} */
 interface IAppWidgetService {
@@ -62,9 +64,9 @@
     void setBindAppWidgetPermission(in String packageName, int userId, in boolean permission);
     boolean bindAppWidgetId(in String callingPackage, int appWidgetId,
             int providerProfileId, in ComponentName providerComponent, in Bundle options);
-    void bindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent,
-            in IBinder connection);
-    void unbindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent);
+    boolean bindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent,
+            IApplicationThread caller, IBinder token, IServiceConnection connection, int flags);
+
     int[] getAppWidgetIds(in ComponentName providerComponent);
     boolean isBoundWidgetPackage(String packageName, int userId);
     boolean requestPinAppWidget(String packageName, in ComponentName providerComponent,
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 5c310b1..239d709 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -6428,6 +6428,11 @@
         }
 
         @Override
+        public Timer getWifiScanTimer() {
+            return mWifiScanTimer;
+        }
+
+        @Override
         public int getWifiScanBackgroundCount(int which) {
             if (mWifiScanTimer == null || mWifiScanTimer.getSubTimer() == null) {
                 return 0;
@@ -6454,6 +6459,14 @@
         }
 
         @Override
+        public Timer getWifiScanBackgroundTimer() {
+            if (mWifiScanTimer == null) {
+                return null;
+            }
+            return mWifiScanTimer.getSubTimer();
+        }
+
+        @Override
         public long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which) {
             if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
             if (mWifiBatchedScanTimer[csphBin] == null) {
diff --git a/core/java/com/android/internal/util/WakeupMessage.java b/core/java/com/android/internal/util/WakeupMessage.java
index 46098c5..70b6f96 100644
--- a/core/java/com/android/internal/util/WakeupMessage.java
+++ b/core/java/com/android/internal/util/WakeupMessage.java
@@ -47,17 +47,19 @@
     protected final int mCmd, mArg1, mArg2;
     @VisibleForTesting
     protected final Object mObj;
+    private final Runnable mRunnable;
     private boolean mScheduled;
 
     public WakeupMessage(Context context, Handler handler,
             String cmdName, int cmd, int arg1, int arg2, Object obj) {
-        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        mAlarmManager = getAlarmManager(context);
         mHandler = handler;
         mCmdName = cmdName;
         mCmd = cmd;
         mArg1 = arg1;
         mArg2 = arg2;
         mObj = obj;
+        mRunnable = null;
     }
 
     public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) {
@@ -73,6 +75,21 @@
         this(context, handler, cmdName, cmd, 0, 0, null);
     }
 
+    public WakeupMessage(Context context, Handler handler, String cmdName, Runnable runnable) {
+        mAlarmManager = getAlarmManager(context);
+        mHandler = handler;
+        mCmdName = cmdName;
+        mCmd = 0;
+        mArg1 = 0;
+        mArg2 = 0;
+        mObj = null;
+        mRunnable = runnable;
+    }
+
+    private static AlarmManager getAlarmManager(Context context) {
+        return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+    }
+
     /**
      * Schedule the message to be delivered at the time in milliseconds of the
      * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} clock and wakeup
@@ -107,7 +124,12 @@
             mScheduled = false;
         }
         if (stillScheduled) {
-            Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+            Message msg;
+            if (mRunnable == null) {
+                msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+            } else {
+                msg = Message.obtain(mHandler, mRunnable);
+            }
             mHandler.dispatchMessage(msg);
             msg.recycle();
         }
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 15e271e..9d012de 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -658,7 +658,7 @@
 
     @Override
     public boolean requiresOverflow() {
-        return (mShowAsAction & SHOW_AS_OVERFLOW_ALWAYS) == SHOW_AS_OVERFLOW_ALWAYS;
+        return !requiresActionButton() && !requestsActionButton();
     }
 
     public void setIsActionButton(boolean isActionButton) {
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index f63b5a2..e3b1c01 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -64,6 +64,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
@@ -118,6 +119,36 @@
     };
 
     /**
+     * Sorts the list of menu items to conform to certain requirements.
+     */
+    private final Comparator<MenuItem> mMenuItemComparator = (menuItem1, menuItem2) -> {
+        // Ensure the assist menu item is always the first item:
+        if (menuItem1.getItemId() == android.R.id.textAssist) {
+            return menuItem2.getItemId() == android.R.id.textAssist ? 0 : -1;
+        }
+        if (menuItem2.getItemId() == android.R.id.textAssist) {
+            return 1;
+        }
+
+        // Order by SHOW_AS_ACTION type:
+        if (menuItem1.requiresActionButton()) {
+            return menuItem2.requiresActionButton() ? 0 : -1;
+        }
+        if (menuItem2.requiresActionButton()) {
+            return 1;
+        }
+        if (menuItem1.requiresOverflow()) {
+            return menuItem2.requiresOverflow() ? 0 : 1;
+        }
+        if (menuItem2.requiresOverflow()) {
+            return -1;
+        }
+
+        // Order by order value:
+        return menuItem1.getOrder() - menuItem2.getOrder();
+    };
+
+    /**
      * Initializes a floating toolbar.
      */
     public FloatingToolbar(Window window) {
@@ -230,7 +261,7 @@
 
     private void doShow() {
         List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
-        tidy(menuItems);
+        menuItems.sort(mMenuItemComparator);
         if (!isCurrentlyShowing(menuItems) || mWidthChanged) {
             mPopup.dismiss();
             mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
@@ -288,36 +319,6 @@
         return menuItems;
     }
 
-    /**
-     * Update the list of menu items to conform to certain requirements.
-     */
-    private void tidy(List<MenuItem> menuItems) {
-        int assistItemIndex = -1;
-        Drawable assistItemDrawable = null;
-
-        final int size = menuItems.size();
-        for (int i = 0; i < size; i++) {
-            final MenuItem menuItem = menuItems.get(i);
-
-            if (menuItem.getItemId() == android.R.id.textAssist) {
-                assistItemIndex = i;
-                assistItemDrawable = menuItem.getIcon();
-            }
-
-            // Remove icons for all menu items with text.
-            if (!TextUtils.isEmpty(menuItem.getTitle())) {
-                menuItem.setIcon(null);
-            }
-        }
-        if (assistItemIndex > -1) {
-            final MenuItem assistMenuItem = menuItems.remove(assistItemIndex);
-            // Ensure the assist menu item preserves its icon.
-            assistMenuItem.setIcon(assistItemDrawable);
-            // Ensure the assist menu item is always the first item.
-            menuItems.add(0, assistMenuItem);
-        }
-    }
-
     private void registerOrientationHandler() {
         unregisterOrientationHandler();
         mWindow.getDecorView().addOnLayoutChangeListener(mOrientationChangeHandler);
@@ -1148,7 +1149,8 @@
             // add the overflow menu items to the end of the remainingMenuItems list.
             final LinkedList<MenuItem> overflowMenuItems = new LinkedList();
             for (MenuItem menuItem : menuItems) {
-                if (menuItem.requiresOverflow()) {
+                if (menuItem.getItemId() != android.R.id.textAssist
+                        && menuItem.requiresOverflow()) {
                     overflowMenuItems.add(menuItem);
                 } else {
                     remainingMenuItems.add(menuItem);
@@ -1171,7 +1173,9 @@
                     break;
                 }
 
-                View menuItemButton = createMenuItemButton(mContext, menuItem, mIconTextSpacing);
+                final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist;
+                final View menuItemButton = createMenuItemButton(
+                        mContext, menuItem, mIconTextSpacing, showIcon);
 
                 // Adding additional start padding for the first button to even out button spacing.
                 if (isFirstItem) {
@@ -1193,16 +1197,17 @@
                 }
 
                 menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-                final int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
+                final int menuItemButtonWidth = Math.min(
+                        menuItemButton.getMeasuredWidth(), toolbarWidth);
 
                 final boolean isNewGroup = !isFirstItem && lastGroupId != menuItem.getGroupId();
                 final int extraPadding = isNewGroup ? menuItemButton.getPaddingEnd() * 2 : 0;
 
                 // Check if we can fit an item while reserving space for the overflowButton.
-                boolean canFitWithOverflow =
+                final boolean canFitWithOverflow =
                         menuItemButtonWidth <=
                                 availableWidth - mOverflowButtonSize.getWidth() - extraPadding;
-                boolean canFitNoOverflow =
+                final boolean canFitNoOverflow =
                         isLastItem && menuItemButtonWidth <= availableWidth - extraPadding;
                 if (canFitWithOverflow || canFitNoOverflow) {
                     if (isNewGroup) {
@@ -1211,7 +1216,8 @@
 
                         // Add extra padding to the end of the previous button.
                         // Half of the extra padding (less borderWidth) goes to the previous button.
-                        View previousButton = mMainPanel.getChildAt(mMainPanel.getChildCount() - 1);
+                        final View previousButton = mMainPanel.getChildAt(
+                                mMainPanel.getChildCount() - 1);
                         final int prevPaddingEnd = previousButton.getPaddingEnd()
                                 + extraPadding / 2 - dividerWidth;
                         previousButton.setPaddingRelative(
@@ -1612,7 +1618,8 @@
             public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
                 Preconditions.checkNotNull(menuItem);
                 if (convertView != null) {
-                    updateMenuItemButton(convertView, menuItem, mIconTextSpacing);
+                    updateMenuItemButton(
+                            convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
                 } else {
                     convertView = createMenuButton(menuItem);
                 }
@@ -1621,17 +1628,26 @@
             }
 
             public int calculateWidth(MenuItem menuItem) {
-                updateMenuItemButton(mCalculator, menuItem, mIconTextSpacing);
+                updateMenuItemButton(
+                        mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
                 mCalculator.measure(
                         View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
                 return mCalculator.getMeasuredWidth();
             }
 
             private View createMenuButton(MenuItem menuItem) {
-                View button = createMenuItemButton(mContext, menuItem, mIconTextSpacing);
+                View button = createMenuItemButton(
+                        mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
                 button.setPadding(mSidePadding, 0, mSidePadding, 0);
                 return button;
             }
+
+            private boolean shouldShowIcon(MenuItem menuItem) {
+                if (menuItem != null) {
+                    return menuItem.getGroupId() == android.R.id.textAssist;
+                }
+                return false;
+            }
         }
     }
 
@@ -1639,11 +1655,11 @@
      * Creates and returns a menu button for the specified menu item.
      */
     private static View createMenuItemButton(
-            Context context, MenuItem menuItem, int iconTextSpacing) {
+            Context context, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
         final View menuItemButton = LayoutInflater.from(context)
                 .inflate(R.layout.floating_popup_menu_button, null);
         if (menuItem != null) {
-            updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing);
+            updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing, showIcon);
         }
         return menuItemButton;
     }
@@ -1652,18 +1668,19 @@
      * Updates the specified menu item button with the specified menu item data.
      */
     private static void updateMenuItemButton(
-            View menuItemButton, MenuItem menuItem, int iconTextSpacing) {
-        final TextView buttonText = (TextView) menuItemButton.findViewById(
+            View menuItemButton, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
+        final TextView buttonText = menuItemButton.findViewById(
                 R.id.floating_toolbar_menu_item_text);
+        buttonText.setEllipsize(null);
         if (TextUtils.isEmpty(menuItem.getTitle())) {
             buttonText.setVisibility(View.GONE);
         } else {
             buttonText.setVisibility(View.VISIBLE);
             buttonText.setText(menuItem.getTitle());
         }
-        final ImageView buttonIcon = (ImageView) menuItemButton
-                .findViewById(R.id.floating_toolbar_menu_item_image);
-        if (menuItem.getIcon() == null) {
+        final ImageView buttonIcon = menuItemButton.findViewById(
+                R.id.floating_toolbar_menu_item_image);
+        if (menuItem.getIcon() == null || !showIcon) {
             buttonIcon.setVisibility(View.GONE);
             if (buttonText != null) {
                 buttonText.setPaddingRelative(0, 0, 0, 0);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 928626b..4fbd265 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -87,7 +87,6 @@
         "android_view_ThreadedRenderer.cpp",
         "android_view_VelocityTracker.cpp",
         "android_text_AndroidCharacter.cpp",
-        "android_text_AndroidBidi.cpp",
         "android_text_Hyphenator.cpp",
         "android_text_StaticLayout.cpp",
         "android_os_Debug.cpp",
@@ -302,13 +301,4 @@
         // GraphicsJNI.h includes hwui headers
         "libhwui",
     ],
-
-    product_variables: {
-        debuggable: {
-            cflags: ["-D__ANDROID_DEBUGGABLE__"]
-        },
-        treble: {
-            cflags: ["-D__ANDROID_TREBLE__"]
-        },
-    },
 }
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index da6d5aa..8977891 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -177,7 +177,6 @@
 extern int register_android_text_AndroidCharacter(JNIEnv *env);
 extern int register_android_text_Hyphenator(JNIEnv *env);
 extern int register_android_text_StaticLayout(JNIEnv *env);
-extern int register_android_text_AndroidBidi(JNIEnv *env);
 extern int register_android_opengl_classes(JNIEnv *env);
 extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
 extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env);
@@ -1335,7 +1334,6 @@
     REG_JNI(register_android_text_AndroidCharacter),
     REG_JNI(register_android_text_Hyphenator),
     REG_JNI(register_android_text_StaticLayout),
-    REG_JNI(register_android_text_AndroidBidi),
     REG_JNI(register_android_view_InputDevice),
     REG_JNI(register_android_view_KeyCharacterMap),
     REG_JNI(register_android_os_Process),
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index e33d6ea..d3da21b 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -755,7 +755,7 @@
         return;
     }
 
-    int fd = open("/proc/meminfo", O_RDONLY);
+    int fd = open("/proc/meminfo", O_RDONLY | O_CLOEXEC);
 
     if (fd < 0) {
         ALOGW("Unable to open /proc/meminfo: %s\n", strerror(errno));
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 59ca050..fe14d48 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -306,7 +306,7 @@
         jstring serviceNameObj) {
 
     using ::android::hidl::base::V1_0::IBase;
-    using ::android::hidl::manager::V1_0::IServiceManager;
+    using ::android::hardware::details::getRawServiceInternal;
 
     if (ifaceNameObj == NULL) {
         jniThrowException(env, "java/lang/NullPointerException", NULL);
@@ -317,22 +317,12 @@
         return NULL;
     }
 
-    auto manager = hardware::defaultServiceManager();
-
-    if (manager == nullptr) {
-        LOG(ERROR) << "Could not get hwservicemanager.";
-        signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
-        return NULL;
-    }
-
     const char *ifaceNameCStr = env->GetStringUTFChars(ifaceNameObj, NULL);
     if (ifaceNameCStr == NULL) {
         return NULL; // XXX exception already pending?
     }
     std::string ifaceName(ifaceNameCStr);
     env->ReleaseStringUTFChars(ifaceNameObj, ifaceNameCStr);
-    ::android::hardware::hidl_string ifaceNameHStr;
-    ifaceNameHStr.setToExternal(ifaceName.c_str(), ifaceName.size());
 
     const char *serviceNameCStr = env->GetStringUTFChars(serviceNameObj, NULL);
     if (serviceNameCStr == NULL) {
@@ -340,50 +330,9 @@
     }
     std::string serviceName(serviceNameCStr);
     env->ReleaseStringUTFChars(serviceNameObj, serviceNameCStr);
-    ::android::hardware::hidl_string serviceNameHStr;
-    serviceNameHStr.setToExternal(serviceName.c_str(), serviceName.size());
 
-    LOG(INFO) << "Looking for service "
-              << ifaceName
-              << "/"
-              << serviceName;
-
-    Return<IServiceManager::Transport> transportRet =
-            manager->getTransport(ifaceNameHStr, serviceNameHStr);
-
-    if (!transportRet.isOk()) {
-        signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
-        return NULL;
-    }
-
-    IServiceManager::Transport transport = transportRet;
-
-#ifdef __ANDROID_TREBLE__
-#ifdef __ANDROID_DEBUGGABLE__
-    const char* testingOverride = std::getenv("TREBLE_TESTING_OVERRIDE");
-    const bool vintfLegacy = (transport == IServiceManager::Transport::EMPTY)
-            && testingOverride && !strcmp(testingOverride, "true");
-#else // __ANDROID_TREBLE__ but not __ANDROID_DEBUGGABLE__
-    const bool vintfLegacy = false;
-#endif // __ANDROID_DEBUGGABLE__
-#else // not __ANDROID_TREBLE__
-    const bool vintfLegacy = (transport == IServiceManager::Transport::EMPTY);
-#endif // __ANDROID_TREBLE__";
-
-    if (transport != IServiceManager::Transport::HWBINDER && !vintfLegacy) {
-        LOG(ERROR) << "service " << ifaceName << " declares transport method "
-                   << toString(transport) << " but framework expects hwbinder.";
-        signalExceptionForError(env, NAME_NOT_FOUND, true /* canThrowRemoteException */);
-        return NULL;
-    }
-
-    Return<sp<hidl::base::V1_0::IBase>> ret = manager->get(ifaceNameHStr, serviceNameHStr);
-
-    if (!ret.isOk()) {
-        signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
-        return NULL;
-    }
-
+    // TODO(b/67981006): true /* retry */
+    sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, false /* retry */, false /* getStub */); 
     sp<hardware::IBinder> service = hardware::toBinder<hidl::base::V1_0::IBase>(ret);
 
     if (service == NULL) {
diff --git a/core/jni/android_text_AndroidBidi.cpp b/core/jni/android_text_AndroidBidi.cpp
deleted file mode 100644
index f72f0f0..0000000
--- a/core/jni/android_text_AndroidBidi.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-/* //device/libs/android_runtime/android_text_AndroidBidi.cpp
-**
-** Copyright 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.
-*/
-
-#define LOG_TAG "AndroidUnicode"
-
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-#include "utils/misc.h"
-#include "utils/Log.h"
-#include "unicode/ubidi.h"
-#include <minikin/Emoji.h>
-
-namespace android {
-
-static jint runBidi(JNIEnv* env, jobject obj, jint dir, jcharArray chsArray,
-                    jbyteArray infoArray, jint n, jboolean haveInfo)
-{
-    // Parameters are checked on java side
-    // Failures from GetXXXArrayElements indicate a serious out-of-memory condition
-    // that we don't bother to report, we're probably dead anyway.
-    jint result = 0;
-    jchar* chs = env->GetCharArrayElements(chsArray, NULL);
-    if (chs != NULL) {
-        jbyte* info = env->GetByteArrayElements(infoArray, NULL);
-        if (info != NULL) {
-            UErrorCode status = U_ZERO_ERROR;
-            UBiDi* bidi = ubidi_openSized(n, 0, &status);
-            // Set callbacks to override bidi classes of new emoji
-            ubidi_setClassCallback(
-                    bidi, minikin::emojiBidiOverride, nullptr, nullptr, nullptr, &status);
-            ubidi_setPara(bidi, reinterpret_cast<const UChar*>(chs), n, dir, NULL, &status);
-            if (U_SUCCESS(status)) {
-                for (int i = 0; i < n; ++i) {
-                  info[i] = ubidi_getLevelAt(bidi, i);
-                }
-                result = ubidi_getParaLevel(bidi);
-            } else {
-                jniThrowException(env, "java/lang/RuntimeException", NULL);
-            }
-            ubidi_close(bidi);
-
-            env->ReleaseByteArrayElements(infoArray, info, 0);
-        }
-        env->ReleaseCharArrayElements(chsArray, chs, JNI_ABORT);
-    }
-    return result;
-}
-
-static const JNINativeMethod gMethods[] = {
-        { "runBidi", "(I[C[BIZ)I", (void*) runBidi }
-};
-
-int register_android_text_AndroidBidi(JNIEnv* env)
-{
-    return RegisterMethodsOrDie(env, "android/text/AndroidBidi", gMethods, NELEM(gMethods));
-}
-
-}
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 33c8304..dec6c02 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -219,7 +219,7 @@
         strcpy(cmdline, "unknown");
 
         sprintf(proc_path, "/proc/%d/cmdline", pid);
-        fd = open(proc_path, O_RDONLY);
+        fd = open(proc_path, O_RDONLY | O_CLOEXEC);
         if (fd >= 0) {
             int rc = read(fd, cmdline, sizeof(cmdline)-1);
             cmdline[rc] = 0;
@@ -555,7 +555,7 @@
         return false;
     }
 
-    int fd = open(text, O_WRONLY);
+    int fd = open(text, O_WRONLY | O_CLOEXEC);
     if (fd >= 0) {
         sprintf(text, "%" PRId32, pid);
         write(fd, text, strlen(text));
@@ -603,7 +603,7 @@
 
 static jlong getFreeMemoryImpl(const char* const sums[], const size_t sumsLen[], size_t num)
 {
-    int fd = open("/proc/meminfo", O_RDONLY);
+    int fd = open("/proc/meminfo", O_RDONLY | O_CLOEXEC);
 
     if (fd < 0) {
         ALOGW("Unable to open /proc/meminfo");
@@ -716,7 +716,7 @@
         sizesArray[i] = 0;
     }
 
-    int fd = open(file.string(), O_RDONLY);
+    int fd = open(file.string(), O_RDONLY | O_CLOEXEC);
 
     if (fd >= 0) {
         const size_t BUFFER_SIZE = 2048;
@@ -1023,7 +1023,7 @@
         jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
         return JNI_FALSE;
     }
-    int fd = open(file8, O_RDONLY);
+    int fd = open(file8, O_RDONLY | O_CLOEXEC);
 
     if (fd < 0) {
         if (kDebugProc) {
@@ -1157,7 +1157,7 @@
         char data[PATH_MAX];
         snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
 
-        int fd = open(path, O_RDONLY);
+        int fd = open(path, O_RDONLY | O_CLOEXEC);
         if (fd < 0) {
             continue;
         }
diff --git a/core/proto/android/app/jobparameters.proto b/core/proto/android/app/jobparameters.proto
new file mode 100644
index 0000000..4f6a2a2
--- /dev/null
+++ b/core/proto/android/app/jobparameters.proto
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package android.app;
+
+/**
+ * An android.app.JobParameters object.
+ */
+message JobParametersProto {
+    enum CancelReason {
+        REASON_CANCELLED = 0;
+        REASON_CONSTRAINTS_NOT_SATISFIED = 1;
+        REASON_PREEMPT = 2;
+        REASON_TIMEOUT = 3;
+        REASON_DEVICE_IDLE = 4;
+    }
+}
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index 38879c0..c33c0a0 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -19,6 +19,8 @@
 
 package android.os;
 
+import "frameworks/base/core/proto/android/app/jobparameters.proto";
+import "frameworks/base/core/proto/android/os/powermanager.proto";
 import "frameworks/base/core/proto/android/telephony/signalstrength.proto";
 
 message BatteryStatsProto {
@@ -356,11 +358,12 @@
   optional PowerUseSummary power_use_summary = 18;
 
   message ResourcePowerManager {
+    // Either StateName or StateName.VoterName.
     optional string name = 1;
     optional TimerProto total = 2;
     optional TimerProto screen_off = 3;
   }
-  optional ResourcePowerManager resource_power_manager = 19;
+  repeated ResourcePowerManager resource_power_manager = 19;
 
   message ScreenBrightness {
     enum Name {
@@ -436,12 +439,332 @@
 }
 
 message TimerProto {
+  // This may be an apportioned time.
   optional int64 duration_ms = 1;
   optional int64 count = 2;
+  // The max duration if it is being tracked. Not all Timer subclasses
+  // track the max duration.
+  optional int64 max_duration_ms = 3;
+  // The current time the timer has been active, if it is being tracked.
+  // Not all Timer subclasses track the current duration.
+  optional int64 current_duration_ms = 4;
+  // The total cumulative duration (i.e. sum of past durations) that this timer
+  // has been on since reset. This may differ from duration_ms since, depending
+  // on the Timer, getTotalTimeLocked may represent the total 'blamed' or
+  // 'pooled' time, rather than the actual time. By contrast, total_duration_ms
+  // always gives the actual total time. Not all Timer subclasses track the
+  // total duration.
+  optional int64 total_duration_ms = 5;
 }
 
 message UidProto {
   // Combination of app ID and user ID.
   optional int32 uid = 1;
-  repeated string package_names = 2;
+
+  // The statistics associated with a particular package.
+  message Package {
+    optional string name = 1;
+
+    message Service {
+      optional string name = 1;
+      // Time spent started.
+      optional int64 start_duration_ms = 2;
+      optional int32 start_count = 3;
+      optional int32 launch_count = 4;
+    }
+    repeated Service services = 2;
+  }
+  repeated Package packages = 2;
+
+  optional ControllerActivityProto bluetooth_controller = 3;
+  optional ControllerActivityProto modem_controller = 4;
+  optional ControllerActivityProto wifi_controller = 5;
+
+  // Bluetooth misc data.
+  message BluetoothMisc {
+    // Duration spent BLE scanning blamed on this App (i.e. apportioned to this
+    // app amongst all apps doing BLE scanning; see explanation of 'apportioned'
+    // in App's comment).
+    optional TimerProto apportioned_ble_scan = 1;
+    // Background times aren't apportioned.
+    optional TimerProto background_ble_scan = 2;
+    // Running unoptimized BLE scanning, as defined by Bluetooth's
+    // AppScanStats.recordScanStart. As of May 2017, these are unfiltered,
+    // non-opportunistic, non-first-match scans. Durations are not
+    // pooled/apportioned.
+    optional TimerProto unoptimized_ble_scan = 3;
+    // Running unoptimized BLE scanning when app is in background. Durations are
+    // not pooled/apportioned.
+    optional TimerProto background_unoptimized_ble_scan = 4;
+    // Count of results returned by BLE scanning.
+    optional int32 ble_scan_result_count = 5;
+    // Count of results returned by BLE scans when app is in background.
+    // (Included in ble_scan_result_count.)
+    optional int32 background_ble_scan_result_count = 6;
+  }
+  optional BluetoothMisc bluetooth_misc = 6;
+
+  message Cpu {
+    // Total CPU time with processes executing in userspace. Summed up across
+    // multiple cores.
+    optional int64 user_duration_ms = 1;
+    // Total CPU time with processes executing kernel syscalls. Summed up across
+    // multiple cores.
+    optional int64 system_duration_ms = 2;
+
+    // CPU time broken down by CPU frequency (go/cpu-battery-metrics).
+    //
+    // These are real CPU time measurement from the kernel, so their sum can
+    // be different from the sum of user_duration_millis and
+    // system_duration_millis, which are just approximations. Data is not
+    // tracked when device is charging.
+    message ByFrequency {
+      // Index of the frequency in system.cpu_frequency. It starts from 1, to
+      // make it easier to analyze.
+      optional int32 frequency_index = 1;
+      // CPU time in milliseconds.
+      optional int64 total_duration_ms = 2;
+      // Screen-off CPU time in milliseconds.
+      optional int64 screen_off_duration_ms = 3;
+    }
+    repeated ByFrequency by_frequency = 3;
+  }
+  optional Cpu cpu = 7;
+
+  // Duration is pooled/apportioned.
+  optional TimerProto audio = 8;
+  // Duration is pooled/apportioned.
+  optional TimerProto camera = 9;
+  // Duration is pooled/apportioned.
+  optional TimerProto flashlight = 10;
+  // Duration is not pooled/apportioned.
+  optional TimerProto foreground_activity = 11;
+  // Duration is not pooled/apportioned.
+  optional TimerProto foreground_service = 12;
+  // Duration is not pooled/apportioned.
+  optional TimerProto vibrator = 13;
+  // Duration is pooled/apportioned.
+  optional TimerProto video = 14;
+
+  message Job {
+    optional string name = 1;
+    // Job times aren't apportioned.
+    optional TimerProto total = 2;
+    optional TimerProto background = 3;
+  }
+  repeated Job jobs = 15;
+
+  message JobCompletion {
+    // Job name.
+    optional string name = 1;
+
+    message ReasonCount {
+      optional android.app.JobParametersProto.CancelReason name = 1;
+      optional int32 count = 2;
+    }
+    repeated ReasonCount reason_count = 2;
+  };
+  repeated JobCompletion job_completion = 16;
+
+  message Network {
+    // Mobile data traffic (total, background + foreground).
+    optional int64 mobile_bytes_rx = 1;
+    optional int64 mobile_bytes_tx = 2;
+    // Wifi data traffic (total, background + foreground).
+    optional int64 wifi_bytes_rx = 3;
+    optional int64 wifi_bytes_tx = 4;
+    // Bluetooth data traffic (total, background + foreground).
+    optional int64 bt_bytes_rx = 5;
+    optional int64 bt_bytes_tx = 6;
+    // In packets (total, background + foreground).
+    optional int64 mobile_packets_rx = 7;
+    optional int64 mobile_packets_tx = 8;
+    optional int64 wifi_packets_rx = 9;
+    optional int64 wifi_packets_tx = 10;
+    // Radio active duration.
+    optional int64 mobile_active_duration_ms = 11;
+    optional int32 mobile_active_count = 12;
+    // Number of times the app woke up the mobile radio.
+    optional int32 mobile_wakeup_count = 13;
+    // Number of times the app woke up the wifi radio.
+    optional int32 wifi_wakeup_count = 14;
+    // Mobile data traffic in the background only, included in total above.
+    optional int64 mobile_bytes_bg_rx = 15;
+    optional int64 mobile_bytes_bg_tx = 16;
+    // Wifi data traffic in the background only, included in total above.
+    optional int64 wifi_bytes_bg_rx = 17;
+    optional int64 wifi_bytes_bg_tx = 18;
+    // In packets (background only, included in total packets above).
+    optional int64 mobile_packets_bg_rx = 19;
+    optional int64 mobile_packets_bg_tx = 20;
+    optional int64 wifi_packets_bg_rx = 21;
+    optional int64 wifi_packets_bg_tx = 22;
+  };
+  optional Network network = 17;
+
+  // TODO: combine System and App messages?
+  message PowerUseItem {
+    // Estimated power use in mAh.
+    optional double computed_power_mah = 1;
+    // Starting in Oreo, Battery Settings has two modes to display the battery
+    // info. The first is "app usage list". In this mode, items with should_hide
+    // enabled are hidden.
+    optional bool should_hide = 2;
+    // Smeared power from screen usage. Screen usage power is split and smeared
+    // among apps, based on activity time.
+    optional double screen_power_mah = 3;
+    // Smeared power using proportional method. Power usage from hidden sippers
+    // is smeared to all apps proportionally (except for screen usage).
+    optional double proportional_smear_mah = 4;
+  };
+  optional PowerUseItem power_use_item = 18;
+
+  // Durations are not pooled/apportioned.
+  message Process {
+    optional string name = 1;
+    // Time spent executing in user code.
+    optional int64 user_duration_ms = 2;
+    // Time spent executing in kernel code.
+    optional int64 system_duration_ms = 3;
+    // Time the process was running in the foreground.
+    optional int64 foreground_duration_ms = 4;
+    // Number of times the process has been started.
+    optional int32 start_count = 5;
+    // Number of times the process has had an ANR.
+    optional int32 anr_count = 6;
+    // Number of times the process has crashed.
+    optional int32 crash_count = 7;
+  };
+  repeated Process process = 19;
+
+  message StateTime {
+    // All of these (non-deprecated) states are mutually exclusive and can be
+    // added together to find the total time a uid has had any processes running
+    // at all.
+
+    // In approximate order or priority (top being what the framework considers
+    // most important and is thus least likely to kill when resources are
+    // needed:
+    // top > foreground service > top sleeping > foreground > background > cache
+    enum State {
+      // Time this uid has any processes in the top state (or above such as
+      // persistent).
+      PROCESS_STATE_TOP = 0;
+      // Time this uid has any process with a started out bound foreground
+      // service, but none in the "top" state.
+      PROCESS_STATE_FOREGROUND_SERVICE = 1;
+      // Time this uid has any process that is top while the device is sleeping,
+      // but none in the "foreground service" or better state. Sleeping is
+      // mostly screen off, but also includes the time when the screen is on but
+      // the device has not yet been unlocked.
+      PROCESS_STATE_TOP_SLEEPING = 2;
+      // Time this uid has any process in an active foreground state, but none
+      // in the "top sleeping" or better state.
+      PROCESS_STATE_FOREGROUND = 3;
+      // Time this uid has any process in an active background state, but none
+      // in the "foreground" or better state.
+      PROCESS_STATE_BACKGROUND = 4;
+      // Time this uid has any processes that are sitting around cached, not in
+      // one of the other active states.
+      PROCESS_STATE_CACHED = 5;
+    }
+    optional State state = 1;
+    optional int64 duration_ms = 2;
+  }
+  repeated StateTime states = 20;
+
+  message Sensor {
+    optional int32 id = 1;
+    optional TimerProto apportioned = 2;
+    // Background times aren't apportioned.
+    optional TimerProto background = 3;
+  }
+  repeated Sensor sensors = 21;
+
+  message Sync {
+    optional string name = 1;
+    // Sync times aren't apportioned.
+    optional TimerProto total = 2;
+    optional TimerProto background = 3;
+  }
+  repeated Sync syncs = 22;
+
+  message UserActivity {
+    optional android.os.PowerManagerProto.UserActivityEvent name = 1;
+    optional int32 count = 2;
+  };
+  repeated UserActivity user_activity = 23;
+
+  // Aggregated wakelock data for an app overall, across all of its wakelocks.
+  // The Wakelock message holds data about each *individual* wakelock, but it
+  // cannot be used to ascertain the aggregated time the app spent holding
+  // wakelocks, since merely summing Wakelock data will either underestimate (in
+  // the case of wakelock.partial.duration_ms) or overestimate (in the case of
+  // wakelock.partial.total_duration_ms) the total time, due to overlapping
+  // wakelocks. AggregatedWakelock, on the other hand, holds overall per-app
+  // wakelock data.
+  message AggregatedWakelock {
+    // The total duration that the app spent holding partial wakelocks.
+    // It includes both foreground + background use.
+    optional int64 partial_duration_ms = 1;
+    // The total duration that the app spent holding partial wakelocks while the
+    // app was in the background. Subtracting from partial_duration_ms will
+    // yield foreground usage.
+    optional int64 background_partial_duration_ms = 2;
+  };
+  optional AggregatedWakelock aggregated_wakelock = 24;
+
+  message Wakelock {
+    optional string name = 1;
+
+    // Full wakelocks keep the screen on. Based on
+    // PowerManager.SCREEN_BRIGHT_WAKE_LOCK (deprecated in API 13) and
+    // PowerManager.SCREEN_DIM_WAKE_LOCK (deprecated in API 17). Current, max,
+    // and total durations are not tracked for full wakelocks.
+    optional TimerProto full = 2;
+
+    // Partial wakelocks ensure the CPU is running while allowing the screen
+    // to turn off. Based on PowerManager.PARTIAL_WAKE_LOCK.
+    // Partial wakelock metrics are only recorded when the device is unplugged
+    // *and* the screen is off. Current, max, and total durations are tracked
+    // for partial wakelocks.
+    optional TimerProto partial = 3;
+
+    // These fields are for tracking partial wakelocks (see above), but only
+    // the time the wakelock was held while the app was in a background state.
+    // Since all background tracking is 'actual', not 'apportioned',
+    // background_partial.duration_ms is identical to
+    // background_partial.total_duration_ms.
+    optional TimerProto background_partial = 4;
+
+    // Window wakelocks keep the screen on. Current, max, and total durations
+    // are not tracked for window wakelocks.
+    optional TimerProto window = 5;
+  };
+  repeated Wakelock wakelocks = 25;
+
+  message WakeupAlarm {
+    // Wakeup alarm name.
+    optional string name = 1;
+    // Only includes counts when screen-off (& on battery).
+    optional int32 count = 2;
+  }
+  repeated WakeupAlarm wakeup_alarm = 26;
+
+  message Wifi {
+    // Duration holding Wifi-lock. This time is apportioned.
+    optional int64 full_wifi_lock_duration_ms = 1;
+    // Duration running Wifi. This time is apportioned.
+    optional int64 running_duration_ms = 2;
+    // Duration performing Wifi-scan blamed on this App (i.e. apportioned to
+    // this app amongst all apps doing Wifi-scanning; see explanation of
+    // 'apportioned' in App's comment).
+    optional TimerProto apportioned_scan = 3;
+    // Scans performed when app is in background. (Included in
+    // apportioned_scan). This value is not apportioned. Subtracting
+    // background_scan.total_duration_ms from apportioned_scan.total_duration_ms
+    // will yield foreground usage.
+    optional TimerProto background_scan = 4;
+  };
+  optional Wifi wifi = 27;
 }
diff --git a/core/proto/android/os/powermanager.proto b/core/proto/android/os/powermanager.proto
new file mode 100644
index 0000000..3bfe5d6
--- /dev/null
+++ b/core/proto/android/os/powermanager.proto
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package android.os;
+
+message PowerManagerProto {
+    /* User activity events in PowerManager.java. */
+    enum UserActivityEvent {
+        // Unspecified event type.
+        USER_ACTIVITY_EVENT_OTHER = 0;
+        // Button or key pressed or released.
+        USER_ACTIVITY_EVENT_BUTTON = 1;
+        // Touch down, move or up.
+        USER_ACTIVITY_EVENT_TOUCH = 2;
+        // Accessibility taking action on behalf of user.
+        USER_ACTIVITY_EVENT_ACCESSIBILITY = 3;
+    }
+}
diff --git a/core/res/res/layout/floating_popup_menu_button.xml b/core/res/res/layout/floating_popup_menu_button.xml
index 6cbe8c8..c419e46 100644
--- a/core/res/res/layout/floating_popup_menu_button.xml
+++ b/core/res/res/layout/floating_popup_menu_button.xml
@@ -53,7 +53,6 @@
         android:ellipsize="end"
         android:fontFamily="sans-serif-medium"
         android:textSize="@dimen/floating_toolbar_text_size"
-        android:textAllCaps="true"
         android:textColor="?attr/floatingToolbarForegroundColor"
         android:background="@null"
         android:focusable="false"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dec5fd9..571aad9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3128,4 +3128,7 @@
     <!-- Class names of device specific services inheriting com.android.server.SystemService. The
          classes are instantiated in the order of the array. -->
     <string-array translatable="false" name="config_deviceSpecificSystemServices"></string-array>
+
+    <!-- Component name of media projection permission dialog -->
+    <string name="config_mediaProjectionPermissionDialogComponent" translateable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 93c02b0..78a8e2a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4346,8 +4346,6 @@
     <string name="lock_to_app_toast">To unpin this screen, touch &amp; hold Back and Overview
         buttons</string>
 
-    <!-- Notify user that they are locked in lock-to-app mode -->
-    <string name="lock_to_app_toast_locked">This app can\'t be unpinned</string>
     <!-- Starting lock-to-app indication. -->
     <string name="lock_to_app_start">Screen pinned</string>
     <!-- Exting lock-to-app indication. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a12187e..1627a0e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -754,7 +754,6 @@
   <java-symbol type="string" name="last_month" />
   <java-symbol type="string" name="launchBrowserDefault" />
   <java-symbol type="string" name="lock_to_app_toast" />
-  <java-symbol type="string" name="lock_to_app_toast_locked" />
   <java-symbol type="string" name="lock_to_app_start" />
   <java-symbol type="string" name="lock_to_app_exit" />
   <java-symbol type="string" name="lock_to_app_unlock_pin" />
@@ -3125,4 +3124,7 @@
   <java-symbol type="string" name="shortcut_restore_not_supported" />
   <java-symbol type="string" name="shortcut_restore_signature_mismatch" />
   <java-symbol type="string" name="shortcut_restore_unknown_issue" />
+
+  <!-- From media projection -->
+  <java-symbol type="string" name="config_mediaProjectionPermissionDialogComponent" />
 </resources>
diff --git a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf b/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
deleted file mode 100644
index 1bad6fe..0000000
--- a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
+++ /dev/null
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx b/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
deleted file mode 100644
index 0cf0f79..0000000
--- a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
+++ /dev/null
@@ -1,207 +0,0 @@
-<?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.
--->
-<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
-
-  <GlyphOrder>
-    <GlyphID id="0" name=".notdef"/>
-    <GlyphID id="1" name="0em"/>
-    <GlyphID id="2" name="1em"/>
-    <GlyphID id="3" name="3em"/>
-    <GlyphID id="4" name="5em"/>
-    <GlyphID id="5" name="7em"/>
-    <GlyphID id="6" name="10em"/>
-    <GlyphID id="7" name="50em"/>
-    <GlyphID id="8" name="100em"/>
-  </GlyphOrder>
-
-  <head>
-    <tableVersion value="1.0"/>
-    <fontRevision value="1.0"/>
-    <checkSumAdjustment value="0x640cdb2f"/>
-    <magicNumber value="0x5f0f3cf5"/>
-    <flags value="00000000 00000011"/>
-    <unitsPerEm value="100"/>
-    <created value="Fri Mar 17 07:26:00 2017"/>
-    <macStyle value="00000000 00000000"/>
-    <lowestRecPPEM value="7"/>
-    <fontDirectionHint value="2"/>
-    <glyphDataFormat value="0"/>
-  </head>
-
-  <hhea>
-    <tableVersion value="0x00010000"/>
-    <ascent value="1000"/>
-    <descent value="-200"/>
-    <lineGap value="0"/>
-    <caretSlopeRise value="1"/>
-    <caretSlopeRun value="0"/>
-    <caretOffset value="0"/>
-    <reserved0 value="0"/>
-    <reserved1 value="0"/>
-    <reserved2 value="0"/>
-    <reserved3 value="0"/>
-    <metricDataFormat value="0"/>
-  </hhea>
-
-  <maxp>
-    <tableVersion value="0x10000"/>
-    <maxZones value="0"/>
-    <maxTwilightPoints value="0"/>
-    <maxStorage value="0"/>
-    <maxFunctionDefs value="0"/>
-    <maxInstructionDefs value="0"/>
-    <maxStackElements value="0"/>
-    <maxSizeOfInstructions value="0"/>
-    <maxComponentElements value="0"/>
-  </maxp>
-
-  <OS_2>
-    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
-         will be recalculated by the compiler -->
-    <version value="3"/>
-    <xAvgCharWidth value="594"/>
-    <usWeightClass value="400"/>
-    <usWidthClass value="5"/>
-    <fsType value="00000000 00001000"/>
-    <ySubscriptXSize value="650"/>
-    <ySubscriptYSize value="600"/>
-    <ySubscriptXOffset value="0"/>
-    <ySubscriptYOffset value="75"/>
-    <ySuperscriptXSize value="650"/>
-    <ySuperscriptYSize value="600"/>
-    <ySuperscriptXOffset value="0"/>
-    <ySuperscriptYOffset value="350"/>
-    <yStrikeoutSize value="50"/>
-    <yStrikeoutPosition value="300"/>
-    <sFamilyClass value="0"/>
-    <panose>
-      <bFamilyType value="0"/>
-      <bSerifStyle value="0"/>
-      <bWeight value="5"/>
-      <bProportion value="0"/>
-      <bContrast value="0"/>
-      <bStrokeVariation value="0"/>
-      <bArmStyle value="0"/>
-      <bLetterForm value="0"/>
-      <bMidline value="0"/>
-      <bXHeight value="0"/>
-    </panose>
-    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
-    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
-    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
-    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
-    <achVendID value="UKWN"/>
-    <fsSelection value="00000000 01000000"/>
-    <usFirstCharIndex value="32"/>
-    <usLastCharIndex value="122"/>
-    <sTypoAscender value="800"/>
-    <sTypoDescender value="-200"/>
-    <sTypoLineGap value="200"/>
-    <usWinAscent value="1000"/>
-    <usWinDescent value="200"/>
-    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
-    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
-    <sxHeight value="500"/>
-    <sCapHeight value="700"/>
-    <usDefaultChar value="0"/>
-    <usBreakChar value="32"/>
-    <usMaxContext value="0"/>
-  </OS_2>
-
-  <hmtx>
-    <mtx name=".notdef" width="50" lsb="0"/>
-    <mtx name="0em" width="0" lsb="0"/>
-    <mtx name="1em" width="100" lsb="0"/>
-    <mtx name="3em" width="300" lsb="0"/>
-    <mtx name="5em" width="500" lsb="0"/>
-    <mtx name="7em" width="700" lsb="0"/>
-    <mtx name="10em" width="1000" lsb="0"/>
-    <mtx name="50em" width="5000" lsb="0"/>
-    <mtx name="100em" width="10000" lsb="0"/>
-  </hmtx>
-
-  <cmap>
-    <tableVersion version="0"/>
-    <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
-      <map code="0x0020" name="10em" />
-      <map code="0x002e" name="10em" />  <!-- . -->
-      <map code="0x0043" name="100em" />  <!-- C -->
-      <map code="0x0049" name="1em" />  <!-- I -->
-      <map code="0x004c" name="50em" />  <!-- L -->
-      <map code="0x0056" name="5em" />  <!-- V -->
-      <map code="0x0058" name="10em" />  <!-- X -->
-      <map code="0x005f" name="0em" /> <!-- _ -->
-      <map code="0xfffd" name="7em" /> <!-- REPLACEMENT CHAR -->
-      <map code="0x10331" name="10em" />
-    </cmap_format_12>
-  </cmap>
-
-  <loca>
-    <!-- The 'loca' table will be calculated by the compiler -->
-  </loca>
-
-  <glyf>
-    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="0em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="5em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="7em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="10em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="50em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="100em" xMin="0" yMin="0" xMax="0" yMax="0" />
-  </glyf>
-
-  <name>
-    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
-      Font for StaticLayoutLineBreakingTest
-    </namerecord>
-    <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
-      Regular
-    </namerecord>
-    <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
-      Font for StaticLayoutLineBreakingTest
-    </namerecord>
-    <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
-      SampleFont-Regular
-    </namerecord>
-    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
-      Sample Font
-    </namerecord>
-    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
-      Regular
-    </namerecord>
-    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
-      Sample Font
-    </namerecord>
-    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
-      SampleFont-Regular
-    </namerecord>
-  </name>
-
-  <post>
-    <formatType value="3.0"/>
-    <italicAngle value="0.0"/>
-    <underlinePosition value="-75"/>
-    <underlineThickness value="50"/>
-    <isFixedPitch value="0"/>
-    <minMemType42 value="0"/>
-    <maxMemType42 value="0"/>
-    <minMemType1 value="0"/>
-    <maxMemType1 value="0"/>
-  </post>
-
-</ttFont>
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 6c32590..ebe0527 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -38,7 +38,6 @@
 import java.util.Set;
 
 /** Tests that ensure appropriate settings are backed up. */
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SettingsBackupTest {
@@ -181,6 +180,7 @@
                     Settings.Global.DNS_RESOLVER_MIN_SAMPLES,
                     Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
                     Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
+                    Settings.Global.DNS_TLS_DISABLED,
                     Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY,
                     Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE,
                     Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
diff --git a/core/tests/coretests/src/android/service/settings/suggestions/MockSuggestionService.java b/core/tests/coretests/src/android/service/settings/suggestions/MockSuggestionService.java
index c158b9f..ab541a1 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/MockSuggestionService.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/MockSuggestionService.java
@@ -16,11 +16,23 @@
 
 package android.service.settings.suggestions;
 
+import android.support.annotation.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.List;
 
 public class MockSuggestionService extends SuggestionService {
 
+    @VisibleForTesting
+    static boolean sOnSuggestionLaunchedCalled;
+    @VisibleForTesting
+    static boolean sOnSuggestionDismissedCalled;
+
+    public static void reset() {
+        sOnSuggestionLaunchedCalled = false;
+        sOnSuggestionDismissedCalled = false;
+    }
+
     @Override
     public List<Suggestion> onGetSuggestions() {
         final List<Suggestion> data = new ArrayList<>();
@@ -34,5 +46,11 @@
 
     @Override
     public void onSuggestionDismissed(Suggestion suggestion) {
+        sOnSuggestionDismissedCalled = true;
+    }
+
+    @Override
+    public void onSuggestionLaunched(Suggestion suggestion) {
+        sOnSuggestionLaunchedCalled = true;
     }
 }
diff --git a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java
index bc88231..dca8c09 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java
@@ -16,12 +16,17 @@
 
 package android.service.settings.suggestions;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ServiceTestRule;
 import android.support.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -45,9 +50,36 @@
                 MockSuggestionService.class);
     }
 
+    @After
+    public void tearUp() {
+        MockSuggestionService.reset();
+    }
+
     @Test
     public void canStartService() throws TimeoutException {
         mServiceTestRule.startService(mMockServiceIntent);
         // Do nothing after starting service.
     }
+
+    @Test
+    public void dismissSuggestion_shouldCallImplementation()
+            throws TimeoutException, RemoteException {
+        MockSuggestionService service = new MockSuggestionService();
+        IBinder binder = mServiceTestRule.bindService(mMockServiceIntent);
+        ISuggestionService serviceBinder = ISuggestionService.Stub.asInterface(binder);
+        serviceBinder.dismissSuggestion(null);
+
+        assertThat(service.sOnSuggestionDismissedCalled).isTrue();
+    }
+
+    @Test
+    public void launchSuggestion_shouldCallImplementation()
+            throws TimeoutException, RemoteException {
+        MockSuggestionService service = new MockSuggestionService();
+        IBinder binder = mServiceTestRule.bindService(mMockServiceIntent);
+        ISuggestionService serviceBinder = ISuggestionService.Stub.asInterface(binder);
+        serviceBinder.launchSuggestion(null);
+
+        assertThat(service.sOnSuggestionLaunchedCalled).isTrue();
+    }
 }
diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
index d16cce8..8092203 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
@@ -96,7 +96,7 @@
         int n = chs.length;
         byte[] chInfo = new byte[n];
 
-        int resultDir = AndroidBidi.bidi(dir, chs, chInfo, n, false);
+        int resultDir = AndroidBidi.bidi(dir, chs, chInfo);
 
         {
             StringBuilder sb = new StringBuilder("info:");
diff --git a/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java b/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java
deleted file mode 100644
index f7dbafa..0000000
--- a/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Copyright (C) 2012 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 static org.junit.Assert.assertEquals;
-
-import android.content.Context;;
-import android.graphics.Typeface;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.text.Layout.Alignment;
-import android.text.style.MetricAffectingSpan;
-import android.util.Log;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class StaticLayoutLineBreakingTest {
-    // Span test are currently not supported because text measurement uses the MeasuredText
-    // internal mWorkPaint instead of the provided MockTestPaint.
-    private static final boolean SPAN_TESTS_SUPPORTED = false;
-    private static final boolean DEBUG = false;
-
-    private static final float SPACE_MULTI = 1.0f;
-    private static final float SPACE_ADD = 0.0f;
-    private static final int WIDTH = 100;
-    private static final Alignment ALIGN = Alignment.ALIGN_LEFT;
-
-    private static final char SURR_FIRST = '\uD800';
-    private static final char SURR_SECOND = '\uDF31';
-
-    private static final int[] NO_BREAK = new int[] {};
-
-    private static final TextPaint sTextPaint = new TextPaint();
-
-    static {
-        // The test font has following coverage and width.
-        // U+0020: 10em
-        // U+002E (.): 10em
-        // U+0043 (C): 100em
-        // U+0049 (I): 1em
-        // U+004C (L): 50em
-        // U+0056 (V): 5em
-        // U+0058 (X): 10em
-        // U+005F (_): 0em
-        // U+FFFD (invalid surrogate will be replaced to this): 7em
-        // U+10331 (\uD800\uDF31): 10em
-        Context context = InstrumentationRegistry.getTargetContext();
-        sTextPaint.setTypeface(Typeface.createFromAsset(context.getAssets(),
-                  "fonts/StaticLayoutLineBreakingTestFont.ttf"));
-        sTextPaint.setTextSize(1.0f);  // Make 1em == 1px.
-    }
-
-    private static StaticLayout getStaticLayout(CharSequence source, int width) {
-        return new StaticLayout(source, sTextPaint, width, ALIGN, SPACE_MULTI, SPACE_ADD, false);
-    }
-
-    private static int[] getBreaks(CharSequence source) {
-        return getBreaks(source, WIDTH);
-    }
-
-    private static int[] getBreaks(CharSequence source, int width) {
-        StaticLayout staticLayout = getStaticLayout(source, width);
-
-        int[] breaks = new int[staticLayout.getLineCount() - 1];
-        for (int line = 0; line < breaks.length; line++) {
-            breaks[line] = staticLayout.getLineEnd(line);
-        }
-        return breaks;
-    }
-
-    private static void debugLayout(CharSequence source, StaticLayout staticLayout) {
-        if (DEBUG) {
-            int count = staticLayout.getLineCount();
-            Log.i("SLLBTest", "\"" + source.toString() + "\": "
-                    + count + " lines");
-            for (int line = 0; line < count; line++) {
-                int lineStart = staticLayout.getLineStart(line);
-                int lineEnd = staticLayout.getLineEnd(line);
-                Log.i("SLLBTest", "Line " + line + " [" + lineStart + ".."
-                        + lineEnd + "]\t" + source.subSequence(lineStart, lineEnd));
-            }
-        }
-    }
-
-    private static void layout(CharSequence source, int[] breaks) {
-        layout(source, breaks, WIDTH);
-    }
-
-    private static void layout(CharSequence source, int[] breaks, int width) {
-        StaticLayout staticLayout = getStaticLayout(source, width);
-
-        debugLayout(source, staticLayout);
-
-        int lineCount = breaks.length + 1;
-        assertEquals("Number of lines", lineCount, staticLayout.getLineCount());
-
-        for (int line = 0; line < lineCount; line++) {
-            int lineStart = staticLayout.getLineStart(line);
-            int lineEnd = staticLayout.getLineEnd(line);
-
-            if (line == 0) {
-                assertEquals("Line start for first line", 0, lineStart);
-            } else {
-                assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
-            }
-
-            if (line == lineCount - 1) {
-                assertEquals("Line end for last line", source.length(), lineEnd);
-            } else {
-                assertEquals("Line end for line " + line, breaks[line], lineEnd);
-            }
-        }
-    }
-
-    private static void layoutMaxLines(CharSequence source, int[] breaks, int maxLines) {
-        StaticLayout staticLayout = new StaticLayout(source, 0, source.length(), sTextPaint, WIDTH,
-                ALIGN, TextDirectionHeuristics.LTR, SPACE_MULTI, SPACE_ADD, false /* includePad */,
-                null, WIDTH, maxLines);
-
-        debugLayout(source, staticLayout);
-
-        final int lineCount = staticLayout.getLineCount();
-
-        for (int line = 0; line < lineCount; line++) {
-            int lineStart = staticLayout.getLineStart(line);
-            int lineEnd = staticLayout.getLineEnd(line);
-
-            if (line == 0) {
-                assertEquals("Line start for first line", 0, lineStart);
-            } else {
-                assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
-            }
-
-            if (line == lineCount - 1 && line != breaks.length - 1) {
-                assertEquals("Line end for last line", source.length(), lineEnd);
-            } else {
-                assertEquals("Line end for line " + line, breaks[line], lineEnd);
-            }
-        }
-    }
-
-    private static final int MAX_SPAN_COUNT = 10;
-    private static final int[] sSpanStarts = new int[MAX_SPAN_COUNT];
-    private static final int[] sSpanEnds = new int[MAX_SPAN_COUNT];
-
-    private static MetricAffectingSpan getMetricAffectingSpan() {
-        return new MetricAffectingSpan() {
-            @Override
-            public void updateDrawState(TextPaint tp) { /* empty */ }
-
-            @Override
-            public void updateMeasureState(TextPaint p) { /* empty */ }
-        };
-    }
-
-    /**
-     * Replaces the "<...>" blocks by spans, assuming non overlapping, correctly defined spans
-     * @param text
-     * @return A CharSequence with '<' '>' replaced by MetricAffectingSpan
-     */
-    private static CharSequence spanify(String text) {
-        int startIndex = text.indexOf('<');
-        if (startIndex < 0) return text;
-
-        int spanCount = 0;
-        do {
-            int endIndex = text.indexOf('>');
-            if (endIndex < 0) throw new IllegalArgumentException("Unbalanced span markers");
-
-            text = text.substring(0, startIndex) + text.substring(startIndex + 1, endIndex)
-                    + text.substring(endIndex + 1);
-
-            sSpanStarts[spanCount] = startIndex;
-            sSpanEnds[spanCount] = endIndex - 2;
-            spanCount++;
-
-            startIndex = text.indexOf('<');
-        } while (startIndex >= 0);
-
-        SpannableStringBuilder result = new SpannableStringBuilder(text);
-        for (int i = 0; i < spanCount; i++) {
-            result.setSpan(getMetricAffectingSpan(), sSpanStarts[i], sSpanEnds[i],
-                    Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-        }
-        return result;
-    }
-
-    @Test
-    public void testNoLineBreak() {
-        // Width lower than WIDTH
-        layout("", NO_BREAK);
-        layout("I", NO_BREAK);
-        layout("V", NO_BREAK);
-        layout("X", NO_BREAK);
-        layout("L", NO_BREAK);
-        layout("I VILI", NO_BREAK);
-        layout("XXXX", NO_BREAK);
-        layout("LXXXX", NO_BREAK);
-
-        // Width equal to WIDTH
-        layout("C", NO_BREAK);
-        layout("LL", NO_BREAK);
-        layout("L XXXX", NO_BREAK);
-        layout("XXXXXXXXXX", NO_BREAK);
-        layout("XXX XXXXXX", NO_BREAK);
-        layout("XXX XXXX X", NO_BREAK);
-        layout("XXX XXXXX ", NO_BREAK);
-        layout(" XXXXXXXX ", NO_BREAK);
-        layout("  XX  XXX ", NO_BREAK);
-        //      0123456789
-
-        // Width greater than WIDTH, but no break
-        layout("  XX  XXX  ", NO_BREAK);
-        layout("XX XXX XXX ", NO_BREAK);
-        layout("XX XXX XXX     ", NO_BREAK);
-        layout("XXXXXXXXXX     ", NO_BREAK);
-        //      01234567890
-    }
-
-    @Test
-    public void testOneLineBreak() {
-        //      01234567890
-        layout("XX XXX XXXX", new int[] {7});
-        layout("XX XXXX XXX", new int[] {8});
-        layout("XX XXXXX XX", new int[] {9});
-        layout("XX XXXXXX X", new int[] {10});
-        //      01234567890
-        layout("XXXXXXXXXXX", new int[] {10});
-        layout("XXXXXXXXX X", new int[] {10});
-        layout("XXXXXXXX XX", new int[] {9});
-        layout("XXXXXXX XXX", new int[] {8});
-        layout("XXXXXX XXXX", new int[] {7});
-        //      01234567890
-        layout("LL LL", new int[] {3});
-        layout("LLLL", new int[] {2});
-        layout("C C", new int[] {2});
-        layout("CC", new int[] {1});
-    }
-
-    @Test
-    public void testSpaceAtBreak() {
-        //      0123456789012
-        layout("XXXX XXXXX X", new int[] {11});
-        layout("XXXXXXXXXX X", new int[] {11});
-        layout("XXXXXXXXXV X", new int[] {11});
-        layout("C X", new int[] {2});
-    }
-
-    @Test
-    public void testMultipleSpacesAtBreak() {
-        //      0123456789012
-        layout("LXX XXXX", new int[] {4});
-        layout("LXX  XXXX", new int[] {5});
-        layout("LXX   XXXX", new int[] {6});
-        layout("LXX    XXXX", new int[] {7});
-        layout("LXX     XXXX", new int[] {8});
-    }
-
-    @Test
-    public void testZeroWidthCharacters() {
-        //      0123456789012345678901234
-        layout("X_X_X_X_X_X_X_X_X_X", NO_BREAK);
-        layout("___X_X_X_X_X_X_X_X_X_X___", NO_BREAK);
-        layout("C_X", new int[] {2});
-        layout("C__X", new int[] {3});
-    }
-
-    /**
-     * Note that when the text has spans, StaticLayout does not use the provided TextPaint to
-     * measure text runs anymore. This is probably a bug.
-     * To be able to use the fake sTextPaint and make this test pass, use mPaint instead of
-     * mWorkPaint in MeasuredText#addStyleRun
-     */
-    @Test
-    public void testWithSpans() {
-        if (!SPAN_TESTS_SUPPORTED) return;
-
-        layout(spanify("<012 456 89>"), NO_BREAK);
-        layout(spanify("012 <456> 89"), NO_BREAK);
-        layout(spanify("<012> <456>< 89>"), NO_BREAK);
-        layout(spanify("<012> <456> <89>"), NO_BREAK);
-
-        layout(spanify("<012> <456> <89>012"), new int[] {8});
-        layout(spanify("<012> <456> 89<012>"), new int[] {8});
-        layout(spanify("<012> <456> <89><012>"), new int[] {8});
-        layout(spanify("<012> <456> 89 <123>"), new int[] {11});
-        layout(spanify("<012> <456> 89< 123>"), new int[] {11});
-        layout(spanify("<012> <456> <89> <123>"), new int[] {11});
-        layout(spanify("012 456 89 <LXX> XX XX"), new int[] {11, 18});
-    }
-
-    /*
-     * Adding a span to the string should not change the layout, since the metrics are unchanged.
-     */
-    @Test
-    public void testWithOneSpan() {
-        if (!SPAN_TESTS_SUPPORTED) return;
-
-        String[] texts = new String[] { "0123", "012 456", "012 456 89 123", "012 45678 012",
-                "012 456 89012 456 89012", "0123456789012" };
-
-        MetricAffectingSpan metricAffectingSpan = getMetricAffectingSpan();
-
-        for (String text : texts) {
-            // Get the line breaks without any span
-            int[] breaks = getBreaks(text);
-
-            // Add spans on all possible offsets
-            for (int spanStart = 0; spanStart < text.length(); spanStart++) {
-                for (int spanEnd = spanStart; spanEnd < text.length(); spanEnd++) {
-                    SpannableStringBuilder ssb = new SpannableStringBuilder(text);
-                    ssb.setSpan(metricAffectingSpan, spanStart, spanEnd,
-                            Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                    layout(ssb, breaks);
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testWithTwoSpans() {
-        if (!SPAN_TESTS_SUPPORTED) return;
-
-        String[] texts = new String[] { "0123", "012 456", "012 456 89 123", "012 45678 012",
-                "012 456 89012 456 89012", "0123456789012" };
-
-        MetricAffectingSpan metricAffectingSpan1 = getMetricAffectingSpan();
-        MetricAffectingSpan metricAffectingSpan2 = getMetricAffectingSpan();
-
-        for (String text : texts) {
-            // Get the line breaks without any span
-            int[] breaks = getBreaks(text);
-
-            // Add spans on all possible offsets
-            for (int spanStart1 = 0; spanStart1 < text.length(); spanStart1++) {
-                for (int spanEnd1 = spanStart1; spanEnd1 < text.length(); spanEnd1++) {
-                    SpannableStringBuilder ssb = new SpannableStringBuilder(text);
-                    ssb.setSpan(metricAffectingSpan1, spanStart1, spanEnd1,
-                            Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-
-                    for (int spanStart2 = 0; spanStart2 < text.length(); spanStart2++) {
-                        for (int spanEnd2 = spanStart2; spanEnd2 < text.length(); spanEnd2++) {
-                            ssb.setSpan(metricAffectingSpan2, spanStart2, spanEnd2,
-                                    Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                            layout(ssb, breaks);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    public static String replace(String string, char c, char r) {
-        return string.replaceAll(String.valueOf(c), String.valueOf(r));
-    }
-
-    @Test
-    public void testWithSurrogate() {
-        layout("LX" + SURR_FIRST + SURR_SECOND, NO_BREAK);
-        layout("LXXXX" + SURR_FIRST + SURR_SECOND, NO_BREAK);
-        // LXXXXI (91) + SURR_FIRST + SURR_SECOND (10). Do not break in the middle point of
-        // surrogatge pair.
-        layout("LXXXXI" + SURR_FIRST + SURR_SECOND, new int[] {6});
-
-        // LXXXXI (91) + SURR_SECOND (replaced with REPLACEMENT CHARACTER. width is 7px) fits.
-        // Break just after invalid trailing surrogate.
-        layout("LXXXXI" + SURR_SECOND + SURR_FIRST, new int[] {7});
-
-        layout("C" + SURR_FIRST + SURR_SECOND, new int[] {1});
-    }
-
-    @Test
-    public void testNarrowWidth() {
-        int[] widths = new int[] { 0, 4, 10 };
-        String[] texts = new String[] { "", "X", " ", "XX", " X", "XXX" };
-
-        for (String text: texts) {
-            // 15 is such that only one character will fit
-            int[] breaks = getBreaks(text, 15);
-
-            // Width under 15 should all lead to the same line break
-            for (int width: widths) {
-                layout(text, breaks, width);
-            }
-        }
-    }
-
-    @Test
-    public void testNarrowWidthZeroWidth() {
-        int[] widths = new int[] { 1, 4 };
-        for (int width: widths) {
-            layout("X.", new int[] {1}, width);
-            layout("X__", NO_BREAK, width);
-            layout("X__X", new int[] {3}, width);
-            layout("X__X_", new int[] {3}, width);
-
-            layout("_", NO_BREAK, width);
-            layout("__", NO_BREAK, width);
-            layout("_X", new int[] {1}, width);
-            layout("_X_", new int[] {1}, width);
-            layout("__X__", new int[] {2}, width);
-        }
-    }
-
-    @Test
-    public void testMaxLines() {
-        layoutMaxLines("C", NO_BREAK, 1);
-        layoutMaxLines("C C", new int[] {2}, 1);
-        layoutMaxLines("C C", new int[] {2}, 2);
-        layoutMaxLines("CC", new int[] {1}, 1);
-        layoutMaxLines("CC", new int[] {1}, 2);
-    }
-}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
new file mode 100644
index 0000000..06b860a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -0,0 +1,380 @@
+/*
+ * 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.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IServiceConnection;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import com.android.frameworks.coretests.R;
+import com.android.internal.widget.IRemoteViewsFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * Tests for RemoteViewsAdapter.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RemoteViewsAdapterTest {
+
+    @Mock AppWidgetManager mAppWidgetManager;
+    @Mock IServiceConnection mIServiceConnection;
+    @Mock RemoteViewsAdapter.RemoteAdapterConnectionCallback mCallback;
+
+    private Handler mMainHandler;
+    private TestContext mContext;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mAppWidgetManager
+                .bindRemoteViewsService(any(), anyInt(), any(), any(), anyInt())).thenReturn(true);
+        mContext = new TestContext();
+        mMainHandler = new Handler(Looper.getMainLooper());
+    }
+
+    @Test
+    public void onRemoteAdapterConnected_after_metadata_loaded() throws Throwable {
+        RemoteViewsAdapter adapter = getOnUiThread(
+                () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+        assertFalse(adapter.isDataReady());
+
+        assertNotNull(mContext.conn.get());
+
+        ViewsFactory factory = new ViewsFactory(1);
+        mContext.sendConnect(factory);
+
+        waitOnHandler(mMainHandler);
+        verify(mCallback, never()).onRemoteAdapterConnected();
+
+        factory.loadingView.set(createViews("loading"));
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+        verify(mCallback, times(1)).onRemoteAdapterConnected();
+
+        assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+        // Service is unbound
+        assertTrue(isUnboundOrScheduled());
+    }
+
+    @Test
+    public void viewReplaced_after_mainView_loaded() throws Throwable {
+        RemoteViewsAdapter adapter = getOnUiThread(
+                () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+
+        ViewsFactory factory = new ViewsFactory(1);
+        factory.loadingView.set(createViews("loading"));
+        mContext.sendConnect(factory);
+
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+
+        // Returned view contains the loading text
+        View view = getOnUiThread(() -> adapter.getView(0, null, new FrameLayout(mContext)));
+        ArrayList<View> search = new ArrayList<>();
+        view.findViewsWithText(search, "loading", View.FIND_VIEWS_WITH_TEXT);
+        assertEquals(1, search.size());
+
+        // Send the final remoteViews
+        factory.views[0].set(createViews("updated"));
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+
+        // Existing view got updated with new text
+        search.clear();
+        view.findViewsWithText(search, "loading", View.FIND_VIEWS_WITH_TEXT);
+        assertTrue(search.isEmpty());
+        view.findViewsWithText(search, "updated", View.FIND_VIEWS_WITH_TEXT);
+        assertEquals(1, search.size());
+
+        // Service is unbound
+        assertTrue(isUnboundOrScheduled());
+    }
+
+    @Test
+    public void notifyDataSetChanged_deferred() throws Throwable {
+        RemoteViewsAdapter adapter = getOnUiThread(
+                () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+
+        ViewsFactory factory = new ViewsFactory(1);
+        factory.loadingView.set(createViews("loading"));
+        mContext.sendConnect(factory);
+
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+        assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+        // Reset the loading view so that next refresh is blocked
+        factory.loadingView = new LockedValue<>();
+        factory.mCount = 3;
+        DataSetObserver observer = mock(DataSetObserver.class);
+        getOnUiThread(() -> {
+            adapter.registerDataSetObserver(observer);
+            adapter.notifyDataSetChanged();
+            return null;
+        });
+
+        waitOnHandler(mMainHandler);
+        // Still giving the old values
+        verify(observer, never()).onChanged();
+        assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+        factory.loadingView.set(createViews("refreshed"));
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+
+        // When the service returns new data, UI is updated.
+        verify(observer, times(1)).onChanged();
+        assertEquals((Integer) 3, getOnUiThread(adapter::getCount));
+
+        // Service is unbound
+        assertTrue(isUnboundOrScheduled());
+    }
+
+    @Test
+    public void serviceDisconnected_before_getView() throws Throwable {
+        RemoteViewsAdapter adapter = getOnUiThread(
+                () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+
+        ViewsFactory factory = new ViewsFactory(1);
+        factory.loadingView.set(createViews("loading"));
+        mContext.sendConnect(factory);
+
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+        verify(mCallback, times(1)).onRemoteAdapterConnected();
+        assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+        // Unbind the service
+        ServiceConnection conn = mContext.conn.get();
+        getOnHandler(mContext.handler.get(), () -> {
+            conn.onServiceDisconnected(null);
+            return null;
+        });
+
+        // Returned view contains the loading text
+        View view = getOnUiThread(() -> adapter.getView(0, null, new FrameLayout(mContext)));
+        ArrayList<View> search = new ArrayList<>();
+        view.findViewsWithText(search, "loading", View.FIND_VIEWS_WITH_TEXT);
+        assertEquals(1, search.size());
+
+        // Unbind is not scheduled
+        assertFalse(isUnboundOrScheduled());
+
+        mContext.sendConnect(factory);
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+        verify(mCallback, times(2)).onRemoteAdapterConnected();
+    }
+
+    private RemoteViews createViews(String text) {
+        RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.remote_views_text);
+        views.setTextViewText(R.id.text, text);
+        return views;
+    }
+
+    private <T> T getOnUiThread(Supplier<T> supplier) throws Throwable {
+        return getOnHandler(mMainHandler, supplier);
+    }
+
+    private boolean isUnboundOrScheduled() throws Throwable {
+        Handler handler = mContext.handler.get();
+        return getOnHandler(handler, () -> mContext.boundCount == 0
+                || handler.hasMessages(RemoteViewsAdapter.MSG_UNBIND_SERVICE));
+    }
+
+    private static <T> T getOnHandler(Handler handler, Supplier<T> supplier) throws Throwable {
+        LockedValue<T> result = new LockedValue<>();
+        handler.post(() -> result.set(supplier.get()));
+        return result.get();
+    }
+
+    private class TestContext extends ContextWrapper {
+
+        public final LockedValue<ServiceConnection> conn = new LockedValue<>();
+        public final LockedValue<Handler> handler = new LockedValue<>();
+        public int boundCount;
+
+        TestContext() {
+            super(InstrumentationRegistry.getContext());
+        }
+
+        @Override
+        public void unbindService(ServiceConnection conn) {
+            boundCount--;
+        }
+
+        @Override
+        public Object getSystemService(String name) {
+            if (Context.APPWIDGET_SERVICE.equals(name)) {
+                return mAppWidgetManager;
+            }
+            return super.getSystemService(name);
+        }
+
+        @Override
+        public IServiceConnection getServiceDispatcher(
+                ServiceConnection conn, Handler handler, int flags) {
+            this.conn.set(conn);
+            this.handler.set(handler);
+            boundCount++;
+            return mIServiceConnection;
+        }
+
+        @Override
+        public Context getApplicationContext() {
+            return this;
+        }
+
+        public void sendConnect(ViewsFactory factory) throws Exception {
+            ServiceConnection connection = conn.get();
+            handler.get().post(() -> connection.onServiceConnected(null, factory.asBinder()));
+        }
+    }
+
+    private static void waitOnHandler(Handler handler) throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        handler.post(() -> latch.countDown());
+        latch.await(20, TimeUnit.SECONDS);
+    }
+
+    private static class ViewsFactory extends IRemoteViewsFactory.Stub {
+
+        public LockedValue<RemoteViews> loadingView = new LockedValue<>();
+        public LockedValue<RemoteViews>[] views;
+
+        private int mCount;
+
+        ViewsFactory(int count) {
+            mCount = count;
+            views = new LockedValue[count];
+            for (int i = 0; i < count; i++) {
+                views[i] = new LockedValue<>();
+            }
+        }
+
+        @Override
+        public void onDataSetChanged() {}
+
+        @Override
+        public void onDataSetChangedAsync() { }
+
+        @Override
+        public void onDestroy(Intent intent) { }
+
+        @Override
+        public int getCount() throws RemoteException {
+            return mCount;
+        }
+
+        @Override
+        public RemoteViews getViewAt(int position) throws RemoteException {
+            try {
+                return views[position].get();
+            } catch (Exception e) {
+                throw new RemoteException(e.getMessage());
+            }
+        }
+
+        @Override
+        public RemoteViews getLoadingView() throws RemoteException {
+            try {
+                return loadingView.get();
+            } catch (Exception e) {
+                throw new RemoteException(e.getMessage());
+            }
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 1;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return false;
+        }
+
+        @Override
+        public boolean isCreated() {
+            return false;
+        }
+    }
+
+    private static class DistinctIntent extends Intent {
+
+        @Override
+        public boolean filterEquals(Intent other) {
+            return false;
+        }
+    }
+
+    private static class LockedValue<T> {
+
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private T mValue;
+
+        public void set(T value) {
+            mValue = value;
+            mLatch.countDown();
+        }
+
+        public T get() throws Exception {
+            mLatch.await(10, TimeUnit.SECONDS);
+            return mValue;
+        }
+    }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java b/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
index 7935880..734ebef 100644
--- a/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
@@ -47,6 +47,7 @@
     private static final int TEST_ARG2 = 182;
     private static final Object TEST_OBJ = "hello";
 
+    @Mock Context mContext;
     @Mock AlarmManager mAlarmManager;
     WakeupMessage mMessage;
     // Make a spy so that we can verify calls to it
@@ -86,13 +87,12 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        Context context = mock(Context.class);
-        when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+        when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
         // capture the listener for each AlarmManager.setExact call
         doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), any(String.class),
                 mListenerCaptor.capture(), any(Handler.class));
 
-        mMessage = new WakeupMessage(context, mHandler, TEST_CMD_NAME, TEST_CMD, TEST_ARG1,
+        mMessage = new WakeupMessage(mContext, mHandler, TEST_CMD_NAME, TEST_CMD, TEST_ARG1,
                 TEST_ARG2, TEST_OBJ);
     }
 
@@ -168,4 +168,19 @@
         verifyMessageDispatchedOnce();
     }
 
+    /**
+     * Verify that a Runnable is scheduled and dispatched.
+     */
+    @Test
+    public void scheduleRunnable() {
+        final long when = 1011;
+        final Runnable runnable = mock(Runnable.class);
+        WakeupMessage dut = new WakeupMessage(mContext, mHandler, TEST_CMD_NAME, runnable);
+        dut.schedule(when);
+        verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(when),
+                eq(TEST_CMD_NAME), any(AlarmManager.OnAlarmListener.class), eq(mHandler));
+        mListenerCaptor.getValue().onAlarm();
+        verify(runnable, times(1)).run();
+    }
+
 }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 902b872..6f2b038 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -20,6 +20,10 @@
 applications that come with the platform
 -->
 <permissions>
+    <privapp-permissions package="android.ext.services">
+        <permission name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" />
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.backupconfirm">
         <permission name="android.permission.BACKUP"/>
         <permission name="android.permission.CRYPT_KEEPER"/>
@@ -367,8 +371,4 @@
         <permission name="android.permission.CONTROL_VPN"/>
     </privapp-permissions>
 
-    <privapp-permissions package="com.google.android.ext.services">
-        <permission name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" />
-    </privapp-permissions>
-
 </permissions>
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index d4d9dcb..83c82af 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -18,6 +18,7 @@
 
 #include "androidfw/AssetManager2.h"
 
+#include <iterator>
 #include <set>
 
 #include "android-base/logging.h"
@@ -263,9 +264,7 @@
 }
 
 ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
-                                         bool stop_at_first_match, LoadedArscEntry* out_entry,
-                                         ResTable_config* out_selected_config,
-                                         uint32_t* out_flags) {
+                                         bool stop_at_first_match, FindEntryResult* out_entry) {
   ATRACE_CALL();
 
   // Might use this if density_override != 0.
@@ -294,30 +293,28 @@
     return kInvalidCookie;
   }
 
-  LoadedArscEntry best_entry;
-  ResTable_config best_config;
+  FindEntryResult best_entry;
   ApkAssetsCookie best_cookie = kInvalidCookie;
   uint32_t cumulated_flags = 0u;
 
   const PackageGroup& package_group = package_groups_[idx];
   const size_t package_count = package_group.packages_.size();
   for (size_t i = 0; i < package_count; i++) {
-    LoadedArscEntry current_entry;
-    ResTable_config current_config;
-    uint32_t current_flags = 0;
-
     const LoadedPackage* loaded_package = package_group.packages_[i];
-    if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, &current_entry,
-                                   &current_config, &current_flags)) {
+
+    FindEntryResult current_entry;
+    if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, &current_entry)) {
       continue;
     }
 
-    cumulated_flags |= current_flags;
+    cumulated_flags |= current_entry.type_flags;
 
-    if (best_cookie == kInvalidCookie || current_config.isBetterThan(best_config, desired_config) ||
-        (loaded_package->IsOverlay() && current_config.compare(best_config) == 0)) {
+    const ResTable_config* current_config = current_entry.config;
+    const ResTable_config* best_config = best_entry.config;
+    if (best_cookie == kInvalidCookie ||
+        current_config->isBetterThan(*best_config, desired_config) ||
+        (loaded_package->IsOverlay() && current_config->compare(*best_config) == 0)) {
       best_entry = current_entry;
-      best_config = current_config;
       best_cookie = package_group.cookies_[i];
       if (stop_at_first_match) {
         break;
@@ -331,19 +328,16 @@
 
   *out_entry = best_entry;
   out_entry->dynamic_ref_table = &package_group.dynamic_ref_table;
-  *out_selected_config = best_config;
-  *out_flags = cumulated_flags;
+  out_entry->type_flags = cumulated_flags;
   return best_cookie;
 }
 
 bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) {
   ATRACE_CALL();
 
-  LoadedArscEntry entry;
-  ResTable_config config;
-  uint32_t flags = 0u;
-  ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
-                                     true /* stop_at_first_match */, &entry, &config, &flags);
+  FindEntryResult entry;
+  ApkAssetsCookie cookie =
+      FindEntry(resid, 0u /* density_override */, true /* stop_at_first_match */, &entry);
   if (cookie == kInvalidCookie) {
     return false;
   }
@@ -377,11 +371,14 @@
 }
 
 bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) {
-  LoadedArscEntry entry;
-  ResTable_config config;
-  ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
-                                     false /* stop_at_first_match */, &entry, &config, out_flags);
-  return cookie != kInvalidCookie;
+  FindEntryResult entry;
+  ApkAssetsCookie cookie =
+      FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry);
+  if (cookie != kInvalidCookie) {
+    *out_flags = entry.type_flags;
+    return cookie;
+  }
+  return kInvalidCookie;
 }
 
 ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
@@ -390,11 +387,9 @@
                                            uint32_t* out_flags) {
   ATRACE_CALL();
 
-  LoadedArscEntry entry;
-  ResTable_config config;
-  uint32_t flags = 0u;
+  FindEntryResult entry;
   ApkAssetsCookie cookie =
-      FindEntry(resid, density_override, false /* stop_at_first_match */, &entry, &config, &flags);
+      FindEntry(resid, density_override, false /* stop_at_first_match */, &entry);
   if (cookie == kInvalidCookie) {
     return kInvalidCookie;
   }
@@ -408,8 +403,8 @@
     // Create a reference since we can't represent this complex type as a Res_value.
     out_value->dataType = Res_value::TYPE_REFERENCE;
     out_value->data = resid;
-    *out_selected_config = config;
-    *out_flags = flags;
+    *out_selected_config = *entry.config;
+    *out_flags = entry.type_flags;
     return cookie;
   }
 
@@ -420,8 +415,8 @@
   // Convert the package ID to the runtime assigned package ID.
   entry.dynamic_ref_table->lookupResourceValue(out_value);
 
-  *out_selected_config = config;
-  *out_flags = flags;
+  *out_selected_config = *entry.config;
+  *out_flags = entry.type_flags;
   return cookie;
 }
 
@@ -464,11 +459,9 @@
     return cached_iter->second.get();
   }
 
-  LoadedArscEntry entry;
-  ResTable_config config;
-  uint32_t flags = 0u;
-  ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
-                                     false /* stop_at_first_match */, &entry, &config, &flags);
+  FindEntryResult entry;
+  ApkAssetsCookie cookie =
+      FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry);
   if (cookie == kInvalidCookie) {
     return nullptr;
   }
@@ -506,13 +499,20 @@
         }
       }
       new_entry->cookie = cookie;
-      new_entry->value.copyFrom_dtoh(map_entry->value);
       new_entry->key = new_key;
       new_entry->key_pool = nullptr;
       new_entry->type_pool = nullptr;
+      new_entry->value.copyFrom_dtoh(map_entry->value);
+      status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
+      if (err != NO_ERROR) {
+        LOG(ERROR) << base::StringPrintf(
+            "Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.", new_entry->value.dataType,
+            new_entry->value.data, new_key);
+        return nullptr;
+      }
       ++new_entry;
     }
-    new_bag->type_spec_flags = flags;
+    new_bag->type_spec_flags = entry.type_flags;
     new_bag->entry_count = static_cast<uint32_t>(entry_count);
     ResolvedBag* result = new_bag.get();
     cached_bags_[resid] = std::move(new_bag);
@@ -530,9 +530,6 @@
     return nullptr;
   }
 
-  // Combine flags from the parent and our own bag.
-  flags |= parent_bag->type_spec_flags;
-
   // Create the max possible entries we can make. Once we construct the bag,
   // we will realloc to fit to size.
   const size_t max_count = parent_bag->entry_count + dtohl(map->count);
@@ -557,10 +554,17 @@
       // Use the child key if it comes before the parent
       // or is equal to the parent (overrides).
       new_entry->cookie = cookie;
-      new_entry->value.copyFrom_dtoh(map_entry->value);
       new_entry->key = child_key;
       new_entry->key_pool = nullptr;
       new_entry->type_pool = nullptr;
+      new_entry->value.copyFrom_dtoh(map_entry->value);
+      status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
+      if (err != NO_ERROR) {
+        LOG(ERROR) << base::StringPrintf(
+            "Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.", new_entry->value.dataType,
+            new_entry->value.data, child_key);
+        return nullptr;
+      }
       ++map_entry;
     } else {
       // Take the parent entry as-is.
@@ -585,10 +589,16 @@
       }
     }
     new_entry->cookie = cookie;
-    new_entry->value.copyFrom_dtoh(map_entry->value);
     new_entry->key = new_key;
     new_entry->key_pool = nullptr;
     new_entry->type_pool = nullptr;
+    new_entry->value.copyFrom_dtoh(map_entry->value);
+    status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
+    if (err != NO_ERROR) {
+      LOG(ERROR) << base::StringPrintf("Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.",
+                                       new_entry->value.dataType, new_entry->value.data, new_key);
+      return nullptr;
+    }
     ++map_entry;
     ++new_entry;
   }
@@ -608,7 +618,8 @@
         new_bag.release(), sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry)))));
   }
 
-  new_bag->type_spec_flags = flags;
+  // Combine flags from the parent and our own bag.
+  new_bag->type_spec_flags = entry.type_flags | parent_bag->type_spec_flags;
   new_bag->entry_count = static_cast<uint32_t>(actual_count);
   ResolvedBag* result = new_bag.get();
   cached_bags_[resid] = std::move(new_bag);
@@ -701,7 +712,37 @@
   }
 }
 
-std::unique_ptr<Theme> AssetManager2::NewTheme() { return std::unique_ptr<Theme>(new Theme(this)); }
+std::unique_ptr<Theme> AssetManager2::NewTheme() {
+  return std::unique_ptr<Theme>(new Theme(this));
+}
+
+Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {
+}
+
+Theme::~Theme() = default;
+
+namespace {
+
+struct ThemeEntry {
+  ApkAssetsCookie cookie;
+  uint32_t type_spec_flags;
+  Res_value value;
+};
+
+struct ThemeType {
+  int entry_count;
+  ThemeEntry entries[0];
+};
+
+constexpr size_t kTypeCount = std::numeric_limits<uint8_t>::max() + 1;
+
+}  // namespace
+
+struct Theme::Package {
+  // Each element of Type will be a dynamically sized object
+  // allocated to have the entries stored contiguously with the Type.
+  std::array<util::unique_cptr<ThemeType>, kTypeCount> types;
+};
 
 bool Theme::ApplyStyle(uint32_t resid, bool force) {
   ATRACE_CALL();
@@ -714,71 +755,69 @@
   // Merge the flags from this style.
   type_spec_flags_ |= bag->type_spec_flags;
 
-  // On the first iteration, verify the attribute IDs and
-  // update the entry count in each type.
-  const auto bag_iter_end = end(bag);
-  for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) {
+  int last_type_idx = -1;
+  int last_package_idx = -1;
+  Package* last_package = nullptr;
+  ThemeType* last_type = nullptr;
+
+  // Iterate backwards, because each bag is sorted in ascending key ID order, meaning we will only
+  // need to perform one resize per type.
+  using reverse_bag_iterator = std::reverse_iterator<const ResolvedBag::Entry*>;
+  const auto bag_iter_end = reverse_bag_iterator(begin(bag));
+  for (auto bag_iter = reverse_bag_iterator(end(bag)); bag_iter != bag_iter_end; ++bag_iter) {
     const uint32_t attr_resid = bag_iter->key;
 
-    // If the resource ID passed in is not a style, the key can be
-    // some other identifier that is not a resource ID.
+    // If the resource ID passed in is not a style, the key can be some other identifier that is not
+    // a resource ID. We should fail fast instead of operating with strange resource IDs.
     if (!is_valid_resid(attr_resid)) {
       return false;
     }
 
-    const uint32_t package_idx = get_package_id(attr_resid);
+    // We don't use the 0-based index for the type so that we can avoid doing ID validation
+    // upon lookup. Instead, we keep space for the type ID 0 in our data structures. Since
+    // the construction of this type is guarded with a resource ID check, it will never be
+    // populated, and querying type ID 0 will always fail.
+    const int package_idx = get_package_id(attr_resid);
+    const int type_idx = get_type_id(attr_resid);
+    const int entry_idx = get_entry_id(attr_resid);
 
-    // The type ID is 1-based, so subtract 1 to get an index.
-    const uint32_t type_idx = get_type_id(attr_resid) - 1;
-    const uint32_t entry_idx = get_entry_id(attr_resid);
-
-    std::unique_ptr<Package>& package = packages_[package_idx];
-    if (package == nullptr) {
-      package.reset(new Package());
-    }
-
-    util::unique_cptr<Type>& type = package->types[type_idx];
-    if (type == nullptr) {
-      // Set the initial capacity to take up a total amount of 1024 bytes.
-      constexpr uint32_t kInitialCapacity = (1024u - sizeof(Type)) / sizeof(Entry);
-      const uint32_t initial_capacity = std::max(entry_idx, kInitialCapacity);
-      type.reset(
-          reinterpret_cast<Type*>(calloc(sizeof(Type) + (initial_capacity * sizeof(Entry)), 1)));
-      type->entry_capacity = initial_capacity;
-    }
-
-    // Set the entry_count to include this entry. We will populate
-    // and resize the array as necessary in the next pass.
-    if (entry_idx + 1 > type->entry_count) {
-      // Increase the entry count to include this.
-      type->entry_count = entry_idx + 1;
-    }
-  }
-
-  // On the second pass, we will realloc to fit the entry counts
-  // and populate the structures.
-  for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) {
-    const uint32_t attr_resid = bag_iter->key;
-    const uint32_t package_idx = get_package_id(attr_resid);
-    const uint32_t type_idx = get_type_id(attr_resid) - 1;
-    const uint32_t entry_idx = get_entry_id(attr_resid);
-    Package* package = packages_[package_idx].get();
-    util::unique_cptr<Type>& type = package->types[type_idx];
-    if (type->entry_count != type->entry_capacity) {
-      // Resize to fit the actual entries that will be included.
-      Type* type_ptr = type.release();
-      type.reset(reinterpret_cast<Type*>(
-          realloc(type_ptr, sizeof(Type) + (type_ptr->entry_count * sizeof(Entry)))));
-      if (type->entry_capacity < type->entry_count) {
-        // Clear the newly allocated memory (which does not get zero initialized).
-        // We need to do this because we |= type_spec_flags.
-        memset(type->entries + type->entry_capacity, 0,
-               sizeof(Entry) * (type->entry_count - type->entry_capacity));
+    if (last_package_idx != package_idx) {
+      std::unique_ptr<Package>& package = packages_[package_idx];
+      if (package == nullptr) {
+        package.reset(new Package());
       }
-      type->entry_capacity = type->entry_count;
+      last_package_idx = package_idx;
+      last_package = package.get();
+      last_type_idx = -1;
     }
-    Entry& entry = type->entries[entry_idx];
-    if (force || entry.value.dataType == Res_value::TYPE_NULL) {
+
+    if (last_type_idx != type_idx) {
+      util::unique_cptr<ThemeType>& type = last_package->types[type_idx];
+      if (type == nullptr) {
+        // Allocate enough memory to contain this entry_idx. Since we're iterating in reverse over
+        // a sorted list of attributes, this shouldn't be resized again during this method call.
+        type.reset(reinterpret_cast<ThemeType*>(
+            calloc(sizeof(ThemeType) + (entry_idx + 1) * sizeof(ThemeEntry), 1)));
+        type->entry_count = entry_idx + 1;
+      } else if (entry_idx >= type->entry_count) {
+        // Reallocate the memory to contain this entry_idx. Since we're iterating in reverse over
+        // a sorted list of attributes, this shouldn't be resized again during this method call.
+        const int new_count = entry_idx + 1;
+        type.reset(reinterpret_cast<ThemeType*>(
+            realloc(type.release(), sizeof(ThemeType) + (new_count * sizeof(ThemeEntry)))));
+
+        // Clear out the newly allocated space (which isn't zeroed).
+        memset(type->entries + type->entry_count, 0,
+               (new_count - type->entry_count) * sizeof(ThemeEntry));
+        type->entry_count = new_count;
+      }
+      last_type_idx = type_idx;
+      last_type = type.get();
+    }
+
+    ThemeEntry& entry = last_type->entries[entry_idx];
+    if (force || (entry.value.dataType == Res_value::TYPE_NULL &&
+                  entry.value.data != Res_value::DATA_NULL_EMPTY)) {
       entry.cookie = bag_iter->cookie;
       entry.type_spec_flags |= bag->type_spec_flags;
       entry.value = bag_iter->value;
@@ -789,88 +828,46 @@
 
 ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value,
                                     uint32_t* out_flags) const {
-  constexpr const int kMaxIterations = 20;
+  int cnt = 20;
 
   uint32_t type_spec_flags = 0u;
 
-  for (int iterations_left = kMaxIterations; iterations_left > 0; iterations_left--) {
-    if (!is_valid_resid(resid)) {
-      return kInvalidCookie;
-    }
-
-    const uint32_t package_idx = get_package_id(resid);
-
-    // Type ID is 1-based, subtract 1 to get the index.
-    const uint32_t type_idx = get_type_id(resid) - 1;
-    const uint32_t entry_idx = get_entry_id(resid);
-
+  do {
+    const int package_idx = get_package_id(resid);
     const Package* package = packages_[package_idx].get();
-    if (package == nullptr) {
-      return kInvalidCookie;
-    }
+    if (package != nullptr) {
+      // The themes are constructed with a 1-based type ID, so no need to decrement here.
+      const int type_idx = get_type_id(resid);
+      const ThemeType* type = package->types[type_idx].get();
+      if (type != nullptr) {
+        const int entry_idx = get_entry_id(resid);
+        if (entry_idx < type->entry_count) {
+          const ThemeEntry& entry = type->entries[entry_idx];
+          type_spec_flags |= entry.type_spec_flags;
 
-    const Type* type = package->types[type_idx].get();
-    if (type == nullptr) {
-      return kInvalidCookie;
-    }
+          if (entry.value.dataType == Res_value::TYPE_ATTRIBUTE) {
+            if (cnt > 0) {
+              cnt--;
+              resid = entry.value.data;
+              continue;
+            }
+            return kInvalidCookie;
+          }
 
-    if (entry_idx >= type->entry_count) {
-      return kInvalidCookie;
-    }
+          // @null is different than @empty.
+          if (entry.value.dataType == Res_value::TYPE_NULL &&
+              entry.value.data != Res_value::DATA_NULL_EMPTY) {
+            return kInvalidCookie;
+          }
 
-    const Entry& entry = type->entries[entry_idx];
-    type_spec_flags |= entry.type_spec_flags;
-
-    switch (entry.value.dataType) {
-      case Res_value::TYPE_NULL:
-        return kInvalidCookie;
-
-      case Res_value::TYPE_ATTRIBUTE:
-        resid = entry.value.data;
-        break;
-
-      case Res_value::TYPE_DYNAMIC_ATTRIBUTE: {
-        // Resolve the dynamic attribute to a normal attribute
-        // (with the right package ID).
-        resid = entry.value.data;
-        const DynamicRefTable* ref_table =
-            asset_manager_->GetDynamicRefTableForPackage(package_idx);
-        if (ref_table == nullptr || ref_table->lookupResourceId(&resid) != NO_ERROR) {
-          LOG(ERROR) << base::StringPrintf("Failed to resolve dynamic attribute 0x%08x", resid);
-          return kInvalidCookie;
-        }
-      } break;
-
-      case Res_value::TYPE_DYNAMIC_REFERENCE: {
-        // Resolve the dynamic reference to a normal reference
-        // (with the right package ID).
-        out_value->dataType = Res_value::TYPE_REFERENCE;
-        out_value->data = entry.value.data;
-        const DynamicRefTable* ref_table =
-            asset_manager_->GetDynamicRefTableForPackage(package_idx);
-        if (ref_table == nullptr || ref_table->lookupResourceId(&out_value->data) != NO_ERROR) {
-          LOG(ERROR) << base::StringPrintf("Failed to resolve dynamic reference 0x%08x",
-                                           out_value->data);
-          return kInvalidCookie;
-        }
-
-        if (out_flags != nullptr) {
+          *out_value = entry.value;
           *out_flags = type_spec_flags;
+          return entry.cookie;
         }
-        return entry.cookie;
       }
-
-      default:
-        *out_value = entry.value;
-        if (out_flags != nullptr) {
-          *out_flags = type_spec_flags;
-        }
-        return entry.cookie;
     }
-  }
-
-  LOG(WARNING) << base::StringPrintf("Too many (%d) attribute references, stopped at: 0x%08x",
-                                     kMaxIterations, resid);
+    break;
+  } while (true);
   return kInvalidCookie;
 }
 
@@ -923,7 +920,7 @@
     }
 
     for (size_t t = 0; t < package->types.size(); t++) {
-      const Type* type = package->types[t].get();
+      const ThemeType* type = package->types[t].get();
       if (type == nullptr) {
         // The other theme doesn't have this type, clear ours.
         packages_[p]->types[t].reset();
@@ -931,10 +928,10 @@
       }
 
       // Create a new type and update it to theirs.
-      const size_t type_alloc_size = sizeof(Type) + (type->entry_capacity * sizeof(Entry));
+      const size_t type_alloc_size = sizeof(ThemeType) + (type->entry_count * sizeof(ThemeEntry));
       void* copied_data = malloc(type_alloc_size);
       memcpy(copied_data, type, type_alloc_size);
-      packages_[p]->types[t].reset(reinterpret_cast<Type*>(copied_data));
+      packages_[p]->types[t].reset(reinterpret_cast<ThemeType*>(copied_data));
     }
   }
   return true;
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index b62fc81..c361ea2 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -122,9 +122,180 @@
 
 }  // namespace
 
+LoadedPackage::LoadedPackage() = default;
+LoadedPackage::~LoadedPackage() = default;
+
+// Precondition: The header passed in has already been verified, so reading any fields and trusting
+// the ResChunk_header is safe.
+static bool VerifyResTableType(const ResTable_type* header) {
+  const size_t entry_count = dtohl(header->entryCount);
+  if (entry_count > std::numeric_limits<uint16_t>::max()) {
+    LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_TYPE.";
+    return false;
+  }
+
+  // Make sure that there is enough room for the entry offsets.
+  const size_t offsets_offset = dtohs(header->header.headerSize);
+  const size_t entries_offset = dtohl(header->entriesStart);
+  const size_t offsets_length = sizeof(uint32_t) * entry_count;
+
+  if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
+    LOG(ERROR) << "Entry offsets overlap actual entry data.";
+    return false;
+  }
+
+  if (entries_offset > dtohl(header->header.size)) {
+    LOG(ERROR) << "Entry offsets extend beyond chunk.";
+    return false;
+  }
+
+  if (entries_offset & 0x03) {
+    LOG(ERROR) << "Entries start at unaligned address.";
+    return false;
+  }
+  return true;
+}
+
+static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset,
+                                size_t entry_idx) {
+  // Check that the offset is aligned.
+  if (entry_offset & 0x03) {
+    LOG(ERROR) << "Entry offset at index " << entry_idx << " is not 4-byte aligned.";
+    return false;
+  }
+
+  // Check that the offset doesn't overflow.
+  if (entry_offset > std::numeric_limits<uint32_t>::max() - dtohl(type->entriesStart)) {
+    // Overflow in offset.
+    LOG(ERROR) << "Entry offset at index " << entry_idx << " is too large.";
+    return false;
+  }
+
+  const size_t chunk_size = dtohl(type->header.size);
+
+  entry_offset += dtohl(type->entriesStart);
+  if (entry_offset > chunk_size - sizeof(ResTable_entry)) {
+    LOG(ERROR) << "Entry offset at index " << entry_idx
+               << " is too large. No room for ResTable_entry.";
+    return false;
+  }
+
+  const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
+      reinterpret_cast<const uint8_t*>(type) + entry_offset);
+
+  const size_t entry_size = dtohs(entry->size);
+  if (entry_size < sizeof(*entry)) {
+    LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx
+               << " is too small.";
+    return false;
+  }
+
+  if (entry_size > chunk_size || entry_offset > chunk_size - entry_size) {
+    LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx
+               << " is too large.";
+    return false;
+  }
+
+  if (entry_size < sizeof(ResTable_map_entry)) {
+    // There needs to be room for one Res_value struct.
+    if (entry_offset + entry_size > chunk_size - sizeof(Res_value)) {
+      LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << entry_idx
+                 << " for type " << (int)type->id << ".";
+      return false;
+    }
+
+    const Res_value* value =
+        reinterpret_cast<const Res_value*>(reinterpret_cast<const uint8_t*>(entry) + entry_size);
+    const size_t value_size = dtohs(value->size);
+    if (value_size < sizeof(Res_value)) {
+      LOG(ERROR) << "Res_value at index " << entry_idx << " is too small.";
+      return false;
+    }
+
+    if (value_size > chunk_size || entry_offset + entry_size > chunk_size - value_size) {
+      LOG(ERROR) << "Res_value size " << value_size << " at index " << entry_idx
+                 << " is too large.";
+      return false;
+    }
+  } else {
+    const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry);
+    const size_t map_entry_count = dtohl(map->count);
+    size_t map_entries_start = entry_offset + entry_size;
+    if (map_entries_start & 0x03) {
+      LOG(ERROR) << "Map entries at index " << entry_idx << " start at unaligned offset.";
+      return false;
+    }
+
+    // Each entry is sizeof(ResTable_map) big.
+    if (map_entry_count > ((chunk_size - map_entries_start) / sizeof(ResTable_map))) {
+      LOG(ERROR) << "Too many map entries in ResTable_map_entry at index " << entry_idx << ".";
+      return false;
+    }
+  }
+  return true;
+}
+
+template <bool Verified>
+bool LoadedPackage::FindEntry(const TypeSpecPtr& type_spec_ptr, uint16_t entry_idx,
+                              const ResTable_config& config, FindEntryResult* out_entry) const {
+  const ResTable_config* best_config = nullptr;
+  const ResTable_type* best_type = nullptr;
+  uint32_t best_offset = 0;
+
+  for (uint32_t i = 0; i < type_spec_ptr->type_count; i++) {
+    const Type* type = &type_spec_ptr->types[i];
+
+    if (type->configuration.match(config) &&
+        (best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) {
+      // The configuration matches and is better than the previous selection.
+      // Find the entry value if it exists for this configuration.
+      const size_t entry_count = dtohl(type->type->entryCount);
+      const size_t offsets_offset = dtohs(type->type->header.headerSize);
+      if (entry_idx < entry_count) {
+        // If the package hasn't been verified, do bounds checking.
+        if (!Verified) {
+          if (!VerifyResTableType(type->type)) {
+            continue;
+          }
+        }
+
+        const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
+            reinterpret_cast<const uint8_t*>(type->type) + offsets_offset);
+        const uint32_t offset = dtohl(entry_offsets[entry_idx]);
+        if (offset != ResTable_type::NO_ENTRY) {
+          // There is an entry for this resource, record it.
+          best_config = &type->configuration;
+          best_type = type->type;
+          best_offset = offset;
+        }
+      }
+    }
+  }
+
+  if (best_type == nullptr) {
+    return false;
+  }
+
+  if (!Verified) {
+    if (!VerifyResTableEntry(best_type, best_offset, entry_idx)) {
+      return false;
+    }
+  }
+
+  const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
+      reinterpret_cast<const uint8_t*>(best_type) + best_offset + dtohl(best_type->entriesStart));
+
+  const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec_ptr->type_spec + 1);
+  out_entry->type_flags = dtohl(flags[entry_idx]);
+  out_entry->entry = best_entry;
+  out_entry->config = best_config;
+  out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1);
+  out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index));
+  return true;
+}
+
 bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
-                              LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
-                              uint32_t* out_flags) const {
+                              FindEntryResult* out_entry) const {
   ATRACE_CALL();
 
   // If the type IDs are offset in this package, we need to take that into account when searching
@@ -142,120 +313,27 @@
     }
   }
 
-  // Don't bother checking if the entry ID is larger than
-  // the number of entries.
+  // Don't bother checking if the entry ID is larger than the number of entries.
   if (entry_idx >= dtohl(ptr->type_spec->entryCount)) {
     return false;
   }
 
-  const ResTable_config* best_config = nullptr;
-  const ResTable_type* best_type = nullptr;
-  uint32_t best_offset = 0;
-
-  for (uint32_t i = 0; i < ptr->type_count; i++) {
-    const Type* type = &ptr->types[i];
-
-    if (type->configuration.match(config) &&
-        (best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) {
-      // The configuration matches and is better than the previous selection.
-      // Find the entry value if it exists for this configuration.
-      size_t entry_count = dtohl(type->type->entryCount);
-      if (entry_idx < entry_count) {
-        const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
-            reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize));
-        const uint32_t offset = dtohl(entry_offsets[entry_idx]);
-        if (offset != ResTable_type::NO_ENTRY) {
-          // There is an entry for this resource, record it.
-          best_config = &type->configuration;
-          best_type = type->type;
-          best_offset = offset + dtohl(type->type->entriesStart);
-        }
-      }
-    }
+  if (verified_) {
+    return FindEntry<true>(ptr, entry_idx, config, out_entry);
   }
-
-  if (best_type == nullptr) {
-    return false;
-  }
-
-  const uint32_t* flags = reinterpret_cast<const uint32_t*>(ptr->type_spec + 1);
-  *out_flags = dtohl(flags[entry_idx]);
-  *out_selected_config = *best_config;
-
-  const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
-      reinterpret_cast<const uint8_t*>(best_type) + best_offset);
-  out_entry->entry = best_entry;
-  out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1);
-  out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index));
-  return true;
-}
-
-// The destructor gets generated into arbitrary translation units
-// if left implicit, which causes the compiler to complain about
-// forward declarations and incomplete types.
-LoadedArsc::~LoadedArsc() {}
-
-bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config,
-                           LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
-                           uint32_t* out_flags) const {
-  ATRACE_CALL();
-  const uint8_t package_id = get_package_id(resid);
-  const uint8_t type_id = get_type_id(resid);
-  const uint16_t entry_id = get_entry_id(resid);
-
-  if (type_id == 0) {
-    LOG(ERROR) << "Invalid ID 0x" << std::hex << resid << std::dec << ".";
-    return false;
-  }
-
-  for (const auto& loaded_package : packages_) {
-    if (loaded_package->package_id_ == package_id) {
-      return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry,
-                                       out_selected_config, out_flags);
-    }
-  }
-  return false;
-}
-
-const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const {
-  const uint8_t package_id = get_package_id(resid);
-  for (const auto& loaded_package : packages_) {
-    if (loaded_package->package_id_ == package_id) {
-      return loaded_package.get();
-    }
-  }
-  return nullptr;
+  return FindEntry<false>(ptr, entry_idx, config, out_entry);
 }
 
 static bool VerifyType(const Chunk& chunk) {
   ATRACE_CALL();
   const ResTable_type* header = chunk.header<ResTable_type, kResTableTypeMinSize>();
 
+  if (!VerifyResTableType(header)) {
+    return false;
+  }
+
   const size_t entry_count = dtohl(header->entryCount);
-  if (entry_count > std::numeric_limits<uint16_t>::max()) {
-    LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_TYPE.";
-    return false;
-  }
-
-  // Make sure that there is enough room for the entry offsets.
   const size_t offsets_offset = chunk.header_size();
-  const size_t entries_offset = dtohl(header->entriesStart);
-  const size_t offsets_length = sizeof(uint32_t) * entry_count;
-
-  if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
-    LOG(ERROR) << "Entry offsets overlap actual entry data.";
-    return false;
-  }
-
-  if (entries_offset > chunk.size()) {
-    LOG(ERROR) << "Entry offsets extend beyond chunk.";
-    return false;
-  }
-
-  if (entries_offset & 0x03) {
-    LOG(ERROR) << "Entries start at unaligned address.";
-    return false;
-  }
 
   // Check each entry offset.
   const uint32_t* offsets =
@@ -263,79 +341,9 @@
   for (size_t i = 0; i < entry_count; i++) {
     uint32_t offset = dtohl(offsets[i]);
     if (offset != ResTable_type::NO_ENTRY) {
-      // Check that the offset is aligned.
-      if (offset & 0x03) {
-        LOG(ERROR) << "Entry offset at index " << i << " is not 4-byte aligned.";
+      if (!VerifyResTableEntry(header, offset, i)) {
         return false;
       }
-
-      // Check that the offset doesn't overflow.
-      if (offset > std::numeric_limits<uint32_t>::max() - entries_offset) {
-        // Overflow in offset.
-        LOG(ERROR) << "Entry offset at index " << i << " is too large.";
-        return false;
-      }
-
-      offset += entries_offset;
-      if (offset > chunk.size() - sizeof(ResTable_entry)) {
-        LOG(ERROR) << "Entry offset at index " << i << " is too large. No room for ResTable_entry.";
-        return false;
-      }
-
-      const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
-          reinterpret_cast<const uint8_t*>(header) + offset);
-      const size_t entry_size = dtohs(entry->size);
-      if (entry_size < sizeof(*entry)) {
-        LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << i << " is too small.";
-        return false;
-      }
-
-      // Check the declared entrySize.
-      if (entry_size > chunk.size() || offset > chunk.size() - entry_size) {
-        LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << i << " is too large.";
-        return false;
-      }
-
-      // If this is a map entry, then keep validating.
-      if (entry_size >= sizeof(ResTable_map_entry)) {
-        const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry);
-        const size_t map_entry_count = dtohl(map->count);
-
-        size_t map_entries_start = offset + entry_size;
-        if (map_entries_start & 0x03) {
-          LOG(ERROR) << "Map entries at index " << i << " start at unaligned offset.";
-          return false;
-        }
-
-        // Each entry is sizeof(ResTable_map) big.
-        if (map_entry_count > ((chunk.size() - map_entries_start) / sizeof(ResTable_map))) {
-          LOG(ERROR) << "Too many map entries in ResTable_map_entry at index " << i << ".";
-          return false;
-        }
-
-        // Great, all the map entries fit!.
-      } else {
-        // There needs to be room for one Res_value struct.
-        if (offset + entry_size > chunk.size() - sizeof(Res_value)) {
-          LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << i << " for type "
-                     << (int)header->id << " with config " << header->config.toString().string()
-                     << ".";
-          return false;
-        }
-
-        const Res_value* value = reinterpret_cast<const Res_value*>(
-            reinterpret_cast<const uint8_t*>(entry) + entry_size);
-        const size_t value_size = dtohs(value->size);
-        if (value_size < sizeof(Res_value)) {
-          LOG(ERROR) << "Res_value at index " << i << " is too small.";
-          return false;
-        }
-
-        if (value_size > chunk.size() || offset + entry_size > chunk.size() - value_size) {
-          LOG(ERROR) << "Res_value size " << value_size << " at index " << i << " is too large.";
-          return false;
-        }
-      }
     }
   }
   return true;
@@ -431,10 +439,21 @@
   return 0u;
 }
 
-std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
-                                                   const LoadedIdmap* loaded_idmap) {
+const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const {
+  const uint8_t package_id = get_package_id(resid);
+  for (const auto& loaded_package : packages_) {
+    if (loaded_package->GetPackageId() == package_id) {
+      return loaded_package.get();
+    }
+  }
+  return nullptr;
+}
+
+std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
+                                                         const LoadedIdmap* loaded_idmap,
+                                                         bool system, bool load_as_shared_library) {
   ATRACE_CALL();
-  std::unique_ptr<LoadedPackage> loaded_package{new LoadedPackage()};
+  std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
 
   // typeIdOffset was added at some point, but we still must recognize apps built before this
   // was added.
@@ -446,8 +465,11 @@
     return {};
   }
 
+  loaded_package->system_ = system;
+
   loaded_package->package_id_ = dtohl(header->id);
-  if (loaded_package->package_id_ == 0) {
+  if (loaded_package->package_id_ == 0 ||
+      (loaded_package->package_id_ == kAppPackageId && load_as_shared_library)) {
     // Package ID of 0 means this is a shared library.
     loaded_package->dynamic_ = true;
   }
@@ -593,13 +615,16 @@
 
         // Type chunks must be preceded by their TypeSpec chunks.
         if (!types_builder || type->id - 1 != last_type_idx) {
-          LOG(ERROR) << "Found RES_TABLE_TYPE_TYPE chunk without "
-                        "RES_TABLE_TYPE_SPEC_TYPE.";
+          LOG(ERROR) << "Found RES_TABLE_TYPE_TYPE chunk without RES_TABLE_TYPE_SPEC_TYPE.";
           return {};
         }
 
-        if (!VerifyType(child_chunk)) {
-          return {};
+        // Only verify the type if we haven't already failed verification.
+        if (loaded_package->verified_) {
+          if (!VerifyType(child_chunk)) {
+            LOG(WARNING) << "Package failed verification, resource retrieval may be slower";
+            loaded_package->verified_ = false;
+          }
         }
 
         types_builder->AddType(type);
@@ -669,7 +694,28 @@
     LOG(ERROR) << iter.GetLastError();
     return {};
   }
-  return loaded_package;
+  return std::move(loaded_package);
+}
+
+bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config,
+                           FindEntryResult* out_entry) const {
+  ATRACE_CALL();
+
+  const uint8_t package_id = get_package_id(resid);
+  const uint8_t type_id = get_type_id(resid);
+  const uint16_t entry_id = get_entry_id(resid);
+
+  if (type_id == 0) {
+    LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
+    return false;
+  }
+
+  for (const auto& loaded_package : packages_) {
+    if (loaded_package->GetPackageId() == package_id) {
+      return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry);
+    }
+  }
+  return false;
 }
 
 bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
@@ -712,17 +758,11 @@
         }
         packages_seen++;
 
-        std::unique_ptr<LoadedPackage> loaded_package =
-            LoadedPackage::Load(child_chunk, loaded_idmap);
+        std::unique_ptr<const LoadedPackage> loaded_package =
+            LoadedPackage::Load(child_chunk, loaded_idmap, system_, load_as_shared_library);
         if (!loaded_package) {
           return false;
         }
-
-        // Mark the package as dynamic if we are forcefully loading the Apk as a shared library.
-        if (loaded_package->package_id_ == kAppPackageId) {
-          loaded_package->dynamic_ = load_as_shared_library;
-        }
-        loaded_package->system_ = system_;
         packages_.push_back(std::move(loaded_package));
       } break;
 
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 7a0ef2b..87999c3 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6014,9 +6014,6 @@
 StringPoolRef::StringPoolRef(const ResStringPool* pool, uint32_t index)
     : mPool(pool), mIndex(index) {}
 
-StringPoolRef::StringPoolRef()
-    : mPool(NULL), mIndex(0) {}
-
 const char* StringPoolRef::string8(size_t* outLen) const {
     if (mPool != NULL) {
         return mPool->string8At(mIndex, outLen);
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index b29bc3a..a77c4b9 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -245,21 +245,22 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(AssetManager2);
 
-  // Finds the best entry for `resid` amongst all the ApkAssets. The entry can be a simple
-  // Res_value, or a complex map/bag type.
+  // Finds the best entry for `resid` from the set of ApkAssets. The entry can be a simple
+  // Res_value, or a complex map/bag type. If successful, it is available in `out_entry`.
+  // Returns kInvalidCookie on failure. Otherwise, the return value is the cookie associated with
+  // the ApkAssets in which the entry was found.
   //
   // `density_override` overrides the density of the current configuration when doing a search.
   //
   // When `stop_at_first_match` is true, the first match found is selected and the search
   // terminates. This is useful for methods that just look up the name of a resource and don't
-  // care about the value. In this case, the value of `out_flags` is incomplete and should not
-  // be used.
+  // care about the value. In this case, the value of `FindEntryResult::type_flags` is incomplete
+  // and should not be used.
   //
-  // `out_flags` stores the resulting bitmask of configuration axis with which the resource
-  // value varies.
+  // NOTE: FindEntry takes care of ensuring that structs within FindEntryResult have been properly
+  // bounds-checked. Callers of FindEntry are free to trust the data if this method succeeds.
   ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match,
-                            LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
-                            uint32_t* out_flags);
+                            FindEntryResult* out_entry);
 
   // Assigns package IDs to all shared library ApkAssets.
   // Should be called whenever the ApkAssets are changed.
@@ -302,6 +303,8 @@
   friend class AssetManager2;
 
  public:
+  ~Theme();
+
   // Applies the style identified by `resid` to this theme. This can be called
   // multiple times with different styles. By default, any theme attributes that
   // are already defined before this call are not overridden. If `force` is set
@@ -316,27 +319,31 @@
 
   void Clear();
 
-  inline const AssetManager2* GetAssetManager() const { return asset_manager_; }
+  inline const AssetManager2* GetAssetManager() const {
+    return asset_manager_;
+  }
 
-  inline AssetManager2* GetAssetManager() { return asset_manager_; }
+  inline AssetManager2* GetAssetManager() {
+    return asset_manager_;
+  }
 
   // Returns a bit mask of configuration changes that will impact this
   // theme (and thus require completely reloading it).
-  inline uint32_t GetChangingConfigurations() const { return type_spec_flags_; }
+  inline uint32_t GetChangingConfigurations() const {
+    return type_spec_flags_;
+  }
 
-  // Retrieve a value in the theme. If the theme defines this value,
-  // returns an asset cookie indicating which ApkAssets it came from
-  // and populates `out_value` with the value. If `out_flags` is non-null,
-  // populates it with a bitmask of the configuration axis the resource
-  // varies with.
+  // Retrieve a value in the theme. If the theme defines this value, returns an asset cookie
+  // indicating which ApkAssets it came from and populates `out_value` with the value.
+  // `out_flags` is populated with a bitmask of the configuration axis with which the resource
+  // varies.
   //
   // If the attribute is not found, returns kInvalidCookie.
   //
-  // NOTE: This function does not do reference traversal. If you want
-  // to follow references to other resources to get the "real" value to
-  // use, you need to call ResolveReference() after this function.
-  ApkAssetsCookie GetAttribute(uint32_t resid, Res_value* out_value,
-                               uint32_t* out_flags = nullptr) const;
+  // NOTE: This function does not do reference traversal. If you want to follow references to other
+  // resources to get the "real" value to use, you need to call ResolveReference() after this
+  // function.
+  ApkAssetsCookie GetAttribute(uint32_t resid, Res_value* out_value, uint32_t* out_flags) const;
 
   // This is like AssetManager2::ResolveReference(), but also takes
   // care of resolving attribute references to the theme.
@@ -349,36 +356,21 @@
   DISALLOW_COPY_AND_ASSIGN(Theme);
 
   // Called by AssetManager2.
-  explicit inline Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {}
-
-  struct Entry {
-    ApkAssetsCookie cookie;
-    uint32_t type_spec_flags;
-    Res_value value;
-  };
-
-  struct Type {
-    // Use uint32_t for fewer cycles when loading from memory.
-    uint32_t entry_count;
-    uint32_t entry_capacity;
-    Entry entries[0];
-  };
-
-  static constexpr const size_t kPackageCount = std::numeric_limits<uint8_t>::max() + 1;
-  static constexpr const size_t kTypeCount = std::numeric_limits<uint8_t>::max() + 1;
-
-  struct Package {
-    // Each element of Type will be a dynamically sized object
-    // allocated to have the entries stored contiguously with the Type.
-    std::array<util::unique_cptr<Type>, kTypeCount> types;
-  };
+  explicit Theme(AssetManager2* asset_manager);
 
   AssetManager2* asset_manager_;
   uint32_t type_spec_flags_ = 0u;
+
+  // Defined in the cpp.
+  struct Package;
+
+  constexpr static size_t kPackageCount = std::numeric_limits<uint8_t>::max() + 1;
   std::array<std::unique_ptr<Package>, kPackageCount> packages_;
 };
 
-inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) { return bag->entries; }
+inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) {
+  return bag->entries;
+}
 
 inline const ResolvedBag::Entry* end(const ResolvedBag* bag) {
   return bag->entries + bag->entry_count;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 1f272e8..377735b 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -41,12 +41,18 @@
   int package_id = 0;
 };
 
-struct LoadedArscEntry {
+struct FindEntryResult {
   // A pointer to the resource table entry for this resource.
   // If the size of the entry is > sizeof(ResTable_entry), it can be cast to
   // a ResTable_map_entry and processed as a bag/map.
   const ResTable_entry* entry = nullptr;
 
+  // The configuration for which the resulting entry was defined.
+  const ResTable_config* config = nullptr;
+
+  // Stores the resulting bitmask of configuration axis with which the resource value varies.
+  uint32_t type_flags = 0u;
+
   // The dynamic package ID map for the package from which this resource came from.
   const DynamicRefTable* dynamic_ref_table = nullptr;
 
@@ -63,12 +69,22 @@
 class LoadedArsc;
 
 class LoadedPackage {
-  friend class LoadedArsc;
-
  public:
+  static std::unique_ptr<const LoadedPackage> Load(const Chunk& chunk,
+                                                   const LoadedIdmap* loaded_idmap, bool system,
+                                                   bool load_as_shared_library);
+
+  ~LoadedPackage();
+
   bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
-                 LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
-                 uint32_t* out_flags) const;
+                 FindEntryResult* out_entry) const;
+
+  // Finds the entry with the specified type name and entry name. The names are in UTF-16 because
+  // the underlying ResStringPool API expects this. For now this is acceptable, but since
+  // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change.
+  // Returns a partial resource ID, with the package ID left as 0x00. The caller is responsible
+  // for patching the correct package ID to the resource ID.
+  uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const;
 
   // Returns the string pool where type names are stored.
   inline const ResStringPool* GetTypeStringPool() const {
@@ -98,10 +114,16 @@
     return system_;
   }
 
+  // Returns true if this package is from an overlay ApkAssets.
   inline bool IsOverlay() const {
     return overlay_;
   }
 
+  // Returns true if this package is verified to be valid.
+  inline bool IsVerified() const {
+    return verified_;
+  }
+
   // Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
   // package could have been assigned a different package ID than what this LoadedPackage was
   // compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime.
@@ -118,19 +140,14 @@
   // before being inserted into the set. This may cause some equivalent locales to de-dupe.
   void CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const;
 
-  // Finds the entry with the specified type name and entry name. The names are in UTF-16 because
-  // the underlying ResStringPool API expects this. For now this is acceptable, but since
-  // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change.
-  // Returns a partial resource ID, with the package ID left as 0x00. The caller is responsible
-  // for patching the correct package ID to the resource ID.
-  uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const;
-
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
 
-  static std::unique_ptr<LoadedPackage> Load(const Chunk& chunk, const LoadedIdmap* loaded_idmap);
+  LoadedPackage();
 
-  LoadedPackage() = default;
+  template <bool Verified>
+  bool FindEntry(const util::unique_cptr<TypeSpec>& type_spec_ptr, uint16_t entry_idx,
+                 const ResTable_config& config, FindEntryResult* out_entry) const;
 
   ResStringPool type_string_pool_;
   ResStringPool key_string_pool_;
@@ -140,6 +157,7 @@
   bool dynamic_ = false;
   bool system_ = false;
   bool overlay_ = false;
+  bool verified_ = true;
 
   ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_;
   std::vector<DynamicPackageEntry> dynamic_package_map_;
@@ -163,8 +181,6 @@
   // Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
   static std::unique_ptr<const LoadedArsc> CreateEmpty();
 
-  ~LoadedArsc();
-
   // Returns the string pool where all string resource values
   // (Res_value::dataType == Res_value::TYPE_STRING) are indexed.
   inline const ResStringPool* GetStringPool() const {
@@ -175,8 +191,7 @@
   // The parameter `out_entry` will be filled with the resulting resource entry.
   // The resource entry can be a simple entry (ResTable_entry) or a complex bag
   // (ResTable_entry_map).
-  bool FindEntry(uint32_t resid, const ResTable_config& config, LoadedArscEntry* out_entry,
-                 ResTable_config* selected_config, uint32_t* out_flags) const;
+  bool FindEntry(uint32_t resid, const ResTable_config& config, FindEntryResult* out_entry) const;
 
   // Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist.
   const LoadedPackage* GetPackageForId(uint32_t resid) const;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 8f858b6..8547955 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -546,15 +546,15 @@
  */
 class StringPoolRef {
 public:
-    StringPoolRef();
-    StringPoolRef(const ResStringPool* pool, uint32_t index);
+ StringPoolRef() = default;
+ StringPoolRef(const ResStringPool* pool, uint32_t index);
 
-    const char* string8(size_t* outLen) const;
-    const char16_t* string16(size_t* outLen) const;
+ const char* string8(size_t* outLen) const;
+ const char16_t* string16(size_t* outLen) const;
 
 private:
-    const ResStringPool*        mPool;
-    uint32_t                    mIndex;
+ const ResStringPool* mPool = nullptr;
+ uint32_t mIndex = 0u;
 };
 
 /** ********************************************************************
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index 6bf7c24..c2eae85 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -40,7 +40,9 @@
   return static_cast<uint8_t>((resid >> 16) & 0x000000ffu);
 }
 
-inline uint16_t get_entry_id(uint32_t resid) { return static_cast<uint16_t>(resid & 0x0000ffffu); }
+inline uint16_t get_entry_id(uint32_t resid) {
+  return static_cast<uint16_t>(resid & 0x0000ffffu);
+}
 
 inline bool is_internal_resid(uint32_t resid) {
   return (resid & 0xffff0000u) != 0 && (resid & 0x00ff0000u) == 0;
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index 2766ce1..d65d93f 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -32,7 +32,13 @@
   std::unique_ptr<const ApkAssets> loaded_apk =
       ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
   ASSERT_NE(nullptr, loaded_apk);
-  EXPECT_NE(nullptr, loaded_apk->GetLoadedArsc());
+
+  const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
+  ASSERT_NE(nullptr, loaded_arsc);
+
+  const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
+  ASSERT_NE(nullptr, loaded_package);
+  EXPECT_TRUE(loaded_package->IsVerified());
 
   std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
   ASSERT_NE(nullptr, asset);
@@ -87,6 +93,20 @@
   ASSERT_NE(nullptr, loaded_overlay_apk);
 }
 
+TEST(ApkAssetsTest, LoadUnverifiableApk) {
+  std::unique_ptr<const ApkAssets> loaded_apk =
+      ApkAssets::Load(GetTestDataPath() + "/unverified/unverified.apk");
+  ASSERT_NE(nullptr, loaded_apk);
+
+  const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
+  ASSERT_NE(nullptr, loaded_arsc);
+
+  const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
+  ASSERT_NE(nullptr, loaded_package);
+
+  EXPECT_FALSE(loaded_package->IsVerified());
+}
+
 TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
   std::unique_ptr<const ApkAssets> loaded_apk =
       ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 99a07a5..739e733 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -26,10 +26,12 @@
 #include "data/basic/R.h"
 #include "data/libclient/R.h"
 #include "data/styles/R.h"
+#include "data/unverified/R.h"
 
 namespace app = com::android::app;
 namespace basic = com::android::basic;
 namespace libclient = com::android::libclient;
+namespace unverified = com::android::unverified;
 
 namespace android {
 
@@ -124,6 +126,12 @@
 }
 BENCHMARK(BM_AssetManagerGetResourceOld);
 
+static void BM_AssetManagerGetResourceUnverified(benchmark::State& state) {
+  GetResourceBenchmark({GetTestDataPath() + "/unverified/unverified.apk"}, nullptr /*config*/,
+                       unverified::R::integer::number1, state);
+}
+BENCHMARK(BM_AssetManagerGetResourceUnverified);
+
 static void BM_AssetManagerGetLibraryResource(benchmark::State& state) {
   GetResourceBenchmark(
       {GetTestDataPath() + "/lib_two/lib_two.apk", GetTestDataPath() + "/lib_one/lib_one.apk",
@@ -206,6 +214,30 @@
 }
 BENCHMARK(BM_AssetManagerGetBagOld);
 
+static void BM_AssetManagerGetBagUnverified(benchmark::State& state) {
+  std::unique_ptr<const ApkAssets> apk =
+      ApkAssets::Load(GetTestDataPath() + "/unverified/unverified.apk");
+  if (apk == nullptr) {
+    state.SkipWithError("Failed to load assets");
+    return;
+  }
+
+  AssetManager2 assets;
+  assets.SetApkAssets({apk.get()});
+
+  while (state.KeepRunning()) {
+    const ResolvedBag* bag = assets.GetBag(unverified::R::array::integerArray1);
+    const auto bag_end = end(bag);
+    for (auto iter = begin(bag); iter != bag_end; ++iter) {
+      uint32_t key = iter->key;
+      Res_value value = iter->value;
+      benchmark::DoNotOptimize(key);
+      benchmark::DoNotOptimize(value);
+    }
+  }
+}
+BENCHMARK(BM_AssetManagerGetBagUnverified);
+
 static void BM_AssetManagerGetResourceLocales(benchmark::State& state) {
   std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
   if (apk == nullptr) {
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index fcae53b..ab1a22e 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -28,6 +28,7 @@
 #include "data/libclient/R.h"
 #include "data/styles/R.h"
 #include "data/system/R.h"
+#include "data/unverified/R.h"
 
 namespace app = com::android::app;
 namespace appaslib = com::android::appaslib::app;
@@ -35,6 +36,7 @@
 namespace lib_one = com::android::lib_one;
 namespace lib_two = com::android::lib_two;
 namespace libclient = com::android::libclient;
+namespace unverified = com::android::unverified;
 
 namespace android {
 
@@ -431,4 +433,30 @@
 
 TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {}
 
+TEST_F(AssetManager2Test, OperateOnUnverifiedApkAssets) {
+  std::unique_ptr<const ApkAssets> unverified_assets =
+      ApkAssets::Load(GetTestDataPath() + "/unverified/unverified.apk");
+  ASSERT_NE(nullptr, unverified_assets);
+
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({unverified_assets.get()});
+
+  Res_value value;
+  ResTable_config config;
+  uint32_t flags;
+
+  EXPECT_EQ(kInvalidCookie,
+            assetmanager.GetResource(unverified::R::string::test1, false /*may_be_bag*/, 0u, &value,
+                                     &config, &flags));
+  EXPECT_EQ(kInvalidCookie,
+            assetmanager.GetResource(unverified::R::string::test2, false /*may_be_bag*/, 0u, &value,
+                                     &config, &flags));
+  EXPECT_NE(kInvalidCookie,
+            assetmanager.GetResource(unverified::R::integer::number1, false /*may_be_bag*/, 0u,
+                                     &value, &config, &flags));
+
+  EXPECT_EQ(nullptr, assetmanager.GetBag(unverified::R::style::Theme1));
+  EXPECT_NE(nullptr, assetmanager.GetBag(unverified::R::array::integerArray1));
+}
+
 }  // namespace android
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 2b72d14..954a54d 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -44,12 +44,9 @@
   memset(&config, 0, sizeof(config));
   config.sdkVersion = 24;
 
-  LoadedArscEntry entry;
-  ResTable_config selected_config;
-  uint32_t flags;
+  FindEntryResult entry;
 
-  ASSERT_TRUE(
-      loaded_arsc->FindEntry(app::R::string::string_one, config, &entry, &selected_config, &flags));
+  ASSERT_TRUE(loaded_arsc->FindEntry(app::R::string::string_one, config, &entry));
   ASSERT_NE(nullptr, entry.entry);
 }
 
@@ -66,12 +63,8 @@
   desired_config.language[0] = 'd';
   desired_config.language[1] = 'e';
 
-  LoadedArscEntry entry;
-  ResTable_config selected_config;
-  uint32_t flags;
-
-  ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry,
-                                     &selected_config, &flags));
+  FindEntryResult entry;
+  ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry));
   ASSERT_NE(nullptr, entry.entry);
 }
 
@@ -150,23 +143,15 @@
   ResTable_config desired_config;
   memset(&desired_config, 0, sizeof(desired_config));
 
-  LoadedArscEntry entry;
-  ResTable_config selected_config;
-  uint32_t flags;
-
-  ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry,
-                                     &selected_config, &flags));
+  FindEntryResult entry;
+  ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry));
 
   size_t len;
   const char16_t* type_name16 = entry.type_string_ref.string16(&len);
   ASSERT_NE(nullptr, type_name16);
   ASSERT_NE(0u, len);
 
-  size_t utf8_len = utf16_to_utf8_length(type_name16, len);
-  std::string type_name;
-  type_name.resize(utf8_len);
-  utf16_to_utf8(type_name16, len, &*type_name.begin(), utf8_len + 1);
-
+  std::string type_name = util::Utf16ToUtf8(StringPiece16(type_name16, len));
   EXPECT_EQ(std::string("string"), type_name);
 }
 
@@ -210,12 +195,8 @@
   ResTable_config desired_config;
   memset(&desired_config, 0, sizeof(desired_config));
 
-  LoadedArscEntry entry;
-  ResTable_config selected_config;
-  uint32_t flags;
-
-  ASSERT_TRUE(
-      loaded_arsc->FindEntry(0x08030001u, desired_config, &entry, &selected_config, &flags));
+  FindEntryResult entry;
+  ASSERT_TRUE(loaded_arsc->FindEntry(0x08030001u, desired_config, &entry));
 }
 
 // structs with size fields (like Res_value, ResTable_entry) should be
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index feb454e..55d53ed 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -128,6 +128,18 @@
   EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
 }
 
+TEST_F(ThemeTest, TryToUseBadResourceId) {
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({style_assets_.get()});
+
+  std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+  ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo));
+
+  Res_value value;
+  uint32_t flags;
+  ASSERT_EQ(kInvalidCookie, theme->GetAttribute(0x7f000001, &value, &flags));
+}
+
 TEST_F(ThemeTest, MultipleThemesOverlaidNotForce) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({style_assets_.get()});
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/libs/androidfw/tests/data/unverified/R.h
similarity index 62%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to libs/androidfw/tests/data/unverified/R.h
index 7294124..b734b49 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/libs/androidfw/tests/data/unverified/R.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,12 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.internal.widget;
+#ifndef TESTS_DATA_UNVERIFIED_R_H_
+#define TESTS_DATA_UNVERIFIED_R_H_
 
-import android.os.IBinder;
+#include <cstdint>
 
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
-    void onServiceConnected(IBinder service);
-    void onServiceDisconnected();
-}
+#include "tests/data/basic/R.h"
+
+namespace com {
+namespace android {
+
+namespace unverified = basic;
+
+}  // namespace android
+}  // namespace com
+
+#endif /* TESTS_DATA_UNVERIFIED_R_H_ */
diff --git a/libs/androidfw/tests/data/unverified/unverified.apk b/libs/androidfw/tests/data/unverified/unverified.apk
new file mode 100644
index 0000000..234b390
--- /dev/null
+++ b/libs/androidfw/tests/data/unverified/unverified.apk
Binary files differ
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 1e71cb0..6b8006c 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -31,6 +31,12 @@
 namespace uirenderer {
 
 Extensions::Extensions() {
+    if (Properties::getRenderPipelineType() != RenderPipelineType::OpenGL) {
+        //Extensions class is used only by OpenGL pipeline
+        //The code below will crash for SkiaVulkan, because OpenGL is not initialized
+        //TODO: instantiate Extensions class only for OpenGL pipeline
+        return;
+    }
     const char* version = (const char*) glGetString(GL_VERSION);
 
     // Section 6.1.5 of the OpenGL ES specification indicates the GL version
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 3b59bb1..588c4de 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -58,6 +58,7 @@
 
 bool Properties::filterOutTestOverhead = false;
 bool Properties::disableVsync = false;
+bool Properties::skpCaptureEnabled = false;
 
 static int property_get_int(const char* key, int defaultValue) {
     char buf[PROPERTY_VALUE_MAX] = {'\0',};
@@ -128,6 +129,9 @@
 
     filterOutTestOverhead = property_get_bool(PROPERTY_FILTER_TEST_OVERHEAD, false);
 
+    skpCaptureEnabled = property_get_bool("ro.debuggable", false)
+            && property_get_bool(PROPERTY_CAPTURE_SKP_ENABLED, false);
+
     return (prevDebugLayersUpdates != debugLayersUpdates)
             || (prevDebugOverdraw != debugOverdraw)
             || (prevDebugStencilClip != debugStencilClip);
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 0fe4c77..077e7ae 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -165,6 +165,22 @@
  */
 #define PROPERTY_RENDERER "debug.hwui.renderer"
 
+/**
+ * Allows to collect a recording of Skia drawing commands.
+ */
+#define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled"
+
+
+/**
+ * Defines how many frames in a sequence to capture.
+ */
+#define PROPERTY_CAPTURE_SKP_FRAMES  "debug.hwui.capture_skp_frames"
+
+/**
+ * File name and location, where a SKP recording will be saved.
+ */
+#define PROPERTY_CAPTURE_SKP_FILENAME "debug.hwui.skp_filename"
+
 ///////////////////////////////////////////////////////////////////////////////
 // Misc
 ///////////////////////////////////////////////////////////////////////////////
@@ -254,6 +270,8 @@
     // created after changing this.
     static bool disableVsync;
 
+    static bool skpCaptureEnabled;
+
     // Used for testing only to change the render pipeline.
 #ifdef HWUI_GLES_WRAP_ENABLED
     static void overrideRenderPipelineType(RenderPipelineType);
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 3b72a8b..def04e1 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -35,6 +35,10 @@
 }
 
 bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer) {
+    if (context == nullptr) {
+        SkDEBUGF(("Attempting to draw LayerDrawable into an unsupported surface"));
+        return false;
+    }
     // transform the matrix based on the layer
     SkMatrix layerTransform;
     layer->getTransform().copyTo(layerTransform);
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 47dee9d..ca93925 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -101,7 +101,7 @@
 
 void RenderNodeDrawable::forceDraw(SkCanvas* canvas) {
     RenderNode* renderNode = mRenderNode.get();
-    if (SkiaPipeline::skpCaptureEnabled()) {
+    if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
         SkRect dimensions = SkRect::MakeWH(renderNode->getWidth(), renderNode->getHeight());
         canvas->drawAnnotation(dimensions, renderNode->getName(), nullptr);
     }
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
index 9982a0c..75967e9 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
@@ -73,7 +73,7 @@
      * for reading back float buffers (skbug.com/6945).
      */
     if (pixelConfig == kRGBA_half_GrPixelConfig &&
-            !DeviceInfo::get()->extensions().hasFloatTextures()) {
+            !grContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig)) {
         ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
         return CopyResult::DestinationInvalid;
     }
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index c4bd1e1..a092cd5 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -75,6 +75,12 @@
     mPinnedImages.clear();
 }
 
+void SkiaPipeline::onPrepareTree() {
+    // The only time mVectorDrawables is not empty is if prepare tree was called 2 times without
+    // a renderFrame in the middle.
+    mVectorDrawables.clear();
+}
+
 void SkiaPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
         LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
         const BakedOpRenderer::LightInfo& lightInfo) {
@@ -106,7 +112,7 @@
 
             const Rect& layerDamage = layers.entries()[i].damage;
 
-            SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
+            SkCanvas* layerCanvas = tryCapture(layerNode->getLayerSurface());
 
             int saveCount = layerCanvas->save();
             SkASSERT(saveCount == 1);
@@ -133,9 +139,11 @@
             layerCanvas->restoreToCount(saveCount);
             mLightCenter = savedLightCenter;
 
+            endCapture(layerNode->getLayerSurface());
+
             // cache the current context so that we can defer flushing it until
             // either all the layers have been rendered or the context changes
-            GrContext* currentContext = layerCanvas->getGrContext();
+            GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
             if (cachedContext.get() != currentContext) {
                 if (cachedContext.get()) {
                     cachedContext->flush();
@@ -221,6 +229,91 @@
     }
 }
 
+class SkiaPipeline::SavePictureProcessor : public TaskProcessor<bool> {
+public:
+    explicit SavePictureProcessor(TaskManager* taskManager) : TaskProcessor<bool>(taskManager) {}
+
+    struct SavePictureTask : public Task<bool> {
+        sk_sp<SkData> data;
+        std::string filename;
+    };
+
+    void savePicture(const sk_sp<SkData>& data, const std::string& filename) {
+        sp<SavePictureTask> task(new SavePictureTask());
+        task->data = data;
+        task->filename = filename;
+        TaskProcessor<bool>::add(task);
+    }
+
+    virtual void onProcess(const sp<Task<bool> >& task) override {
+        SavePictureTask* t = static_cast<SavePictureTask*>(task.get());
+
+        if (0 == access(t->filename.c_str(), F_OK)) {
+            task->setResult(false);
+            return;
+        }
+
+        SkFILEWStream stream(t->filename.c_str());
+        if (stream.isValid()) {
+            stream.write(t->data->data(), t->data->size());
+            stream.flush();
+            SkDebugf("SKP Captured Drawing Output (%d bytes) for frame. %s",
+                    stream.bytesWritten(), t->filename.c_str());
+        }
+
+        task->setResult(true);
+    }
+};
+
+SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
+    if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
+        bool recordingPicture = mCaptureSequence > 0;
+        char prop[PROPERTY_VALUE_MAX] = {'\0'};
+        if (!recordingPicture) {
+            property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0");
+            recordingPicture = prop[0] != '0'
+                    && mCapturedFile != prop; //ensure we capture only once per filename
+            if (recordingPicture) {
+                mCapturedFile = prop;
+                mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1);
+            }
+        }
+        if (recordingPicture) {
+            mRecorder.reset(new SkPictureRecorder());
+            return mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
+                    SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+        }
+    }
+    return surface->getCanvas();
+}
+
+void SkiaPipeline::endCapture(SkSurface* surface) {
+    if (CC_UNLIKELY(mRecorder.get())) {
+        sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture();
+        surface->getCanvas()->drawPicture(picture);
+        if (picture->approximateOpCount() > 0) {
+            SkDynamicMemoryWStream stream;
+            PngPixelSerializer serializer;
+            picture->serialize(&stream, &serializer);
+
+            //offload saving to file in a different thread
+            if (!mSavePictureProcessor.get()) {
+                TaskManager* taskManager = getTaskManager();
+                mSavePictureProcessor = new SavePictureProcessor(
+                        taskManager->canRunTasks() ? taskManager : nullptr);
+            }
+            if (1 == mCaptureSequence) {
+                mSavePictureProcessor->savePicture(stream.detachAsData(), mCapturedFile);
+            } else {
+                mSavePictureProcessor->savePicture(stream.detachAsData(),
+                                        mCapturedFile + "_" + std::to_string(mCaptureSequence));
+            }
+            mCaptureSequence--;
+        }
+        mRecorder.reset();
+    }
+}
+
 void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
         const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut,
         const Rect &contentDrawBounds, sk_sp<SkSurface> surface) {
@@ -230,44 +323,21 @@
     // draw all layers up front
     renderLayersImpl(layers, opaque, wideColorGamut);
 
-    // initialize the canvas for the current frame
-    SkCanvas* canvas = surface->getCanvas();
-
+    // initialize the canvas for the current frame, that might be a recording canvas if SKP
+    // capture is enabled.
     std::unique_ptr<SkPictureRecorder> recorder;
-    bool recordingPicture = false;
-    char prop[PROPERTY_VALUE_MAX];
-    if (skpCaptureEnabled()) {
-        property_get("debug.hwui.capture_frame_as_skp", prop, "0");
-        recordingPicture = prop[0] != '0' && access(prop, F_OK) != 0;
-        if (recordingPicture) {
-            recorder.reset(new SkPictureRecorder());
-            canvas = recorder->beginRecording(surface->width(), surface->height(),
-                    nullptr, SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
-        }
-    }
+    SkCanvas* canvas = tryCapture(surface.get());
 
     renderFrameImpl(layers, clip, nodes, opaque, wideColorGamut, contentDrawBounds, canvas);
 
-    if (skpCaptureEnabled() && recordingPicture) {
-        sk_sp<SkPicture> picture = recorder->finishRecordingAsPicture();
-        if (picture->approximateOpCount() > 0) {
-            SkFILEWStream stream(prop);
-            if (stream.isValid()) {
-                PngPixelSerializer serializer;
-                picture->serialize(&stream, &serializer);
-                stream.flush();
-                SkDebugf("Captured Drawing Output (%d bytes) for frame. %s", stream.bytesWritten(), prop);
-            }
-        }
-        surface->getCanvas()->drawPicture(picture);
-    }
+    endCapture(surface.get());
 
     if (CC_UNLIKELY(Properties::debugOverdraw)) {
         renderOverdraw(layers, clip, nodes, contentDrawBounds, surface);
     }
 
     ATRACE_NAME("flush commands");
-    canvas->flush();
+    surface->getCanvas()->flush();
 }
 
 namespace {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 3e6ae30..67b1878 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -21,6 +21,8 @@
 #include "renderthread/IRenderPipeline.h"
 #include <SkSurface.h>
 
+class SkPictureRecorder;
+
 namespace android {
 namespace uirenderer {
 namespace skiapipeline {
@@ -37,6 +39,7 @@
     bool pinImages(std::vector<SkImage*>& mutableImages) override;
     bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
     void unpinImages() override;
+    void onPrepareTree() override;
 
     void renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
             LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
@@ -55,9 +58,7 @@
 
     static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
 
-    static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, bool wideColorGamut);
-
-    static bool skpCaptureEnabled() { return false; }
+    void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, bool wideColorGamut);
 
     static float getLightRadius() {
         if (CC_UNLIKELY(Properties::overrideLightRadius > 0)) {
@@ -126,12 +127,38 @@
      */
     void renderVectorDrawableCache();
 
+    SkCanvas* tryCapture(SkSurface* surface);
+    void endCapture(SkSurface* surface);
+
     std::vector<sk_sp<SkImage>> mPinnedImages;
 
     /**
      *  populated by prepareTree with dirty VDs
      */
     std::vector<VectorDrawableRoot*> mVectorDrawables;
+
+
+    // Block of properties used only for debugging to record a SkPicture and save it in a file.
+    /**
+     * mCapturedFile is used to enforce we don't capture more than once for a given name (cause
+     * permissions don't allow to reset a property from render thread).
+     */
+    std::string mCapturedFile;
+    /**
+     *  mCaptureSequence counts how many frames are left to take in the sequence.
+     */
+    int mCaptureSequence = 0;
+    /**
+     *  mSavePictureProcessor is used to run the file saving code in a separate thread.
+     */
+    class SavePictureProcessor;
+    sp<SavePictureProcessor> mSavePictureProcessor;
+    /**
+     *  mRecorder holds the current picture recorder. We could store it on the stack to support
+     *  parallel tryCapture calls (not really needed).
+     */
+    std::unique_ptr<SkPictureRecorder> mRecorder;
+
     static float mLightRadius;
     static uint8_t mAmbientShadowAlpha;
     static uint8_t mSpotShadowAlpha;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 5d7f594..ad684db 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -331,6 +331,7 @@
     info.layerUpdateQueue = &mLayerUpdateQueue;
 
     mAnimationContext->startFrame(info.mode);
+    mRenderPipeline->onPrepareTree();
     for (const sp<RenderNode>& node : mRenderNodes) {
         // Only the primary target node will be drawn full - all other nodes would get drawn in
         // real time mode. In case of a window, the primary node is the window content and the other
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index f9b6e38..0bb3889 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -81,6 +81,7 @@
     virtual bool pinImages(std::vector<SkImage*>& mutableImages) = 0;
     virtual bool pinImages(LsaVector<sk_sp<Bitmap>>& images) = 0;
     virtual void unpinImages() = 0;
+    virtual void onPrepareTree() = 0;
 
     virtual ~IRenderPipeline() {}
 };
diff --git a/libs/hwui/renderthread/OpenGLPipeline.h b/libs/hwui/renderthread/OpenGLPipeline.h
index 4ca19fb..1f467c1 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.h
+++ b/libs/hwui/renderthread/OpenGLPipeline.h
@@ -58,6 +58,7 @@
     bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
     bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override;
     void unpinImages() override;
+    void onPrepareTree() override {}
     static void destroyLayer(RenderNode* node);
     static void prepareToDraw(const RenderThread& thread, Bitmap* bitmap);
     static void invokeFunctor(const RenderThread& thread, Functor* functor);
diff --git a/libs/hwui/tests/scripts/skp-capture.sh b/libs/hwui/tests/scripts/skp-capture.sh
new file mode 100755
index 0000000..54fa229
--- /dev/null
+++ b/libs/hwui/tests/scripts/skp-capture.sh
@@ -0,0 +1,116 @@
+#!/bin/sh
+
+# Copyright 2015 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if [ -z "$1" ]; then
+    printf 'Usage:\n    skp-capture.sh PACKAGE_NAME OPTIONAL_FRAME_COUNT\n\n'
+    printf "Use \`adb shell 'pm list packages'\` to get a listing.\n\n"
+    exit 1
+fi
+if ! command -v adb > /dev/null 2>&1; then
+    if [ -x "${ANDROID_SDK_ROOT}/platform-tools/adb" ]; then
+        adb() {
+            "${ANDROID_SDK_ROOT}/platform-tools/adb" "$@"
+        }
+    else
+        echo 'adb missing'
+        exit 2
+    fi
+fi
+phase1_timeout_seconds=15
+phase2_timeout_seconds=60
+package="$1"
+filename="$(date '+%H%M%S').skp"
+remote_path="/data/data/${package}/cache/${filename}"
+local_path_prefix="$(date '+%Y-%m-%d_%H%M%S')_${package}"
+local_path="${local_path_prefix}.skp"
+enable_capture_key='debug.hwui.capture_skp_enabled'
+enable_capture_value=$(adb shell "getprop '${enable_capture_key}'")
+#printf 'captureflag=' "$enable_capture_value" '\n'
+if [ -z "$enable_capture_value" ]; then
+    printf 'Capture SKP property need to be enabled first. Please use\n'
+    printf "\"adb shell setprop debug.hwui.capture_skp_enabled true\" and then restart\n"
+    printf "the process.\n\n"
+    exit 1
+fi
+if [ ! -z "$2" ]; then
+    adb shell "setprop 'debug.hwui.capture_skp_frames' $2"
+fi
+filename_key='debug.hwui.skp_filename'
+adb shell "setprop '${filename_key}' '${remote_path}'"
+spin() {
+    case "$spin" in
+         1) printf '\b|';;
+         2) printf '\b\\';;
+         3) printf '\b-';;
+         *) printf '\b/';;
+    esac
+    spin=$(( ( ${spin:-0} + 1 ) % 4 ))
+    sleep $1
+}
+
+banner() {
+    printf '\n=====================\n'
+    printf '   %s' "$*"
+    printf '\n=====================\n'
+}
+banner '...WAITING...'
+adb_test_exist() {
+    test '0' = "$(adb shell "test -e \"$1\"; echo \$?")";
+}
+timeout=$(( $(date +%s) + $phase1_timeout_seconds))
+while ! adb_test_exist "$remote_path"; do
+    spin 0.05
+    if [ $(date +%s) -gt $timeout ] ; then
+        printf '\bTimed out.\n'
+        adb shell "setprop '${filename_key}' ''"
+        exit 3
+    fi
+done
+printf '\b'
+
+#read -n1 -r -p "Press any key to continue..." key
+
+banner '...SAVING...'
+adb_test_file_nonzero() {
+    # grab first byte of `du` output
+    X="$(adb shell "du \"$1\" 2> /dev/null | dd bs=1 count=1 2> /dev/null")"
+    test "$X" && test "$X" -ne 0
+}
+#adb_filesize() {
+#    adb shell "wc -c \"$1\"" 2> /dev/null | awk '{print $1}'
+#}
+timeout=$(( $(date +%s) + $phase2_timeout_seconds))
+while ! adb_test_file_nonzero "$remote_path"; do
+    spin 0.05
+    if [ $(date +%s) -gt $timeout ] ; then
+        printf '\bTimed out.\n'
+        adb shell "setprop '${filename_key}' ''"
+        exit 3
+    fi
+done
+printf '\b'
+
+adb shell "setprop '${filename_key}' ''"
+
+i=0; while [ $i -lt 10 ]; do spin 0.10; i=$(($i + 1)); done; echo
+
+adb pull "$remote_path" "$local_path"
+if ! [ -f "$local_path" ] ; then
+    printf "something went wrong with `adb pull`."
+    exit 4
+fi
+adb shell rm "$remote_path"
+printf '\nSKP saved to file:\n    %s\n\n'  "$local_path"
+if [ ! -z "$2" ]; then
+    bridge="_"
+    adb shell "setprop 'debug.hwui.capture_skp_frames' ''"
+    for i in $(seq 2 $2); do
+        adb pull "${remote_path}_${i}" "${local_path_prefix}_${i}.skp"
+        adb shell rm "${remote_path}_${i}"
+    done
+fi
+
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 4c3e182..d182d78 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -22,6 +22,7 @@
 #include "IContextFactory.h"
 #include "pipeline/skia/SkiaDisplayList.h"
 #include "pipeline/skia/SkiaPipeline.h"
+#include "pipeline/skia/SkiaOpenGLPipeline.h"
 #include "pipeline/skia/SkiaRecordingCanvas.h"
 #include "renderthread/CanvasContext.h"
 #include "tests/common/TestUtils.h"
@@ -335,7 +336,7 @@
     EXPECT_EQ(3, canvas.getIndex());
 }
 
-RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
     /* R is backward projected on B and C is a layer.
                 A
                / \
@@ -450,7 +451,8 @@
     LayerUpdateQueue layerUpdateQueue;
     layerUpdateQueue.enqueueLayerWithDamage(child.get(),
             android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT));
-    SkiaPipeline::renderLayersImpl(layerUpdateQueue, true, false);
+    auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
+    pipeline->renderLayersImpl(layerUpdateQueue, true, false);
     EXPECT_EQ(1, drawCounter);  //assert index 0 is drawn on the layer
 
     RenderNodeDrawable drawable(parent.get(), surfaceLayer1->getCanvas(), true);
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index b397b15..f430ce6 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -56,6 +56,40 @@
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
 }
 
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, testOnPrepareTree) {
+
+    auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1,
+        [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
+            redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
+        });
+
+    LayerUpdateQueue layerUpdateQueue;
+    SkRect dirty = SkRect::MakeLargest();
+    std::vector<sp<RenderNode>> renderNodes;
+    renderNodes.push_back(redNode);
+    bool opaque = true;
+    android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
+    auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
+    {
+        //add a pointer to a deleted vector drawable object in the pipeline
+        sp<VectorDrawableRoot> dirtyVD(new VectorDrawableRoot(new VectorDrawable::Group()));
+        dirtyVD->mutateProperties()->setScaledSize(5,5);
+        pipeline->getVectorDrawables()->push_back(dirtyVD.get());
+    }
+
+    //pipeline should clean list of dirty vector drawables before prepare tree
+    pipeline->onPrepareTree();
+
+    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+    surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
+    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
+
+    //drawFrame will crash if "SkiaPipeline::onPrepareTree" did not clean invalid VD pointer
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes,
+            opaque, false, contentDrawBounds, surface);
+    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
+}
+
 RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) {
     auto halfGreenNode = TestUtils::createSkiaNode(0, 0, 2, 2,
         [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
diff --git a/libs/services/Android.mk b/libs/services/Android.mk
index cbfd4b3..d72059a 100644
--- a/libs/services/Android.mk
+++ b/libs/services/Android.mk
@@ -21,7 +21,8 @@
 LOCAL_MODULE := libservices
 LOCAL_SRC_FILES := \
     ../../core/java/com/android/internal/os/IDropBoxManagerService.aidl \
-    src/os/DropBoxManager.cpp
+    src/os/DropBoxManager.cpp \
+    src/os/StatsLogEventWrapper.cpp
 
 LOCAL_AIDL_INCLUDES := \
     $(LOCAL_PATH)/../../core/java
diff --git a/libs/services/include/android/os/StatsLogEventWrapper.h b/libs/services/include/android/os/StatsLogEventWrapper.h
new file mode 100644
index 0000000..255619c
--- /dev/null
+++ b/libs/services/include/android/os/StatsLogEventWrapper.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef STATS_LOG_EVENT_WRAPPER_H
+#define STATS_LOG_EVENT_WRAPPER_H
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <binder/Status.h>
+#include <utils/RefBase.h>
+#include <vector>
+
+namespace android {
+namespace os {
+
+// Represents a parcelable object. Only used to send data from Android OS to statsd.
+class StatsLogEventWrapper : public android::Parcelable {
+ public:
+  StatsLogEventWrapper();
+
+  StatsLogEventWrapper(StatsLogEventWrapper&& in) = default;
+
+  android::status_t writeToParcel(android::Parcel* out) const;
+
+  android::status_t readFromParcel(const android::Parcel* in);
+
+  // These are public for ease of conversion.
+  std::vector<uint8_t> bytes;
+};
+} // Namespace os
+} // Namespace android
+
+
+#endif  // STATS_LOG_EVENT_WRAPPER_H
+
diff --git a/libs/services/src/os/StatsLogEventWrapper.cpp b/libs/services/src/os/StatsLogEventWrapper.cpp
new file mode 100644
index 0000000..8b3aa9a
--- /dev/null
+++ b/libs/services/src/os/StatsLogEventWrapper.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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 <android/os/StatsLogEventWrapper.h>
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <binder/Status.h>
+#include <utils/RefBase.h>
+#include <vector>
+
+using android::Parcel;
+using android::Parcelable;
+using android::status_t;
+using std::vector;
+
+namespace android {
+namespace os {
+
+StatsLogEventWrapper::StatsLogEventWrapper(){};
+
+status_t StatsLogEventWrapper::writeToParcel(Parcel* out) const {
+    out->writeByteVector(bytes);
+    return ::android::NO_ERROR;
+};
+
+status_t StatsLogEventWrapper::readFromParcel(const Parcel* in) {
+    in->readByteVector(&bytes);
+    return ::android::NO_ERROR;
+};
+
+} // Namespace os
+} // Namespace android
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 8c1010d..33c5490 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -138,9 +138,10 @@
 </pre>
 
 
-<p>Note that &ldquo;input&rdquo; and &ldquo;output&rdquo; are from the standpoint of the device. So a
-synthesizer will have an &ldquo;input&rdquo; port that receives messages. A keyboard will
-have an &ldquo;output&rdquo; port that sends messages.</p>
+<p>Note that &ldquo;input&rdquo; and &ldquo;output&rdquo; directions reflect the point of view
+of the MIDI device itself, not your app.
+For example, to send MIDI notes to a synthesizer, open the synth's INPUT port.
+To receive notes from a keyboard, open the keyboard's OUTPUT port.</p>
 
 <p>The MidiDeviceInfo has a bundle of properties.</p>
 
@@ -359,8 +360,10 @@
 <p>MIDI devices can be connected to Android using Bluetooth LE.</p>
 
 <p>Before using the device, the app must scan for available BTLE devices and then allow
-the user to connect. An example program
-will be provided so look for it on the Android developer website.</p>
+the user to connect.
+See the Android developer website for an
+<a href="https://source.android.com/devices/audio/midi_test#apps" target="_blank">example
+program</a>.</p>
 
 <h2 id=btle_location_permissions>Request Location Permission for BTLE</h2>
 
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 9f2c08e..aa0d0cc 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -20,8 +20,10 @@
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.media.projection.IMediaProjection;
 import android.os.Handler;
 import android.os.IBinder;
@@ -71,8 +73,11 @@
      */
     public Intent createScreenCaptureIntent() {
         Intent i = new Intent();
-        i.setClassName("com.android.systemui",
-                "com.android.systemui.media.MediaProjectionPermissionActivity");
+        final ComponentName mediaProjectionPermissionDialogComponent =
+                ComponentName.unflattenFromString(mContext.getResources().getString(
+                        com.android.internal.R.string
+                        .config_mediaProjectionPermissionDialogComponent));
+        i.setComponent(mediaProjectionPermissionDialogComponent);
         return i;
     }
 
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 17b2326..aaf18e7 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -471,10 +471,14 @@
                     if (parent == 0xFFFFFFFF) {
                         // all objects in root of store
                         parent = 0;
+                        where = STORAGE_PARENT_WHERE;
+                        whereArgs = new String[]{Integer.toString(storageID),
+                                Integer.toString(parent)};
+                    }  else {
+                        // If a parent is specified, the storage is redundant
+                        where = PARENT_WHERE;
+                        whereArgs = new String[]{Integer.toString(parent)};
                     }
-                    where = STORAGE_PARENT_WHERE;
-                    whereArgs = new String[] { Integer.toString(storageID),
-                                               Integer.toString(parent) };
                 }
             } else {
                 // query specific format
@@ -487,11 +491,16 @@
                     if (parent == 0xFFFFFFFF) {
                         // all objects in root of store
                         parent = 0;
+                        where = STORAGE_FORMAT_PARENT_WHERE;
+                        whereArgs = new String[]{Integer.toString(storageID),
+                                Integer.toString(format),
+                                Integer.toString(parent)};
+                    } else {
+                        // If a parent is specified, the storage is redundant
+                        where = FORMAT_PARENT_WHERE;
+                        whereArgs = new String[]{Integer.toString(format),
+                                Integer.toString(parent)};
                     }
-                    where = STORAGE_FORMAT_PARENT_WHERE;
-                    whereArgs = new String[] { Integer.toString(storageID),
-                                               Integer.toString(format),
-                                               Integer.toString(parent) };
                 }
             }
         }
@@ -845,7 +854,7 @@
         return MtpConstants.RESPONSE_OK;
     }
 
-    private int moveObject(int handle, int newParent, String newPath) {
+    private int moveObject(int handle, int newParent, int newStorage, String newPath) {
         String[] whereArgs = new String[] {  Integer.toString(handle) };
 
         // do not allow renaming any of the special subdirectories
@@ -857,6 +866,7 @@
         ContentValues values = new ContentValues();
         values.put(Files.FileColumns.DATA, newPath);
         values.put(Files.FileColumns.PARENT, newParent);
+        values.put(Files.FileColumns.STORAGE_ID, newStorage);
         int updated = 0;
         try {
             // note - we are relying on a special case in MediaProvider.update() to update
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 7225f10..0da6289 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -180,7 +180,7 @@
     virtual MtpProperty*            getDevicePropertyDesc(MtpDeviceProperty property);
 
     virtual MtpResponseCode         moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
-                                            MtpString& newPath);
+                                            MtpStorageID newStorage, MtpString& newPath);
 
     virtual void                    sessionStarted();
 
@@ -1000,11 +1000,11 @@
 }
 
 MtpResponseCode MyMtpDatabase::moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
-        MtpString &newPath) {
+        MtpStorageID newStorage, MtpString &newPath) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     jstring stringValue = env->NewStringUTF((const char *) newPath);
     MtpResponseCode result = env->CallIntMethod(mDatabase, method_moveObject,
-                (jint)handle, (jint)newParent, stringValue);
+                (jint)handle, (jint)newParent, (jint) newStorage, stringValue);
 
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     env->DeleteLocalRef(stringValue);
@@ -1376,7 +1376,7 @@
         ALOGE("Can't find deleteFile");
         return -1;
     }
-    method_moveObject = env->GetMethodID(clazz, "moveObject", "(IILjava/lang/String;)I");
+    method_moveObject = env->GetMethodID(clazz, "moveObject", "(IIILjava/lang/String;)I");
     if (method_moveObject == NULL) {
         ALOGE("Can't find moveObject");
         return -1;
diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp
index e9e9309..6ce104d 100644
--- a/media/jni/android_mtp_MtpServer.cpp
+++ b/media/jni/android_mtp_MtpServer.cpp
@@ -72,7 +72,7 @@
     const char *deviceInfoDeviceVersionStr = env->GetStringUTFChars(deviceInfoDeviceVersion, NULL);
     const char *deviceInfoSerialNumberStr = env->GetStringUTFChars(deviceInfoSerialNumber, NULL);
     MtpServer* server = new MtpServer(getMtpDatabase(env, javaDatabase),
-            usePtp, AID_MEDIA_RW, 0664, 0775,
+            usePtp,
             MtpString((deviceInfoManufacturerStr != NULL) ? deviceInfoManufacturerStr : ""),
             MtpString((deviceInfoModelStr != NULL) ? deviceInfoModelStr : ""),
             MtpString((deviceInfoDeviceVersionStr != NULL) ? deviceInfoDeviceVersionStr : ""),
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 3d083b1..a404759 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -525,6 +525,12 @@
     <!-- [CHAR LIMIT=NONE] Label for displaying Bluetooth Audio Codec Parameters while streaming -->
     <string name="bluetooth_select_a2dp_codec_streaming_label">Streaming: <xliff:g id="streaming_parameter">%1$s</xliff:g></string>
 
+    <!-- Title of the developer option for DNS over TLS. -->
+    <string name="dns_tls">DNS over TLS</string>
+    <!-- Summary to explain the developer option for DNS over TLS.  This allows the user to
+    request that the system attempt TLS with all DNS servers, or none. -->
+    <string name="dns_tls_summary">If enabled, attempt DNS over TLS on port 853.</string>
+
     <!-- setting Checkbox summary whether to show options for wireless display certification  -->
     <string name="wifi_display_certification_summary">Show options for wireless display certification</string>
     <!-- Setting Checkbox summary whether to enable Wifi verbose Logging [CHAR LIMIT=80] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
new file mode 100644
index 0000000..7162121
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
@@ -0,0 +1,8 @@
+# Default reviewers for this and subdirectories.
+asapperstein@google.com
+asargent@google.com
+eisenbach@google.com
+jackqdyulei@google.com
+siyuanh@google.com
+
+# Emergency approvers in case the above are not available
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
new file mode 100644
index 0000000..d5d2e9e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
@@ -0,0 +1,7 @@
+# Default reviewers for this and subdirectories.
+asapperstein@google.com
+asargent@google.com
+dling@google.com
+zhfan@google.com
+
+# Emergency approvers in case the above are not available
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
index c9368f3..806a073 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
@@ -115,8 +115,12 @@
             boolean isStackTask = !isFreeformTask;
             boolean isLaunchTarget = taskKey.id == runningTaskId;
 
-            // Load the title, icon, and color
             ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
+            if (info == null) {
+                continue;
+            }
+
+            // Load the title, icon, and color
             String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
             String titleDescription = loader.getAndUpdateContentDescription(taskKey,
                     t.taskDescription);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index b4cc4b1..8869e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -34,6 +34,7 @@
 import android.widget.TextView;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSTile;
 
 /**
  * Quick settings common detail view with line items.
@@ -185,7 +186,7 @@
             view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
             final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
             if (item.iconDrawable != null) {
-                iv.setImageDrawable(item.iconDrawable);
+                iv.setImageDrawable(item.iconDrawable.getDrawable(iv.getContext()));
             } else {
                 iv.setImageResource(item.icon);
             }
@@ -258,7 +259,7 @@
 
     public static class Item {
         public int icon;
-        public Drawable iconDrawable;
+        public QSTile.Icon iconDrawable;
         public Drawable overlay;
         public CharSequence line1;
         public CharSequence line2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 81b8622..bc3ccb4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -134,7 +134,9 @@
                 if (lastDevice != null) {
                     int batteryLevel = lastDevice.getBatteryLevel();
                     if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                        state.icon = new BluetoothBatteryDrawable(batteryLevel);
+                        state.icon = new BluetoothBatteryDrawable(batteryLevel,
+                                mContext.getResources().getFraction(
+                                        R.fraction.bt_battery_scale_fraction, 1, 1));
                     }
                 }
                 state.contentDescription = mContext.getString(
@@ -212,17 +214,21 @@
 
     private class BluetoothBatteryDrawable extends Icon {
         private int mLevel;
+        private float mIconScale;
 
         BluetoothBatteryDrawable(int level) {
+            this(level, 1 /* iconScale */);
+        }
+
+        BluetoothBatteryDrawable(int level, float iconScale) {
             mLevel = level;
+            mIconScale = iconScale;
         }
 
         @Override
         public Drawable getDrawable(Context context) {
             return createLayerDrawable(context,
-                    R.drawable.ic_qs_bluetooth_connected, mLevel,
-                    context.getResources().getFraction(
-                            R.fraction.bt_battery_scale_fraction, 1, 1));
+                    R.drawable.ic_qs_bluetooth_connected, mLevel, mIconScale);
         }
     }
 
@@ -304,8 +310,7 @@
                         item.icon = R.drawable.ic_qs_bluetooth_connected;
                         int batteryLevel = device.getBatteryLevel();
                         if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                            item.iconDrawable = createLayerDrawable(mContext, item.icon,
-                                    batteryLevel);
+                            item.iconDrawable = new BluetoothBatteryDrawable(batteryLevel);
                             item.line2 = mContext.getString(
                                     R.string.quick_settings_connected_battery_level,
                                     Utils.formatPercentage(batteryLevel));
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 54eba2b..ee0c043 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -336,6 +336,34 @@
 
   // Count of connection attempts that were initiated unsuccessfully
   optional int32 num_open_network_connect_message_failed_to_send = 82;
+
+  // Histogram counting instances of scans with N many HotSpot 2.0 R1 APs
+  repeated NumConnectableNetworksBucket observed_hotspot_r1_aps_in_scan_histogram = 83;
+
+  // Histogram counting instances of scans with N many HotSpot 2.0 R2 APs
+  repeated NumConnectableNetworksBucket observed_hotspot_r2_aps_in_scan_histogram = 84;
+
+  // Histogram counting instances of scans with N many unique HotSpot 2.0 R1 ESS.
+  // Where ESS is defined as the (HESSID, ANQP Domain ID), (SSID, ANQP Domain ID) or
+  // (SSID, BSSID) tuple depending on AP configuration (in the above priority
+  // order).
+  repeated NumConnectableNetworksBucket observed_hotspot_r1_ess_in_scan_histogram = 85;
+
+  // Histogram counting instances of scans with N many unique HotSpot 2.0 R2 ESS.
+  // Where ESS is defined as the (HESSID, ANQP Domain ID), (SSID, ANQP Domain ID) or
+  // (SSID, BSSID) tuple depending on AP configuration (in the above priority
+  // order).
+  repeated NumConnectableNetworksBucket observed_hotspot_r2_ess_in_scan_histogram = 86;
+
+  // Histogram counting number of HotSpot 2.0 R1 APs per observed ESS in a scan
+  // (one value added per unique ESS - potentially multiple counts per single
+  // scan!)
+  repeated NumConnectableNetworksBucket observed_hotspot_r1_aps_per_ess_in_scan_histogram = 87;
+
+  // Histogram counting number of HotSpot 2.0 R2 APs per observed ESS in a scan
+  // (one value added per unique ESS - potentially multiple counts per single
+  // scan!)
+  repeated NumConnectableNetworksBucket observed_hotspot_r2_aps_per_ess_in_scan_histogram = 88;
 }
 
 // Information that gets logged for every WiFi connection.
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 51afada..76e7782 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -21,9 +21,12 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -99,7 +102,6 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.widget.IRemoteViewsAdapterConnection;
 import com.android.internal.widget.IRemoteViewsFactory;
 import com.android.server.LocalServices;
 import com.android.server.WidgetBackupProvider;
@@ -191,10 +193,6 @@
         }
     };
 
-    // Manages active connections to RemoteViewsServices.
-    private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
-            mBoundRemoteViewsServices = new HashMap<>();
-
     // Manages persistent references to RemoteViewsServices from different App Widgets.
     private final HashMap<Pair<Integer, FilterComparison>, HashSet<Integer>>
             mRemoteViewsServicesAppWidgets = new HashMap<>();
@@ -1209,17 +1207,14 @@
     }
 
     @Override
-    public void bindRemoteViewsService(String callingPackage, int appWidgetId,
-            Intent intent, IBinder callbacks) {
+    public boolean bindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent,
+            IApplicationThread caller, IBinder activtiyToken, IServiceConnection connection,
+            int flags) {
         final int userId = UserHandle.getCallingUserId();
-
         if (DEBUG) {
             Slog.i(TAG, "bindRemoteViewsService() " + userId);
         }
 
-        // Make sure the package runs under the caller uid.
-        mSecurityPolicy.enforceCallFromPackage(callingPackage);
-
         synchronized (mLock) {
             ensureGroupStateLoadedLocked(userId);
 
@@ -1254,76 +1249,35 @@
             mSecurityPolicy.enforceServiceExistsAndRequiresBindRemoteViewsPermission(
                     componentName, widget.provider.getUserId());
 
-            // Good to go - the service pakcage is correct, it exists for the correct
+            // Good to go - the service package is correct, it exists for the correct
             // user, and requires the bind permission.
 
-            // If there is already a connection made for this service intent, then
-            // disconnect from that first. (This does not allow multiple connections
-            // to the same service under the same key).
-            ServiceConnectionProxy connection = null;
-            FilterComparison fc = new FilterComparison(intent);
-            Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc);
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                // Ask ActivityManager to bind it. Notice that we are binding the service with the
+                // caller app instead of DevicePolicyManagerService.
+                if(ActivityManager.getService().bindService(
+                        caller, activtiyToken, intent,
+                        intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                        connection, flags, mContext.getOpPackageName(),
+                        widget.provider.getUserId()) != 0) {
 
-            if (mBoundRemoteViewsServices.containsKey(key)) {
-                connection = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
-                connection.disconnect();
-                unbindService(connection);
-                mBoundRemoteViewsServices.remove(key);
-            }
-
-            // Bind to the RemoteViewsService (which will trigger a callback to the
-            // RemoteViewsAdapter.onServiceConnected())
-            connection = new ServiceConnectionProxy(callbacks);
-            bindService(intent, connection, widget.provider.info.getProfile());
-            mBoundRemoteViewsServices.put(key, connection);
-
-            // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
-            // can determine when we can call back to the RemoteViewsService later to
-            // destroy associated factories.
-            Pair<Integer, FilterComparison> serviceId = Pair.create(widget.provider.id.uid, fc);
-            incrementAppWidgetServiceRefCount(appWidgetId, serviceId);
-        }
-    }
-
-    @Override
-    public void unbindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent) {
-        final int userId = UserHandle.getCallingUserId();
-
-        if (DEBUG) {
-            Slog.i(TAG, "unbindRemoteViewsService() " + userId);
-        }
-
-        // Make sure the package runs under the caller uid.
-        mSecurityPolicy.enforceCallFromPackage(callingPackage);
-
-        synchronized (mLock) {
-            ensureGroupStateLoadedLocked(userId);
-
-            // Unbind from the RemoteViewsService (which will trigger a callback to the bound
-            // RemoteViewsAdapter)
-            Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
-                    new FilterComparison(intent));
-            if (mBoundRemoteViewsServices.containsKey(key)) {
-                // We don't need to use the appWidgetId until after we are sure there is something
-                // to unbind. Note that this may mask certain issues with apps calling unbind()
-                // more than necessary.
-
-                // NOTE: The lookup is enforcing security across users by making
-                // sure the caller can only access widgets it hosts or provides.
-                Widget widget = lookupWidgetLocked(appWidgetId,
-                        Binder.getCallingUid(), callingPackage);
-
-                if (widget == null) {
-                    throw new IllegalArgumentException("Bad widget id " + appWidgetId);
+                    // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
+                    // can determine when we can call back to the RemoteViewsService later to
+                    // destroy associated factories.
+                    incrementAppWidgetServiceRefCount(appWidgetId,
+                            Pair.create(widget.provider.id.uid, new FilterComparison(intent)));
+                    return true;
                 }
-
-                ServiceConnectionProxy connection = (ServiceConnectionProxy)
-                        mBoundRemoteViewsServices.get(key);
-                connection.disconnect();
-                mContext.unbindService(connection);
-                mBoundRemoteViewsServices.remove(key);
+            } catch (RemoteException ex) {
+                // Same process, should not happen.
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
             }
         }
+
+        // Failed to bind.
+        return false;
     }
 
     @Override
@@ -1754,7 +1708,9 @@
 
     private void deleteAppWidgetLocked(Widget widget) {
         // We first unbind all services that are bound to this id
-        unbindAppWidgetRemoteViewsServicesLocked(widget);
+        // Check if we need to destroy any services (if no other app widgets are
+        // referencing the same service)
+        decrementAppWidgetServiceRefCount(widget);
 
         Host host = widget.host;
         host.widgets.remove(widget);
@@ -1796,28 +1752,6 @@
         }
     }
 
-    // Unbinds from a RemoteViewsService when we delete an app widget
-    private void unbindAppWidgetRemoteViewsServicesLocked(Widget widget) {
-        int appWidgetId = widget.appWidgetId;
-        // Unbind all connections to Services bound to this AppWidgetId
-        Iterator<Pair<Integer, Intent.FilterComparison>> it = mBoundRemoteViewsServices.keySet()
-                .iterator();
-        while (it.hasNext()) {
-            final Pair<Integer, Intent.FilterComparison> key = it.next();
-            if (key.first == appWidgetId) {
-                final ServiceConnectionProxy conn = (ServiceConnectionProxy)
-                        mBoundRemoteViewsServices.get(key);
-                conn.disconnect();
-                mContext.unbindService(conn);
-                it.remove();
-            }
-        }
-
-        // Check if we need to destroy any services (if no other app widgets are
-        // referencing the same service)
-        decrementAppWidgetServiceRefCount(widget);
-    }
-
     // Destroys the cached factory on the RemoteViewsService's side related to the specified intent
     private void destroyRemoteViewsService(final Intent intent, Widget widget) {
         final ServiceConnection conn = new ServiceConnection() {
@@ -1853,7 +1787,7 @@
     // Adds to the ref-count for a given RemoteViewsService intent
     private void incrementAppWidgetServiceRefCount(int appWidgetId,
             Pair<Integer, FilterComparison> serviceId) {
-        HashSet<Integer> appWidgetIds = null;
+        final HashSet<Integer> appWidgetIds;
         if (mRemoteViewsServicesAppWidgets.containsKey(serviceId)) {
             appWidgetIds = mRemoteViewsServicesAppWidgets.get(serviceId);
         } else {
@@ -4055,40 +3989,6 @@
         }
     }
 
-    /**
-     * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. This
-     * needs to be a static inner class since a reference to the ServiceConnection is held globally
-     * and may lead us to leak AppWidgetService instances (if there were more than one).
-     */
-    private static final class ServiceConnectionProxy implements ServiceConnection {
-        private final IRemoteViewsAdapterConnection mConnectionCb;
-
-        ServiceConnectionProxy(IBinder connectionCb) {
-            mConnectionCb = IRemoteViewsAdapterConnection.Stub
-                    .asInterface(connectionCb);
-        }
-
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            try {
-                mConnectionCb.onServiceConnected(service);
-            } catch (RemoteException re) {
-                Slog.e(TAG, "Error passing service interface", re);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName name) {
-            disconnect();
-        }
-
-        public void disconnect() {
-            try {
-                mConnectionCb.onServiceDisconnected();
-            } catch (RemoteException re) {
-                Slog.e(TAG, "Error clearing service interface", re);
-            }
-        }
-    }
-
     private class LoadedWidgetState {
         final Widget widget;
         final int hostTag;
@@ -4642,7 +4542,9 @@
                         // reconstructed due to the restore
                         host.widgets.remove(widget);
                         provider.widgets.remove(widget);
-                        unbindAppWidgetRemoteViewsServicesLocked(widget);
+                        // Check if we need to destroy any services (if no other app widgets are
+                        // referencing the same service)
+                        decrementAppWidgetServiceRefCount(widget);
                         removeWidgetLocked(widget);
                     }
                 }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 880f236..075c741 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -52,6 +52,7 @@
 import android.service.autofill.FillResponse;
 import android.service.autofill.IAutoFillService;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.util.LocalLog;
@@ -338,6 +339,8 @@
             return;
         }
 
+        session.logContextCommittedLocked();
+
         final boolean finished = session.showSaveLocked();
         if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
 
@@ -563,8 +566,9 @@
     void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState) {
         synchronized (mLock) {
             if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
-                mEventHistory
-                        .addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState));
+                mEventHistory.addEvent(
+                        new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
+                                null, null, null, null));
             }
         }
     }
@@ -578,7 +582,7 @@
             if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
-                                clientState));
+                                clientState, null, null, null, null, null, null));
             }
         }
     }
@@ -589,7 +593,8 @@
     void logSaveShown(int sessionId, @Nullable Bundle clientState) {
         synchronized (mLock) {
             if (isValidEventLocked("logSaveShown()", sessionId)) {
-                mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState));
+                mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
+                        null, null, null, null, null));
             }
         }
     }
@@ -602,7 +607,28 @@
         synchronized (mLock) {
             if (isValidEventLocked("logDatasetSelected()", sessionId)) {
                 mEventHistory.addEvent(
-                        new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState));
+                        new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
+                                null, null, null, null, null));
+            }
+        }
+    }
+
+    /**
+     * Updates the last fill response when an autofill context is committed.
+     */
+    void logContextCommitted(int sessionId, @Nullable Bundle clientState,
+            @Nullable ArrayList<String> selectedDatasets,
+            @Nullable ArraySet<String> ignoredDatasets,
+            @Nullable ArrayList<AutofillId> changedFieldIds,
+            @Nullable ArrayList<String> changedDatasetIds,
+            @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
+            @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) {
+        synchronized (mLock) {
+            if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
+                mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
+                        clientState, selectedDatasets, ignoredDatasets,
+                        changedFieldIds, changedDatasetIds,
+                        manuallyFilledFieldIds, manuallyFilledDatasetIds));
             }
         }
     }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 3c12d67..b720f74 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -54,6 +54,7 @@
 import android.service.autofill.AutofillService;
 import android.service.autofill.Dataset;
 import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
 import android.service.autofill.InternalSanitizer;
@@ -90,6 +91,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -840,6 +842,191 @@
     }
 
     /**
+     * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
+     * when necessary.
+     */
+    public void logContextCommittedLocked() {
+        final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
+        if (lastResponse == null) return;
+
+        final int flags = lastResponse.getFlags();
+        if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
+            if (sDebug) Slog.d(TAG, "logContextCommittedLocked(): ignored by flags " + flags);
+            return;
+        }
+
+        ArraySet<String> ignoredDatasets = null;
+        ArrayList<AutofillId> changedFieldIds = null;
+        ArrayList<String> changedDatasetIds = null;
+        ArrayMap<AutofillId, ArraySet<String>> manuallyFilledIds = null;
+
+        boolean hasAtLeastOneDataset = false;
+        final int responseCount = mResponses.size();
+        for (int i = 0; i < responseCount; i++) {
+            final FillResponse response = mResponses.valueAt(i);
+            final List<Dataset> datasets = response.getDatasets();
+            if (datasets == null || datasets.isEmpty()) {
+                if (sVerbose) Slog.v(TAG,  "logContextCommitted() no datasets at " + i);
+            } else {
+                for (int j = 0; j < datasets.size(); j++) {
+                    final Dataset dataset = datasets.get(j);
+                    final String datasetId = dataset.getId();
+                    if (datasetId == null) {
+                        if (sVerbose) {
+                            Slog.v(TAG, "logContextCommitted() skipping idless dataset " + dataset);
+                        }
+                    } else {
+                        hasAtLeastOneDataset = true;
+                        if (mSelectedDatasetIds == null
+                                || !mSelectedDatasetIds.contains(datasetId)) {
+                            if (sVerbose) Slog.v(TAG, "adding ignored dataset " + datasetId);
+                            if (ignoredDatasets == null) {
+                                ignoredDatasets = new ArraySet<>();
+                            }
+                            ignoredDatasets.add(datasetId);
+                        }
+                    }
+                }
+            }
+        }
+        if (!hasAtLeastOneDataset) {
+            if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets)");
+            return;
+        }
+
+        for (int i = 0; i < mViewStates.size(); i++) {
+            final ViewState viewState = mViewStates.valueAt(i);
+            final int state = viewState.getState();
+
+            // When value changed, we need to log if it was:
+            // - autofilled -> changedDatasetIds
+            // - not autofilled but matches a dataset value -> manuallyFilledIds
+            if ((state & ViewState.STATE_CHANGED) != 0) {
+
+                // Check if autofilled value was changed
+                if ((state & ViewState.STATE_AUTOFILLED) != 0) {
+                    final String datasetId = viewState.getDatasetId();
+                    if (datasetId == null) {
+                        // Sanity check - should never happen.
+                        Slog.w(TAG, "logContextCommitted(): no dataset id on " + viewState);
+                        continue;
+                    }
+
+                    // Must first check if final changed value is not the same as value sent by
+                    // service.
+                    final AutofillValue autofilledValue = viewState.getAutofilledValue();
+                    final AutofillValue currentValue = viewState.getCurrentValue();
+                    if (autofilledValue != null && autofilledValue.equals(currentValue)) {
+                        if (sDebug) {
+                            Slog.d(TAG, "logContextCommitted(): ignoring changed " + viewState
+                                    + " because it has same value that was autofilled");
+                        }
+                        continue;
+                    }
+
+                    if (sDebug) {
+                        Slog.d(TAG, "logContextCommitted() found changed state: " + viewState);
+                    }
+                    if (changedFieldIds == null) {
+                        changedFieldIds = new ArrayList<>();
+                        changedDatasetIds = new ArrayList<>();
+                    }
+                    changedFieldIds.add(viewState.id);
+                    changedDatasetIds.add(datasetId);
+                } else {
+                    // Check if value match a dataset.
+                    final AutofillValue currentValue = viewState.getCurrentValue();
+                    if (currentValue == null) {
+                        if (sDebug) {
+                            Slog.d(TAG, "logContextCommitted(): skipping view witout current value "
+                                    + "( " + viewState + ")");
+                        }
+                        continue;
+                    }
+                    for (int j = 0; j < responseCount; j++) {
+                        final FillResponse response = mResponses.valueAt(j);
+                        final List<Dataset> datasets = response.getDatasets();
+                        if (datasets == null || datasets.isEmpty()) {
+                            if (sVerbose) Slog.v(TAG,  "logContextCommitted() no datasets at " + j);
+                        } else {
+                            for (int k = 0; k < datasets.size(); k++) {
+                                final Dataset dataset = datasets.get(k);
+                                final String datasetId = dataset.getId();
+                                if (datasetId == null) {
+                                    if (sVerbose) {
+                                        Slog.v(TAG, "logContextCommitted() skipping idless dataset "
+                                                + dataset);
+                                    }
+                                } else {
+                                    final ArrayList<AutofillValue> values = dataset.getFieldValues();
+                                    for (int l = 0; l < values.size(); l++) {
+                                        final AutofillValue candidate = values.get(l);
+                                        if (currentValue.equals(candidate)) {
+                                            if (sDebug) {
+                                                Slog.d(TAG, "field " + viewState.id
+                                                        + " was manually filled with value set by "
+                                                        + "dataset " + datasetId);
+                                            }
+                                            if (manuallyFilledIds == null) {
+                                                manuallyFilledIds = new ArrayMap<>();
+                                            }
+                                            ArraySet<String> datasetIds =
+                                                    manuallyFilledIds.get(viewState.id);
+                                            if (datasetIds == null) {
+                                                datasetIds = new ArraySet<>(1);
+                                                manuallyFilledIds.put(viewState.id, datasetIds);
+                                            }
+                                            datasetIds.add(datasetId);
+                                        }
+                                    }
+                                    if (mSelectedDatasetIds == null
+                                            || !mSelectedDatasetIds.contains(datasetId)) {
+                                        if (sVerbose) {
+                                            Slog.v(TAG, "adding ignored dataset " + datasetId);
+                                        }
+                                        if (ignoredDatasets == null) {
+                                            ignoredDatasets = new ArraySet<>();
+                                        }
+                                        ignoredDatasets.add(datasetId);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (sVerbose) {
+            Slog.v(TAG, "logContextCommitted(): id=" + id
+                    + ", selectedDatasetids=" + mSelectedDatasetIds
+                    + ", ignoredDatasetIds=" + ignoredDatasets
+                    + ", changedAutofillIds=" + changedFieldIds
+                    + ", changedDatasetIds=" + changedDatasetIds
+                    + ", manuallyFilledIds=" + manuallyFilledIds);
+        }
+
+        ArrayList<AutofillId> manuallyFilledFieldIds = null;
+        ArrayList<ArrayList<String>> manuallyFilledDatasetIds = null;
+
+        // Must "flatten" the map to the parcellable collection primitives
+        if (manuallyFilledIds != null) {
+            final int size = manuallyFilledIds.size();
+            manuallyFilledFieldIds = new ArrayList<>(size);
+            manuallyFilledDatasetIds = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                final AutofillId fieldId = manuallyFilledIds.keyAt(i);
+                final ArraySet<String> datasetIds = manuallyFilledIds.valueAt(i);
+                manuallyFilledFieldIds.add(fieldId);
+                manuallyFilledDatasetIds.add(new ArrayList<>(datasetIds));
+            }
+        }
+        mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
+                changedFieldIds, changedDatasetIds,
+                manuallyFilledFieldIds, manuallyFilledDatasetIds);
+    }
+
+    /**
      * Shows the save UI, when session can be saved.
      *
      * @return {@code true} if session is done, or {@code false} if it's pending user action.
@@ -970,6 +1157,7 @@
                     boolean isValid;
                     try {
                         isValid = validator.isValid(valueFinder);
+                        if (sDebug) Slog.d(TAG, validator + " returned " + isValid);
                         log.setType(isValid
                                 ? MetricsEvent.TYPE_SUCCESS
                                 : MetricsEvent.TYPE_DISMISS);
@@ -1408,11 +1596,10 @@
      * Checks whether a view should be ignored.
      */
     private boolean isIgnoredLocked(AutofillId id) {
-        if (mResponses == null || mResponses.size() == 0) {
-            return false;
-        }
         // Always check the latest response only
-        final FillResponse response = mResponses.valueAt(mResponses.size() - 1);
+        final FillResponse response = getLastResponseLocked(null);
+        if (response == null) return false;
+
         return ArrayUtils.contains(response.getIgnoredIds(), id);
     }
 
@@ -1489,13 +1676,10 @@
     }
 
     private void updateTrackedIdsLocked() {
-        if (mResponses == null || mResponses.size() == 0) {
-            return;
-        }
-
         // Only track the views of the last response as only those are reported back to the
         // service, see #showSaveLocked
-        final FillResponse response = mResponses.valueAt(getLastResponseIndexLocked());
+        final FillResponse response = getLastResponseLocked(null);
+        if (response == null) return;
 
         ArraySet<AutofillId> trackedViews = null;
         boolean saveOnAllViewsInvisible = false;
@@ -1600,7 +1784,10 @@
         }
 
         if (mResponses == null) {
-            mResponses = new SparseArray<>(4);
+            // Set initial capacity as 2 to handle cases where service always requires auth.
+            // TODO: add a metric for number of responses set by server, so we can use its average
+            // as the initial array capacitiy.
+            mResponses = new SparseArray<>(2);
         }
         mResponses.put(requestId, newResponse);
         mClientState = newClientState != null ? newClientState : newResponse.getClientState();
@@ -1676,6 +1863,10 @@
             final AutofillId id = ids.get(j);
             final AutofillValue value = values.get(j);
             final ViewState viewState = createOrUpdateViewStateLocked(id, state, value);
+            final String datasetId = dataset.getId();
+            if (datasetId != null) {
+                viewState.setDatasetId(datasetId);
+            }
             if (response != null) {
                 viewState.setResponse(response);
             } else if (clearResponse) {
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index 51659bb..1d8110f 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -78,6 +78,7 @@
     private AutofillValue mAutofilledValue;
     private Rect mVirtualBounds;
     private int mState;
+    private String mDatasetId;
 
     ViewState(Session session, AutofillId id, Listener listener, int state) {
         mSession = session;
@@ -148,6 +149,15 @@
         mState &= ~state;
     }
 
+    @Nullable
+    String getDatasetId() {
+        return mDatasetId;
+    }
+
+    void setDatasetId(String datasetId) {
+        mDatasetId = datasetId;
+    }
+
     // TODO: refactor / rename / document this method (and maybeCallOnFillReady) to make it clear
     // that it can change the value and update the UI; similarly, should replace code that
     // directly sets mAutofillValue to use encapsulation.
@@ -182,13 +192,15 @@
 
     @Override
     public String toString() {
-        return "ViewState: [id=" + id + ", currentValue=" + mCurrentValue
+        return "ViewState: [id=" + id + ", datasetId=" + mDatasetId
+                + ", currentValue=" + mCurrentValue
                 + ", autofilledValue=" + mAutofilledValue
                 + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() + "]";
     }
 
     void dump(String prefix, PrintWriter pw) {
         pw.print(prefix); pw.print("id:" ); pw.println(this.id);
+        pw.print(prefix); pw.print("datasetId:" ); pw.println(this.mDatasetId);
         pw.print(prefix); pw.print("state:" ); pw.println(getStateAsString());
         pw.print(prefix); pw.print("response:");
         if (mResponse == null) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index bf442dc..6d3d792 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -157,6 +157,11 @@
                 final int index = dataset.getFieldIds().indexOf(focusedViewId);
                 if (index >= 0) {
                     final RemoteViews presentation = dataset.getFieldPresentation(index);
+                    if (presentation == null) {
+                        Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
+                                + "service didn't provide a presentation for it on " + dataset);
+                        continue;
+                    }
                     final View view;
                     try {
                         if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId);
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 1659133..633bb3e 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -6,6 +6,7 @@
 
 LOCAL_AIDL_INCLUDES := \
     frameworks/native/aidl/binder \
+    system/core/storaged/binder \
     system/netd/server/binder \
     system/vold/binder
 
@@ -13,6 +14,7 @@
     $(call all-java-files-under,java) \
     java/com/android/server/EventLogTags.logtags \
     java/com/android/server/am/EventLogTags.logtags \
+    ../../../../system/core/storaged/binder/android/os/IStoraged.aidl \
     ../../../../system/netd/server/binder/android/net/INetd.aidl \
     ../../../../system/netd/server/binder/android/net/metrics/INetdEventListener.aidl \
     ../../../../system/vold/binder/android/os/IVold.aidl \
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 47be0a7..46b671b 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -39,10 +39,12 @@
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
 import android.hardware.health.V2_0.HealthInfo;
+import android.hardware.health.V2_0.IHealthInfoCallback;
 import android.hardware.health.V2_0.IHealth;
+import android.hardware.health.V2_0.Result;
 import android.os.BatteryManager;
 import android.os.BatteryManagerInternal;
-import android.os.BatteryProperties;
+import android.os.BatteryProperty;
 import android.os.Binder;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -58,6 +60,7 @@
 import android.provider.Settings;
 import android.service.battery.BatteryServiceDumpProto;
 import android.util.EventLog;
+import android.util.MutableInt;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
@@ -70,6 +73,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * <p>BatteryService monitors the charging status, and charge level of the device
@@ -108,6 +112,8 @@
 
     private static final int BATTERY_SCALE = 100;    // battery capacity is a percentage
 
+    private static final long HEALTH_HAL_WAIT_MS = 1000;
+
     // Used locally for determining when to make a last ditch effort to log
     // discharge stats before the device dies.
     private int mCriticalBatteryLevel;
@@ -165,6 +171,10 @@
 
     private ActivityManagerInternal mActivityManagerInternal;
 
+    private HealthServiceWrapper mHealthServiceWrapper;
+    private HealthHalCallback mHealthHalCallback;
+    private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+
     public BatteryService(Context context) {
         super(context);
 
@@ -203,17 +213,12 @@
 
     @Override
     public void onStart() {
-        IBinder b = ServiceManager.getService("batteryproperties");
-        final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
-                IBatteryPropertiesRegistrar.Stub.asInterface(b);
-        try {
-            batteryPropertiesRegistrar.registerListener(new BatteryListener());
-        } catch (RemoteException e) {
-            // Should never happen.
-        }
+        registerHealthCallback();
 
         mBinderService = new BinderService();
         publishBinderService("battery", mBinderService);
+        mBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar();
+        publishBinderService("batteryproperties", mBatteryPropertiesRegistrar);
         publishLocalService(BatteryManagerInternal.class, new LocalService());
     }
 
@@ -239,6 +244,49 @@
         }
     }
 
+    private void registerHealthCallback() {
+        mHealthServiceWrapper = new HealthServiceWrapper();
+        mHealthHalCallback = new HealthHalCallback();
+        // IHealth is lazily retrieved.
+        try {
+            mHealthServiceWrapper.init(mHealthHalCallback,
+                    new HealthServiceWrapper.IServiceManagerSupplier() {},
+                    new HealthServiceWrapper.IHealthSupplier() {});
+        } catch (RemoteException | NoSuchElementException ex) {
+            Slog.w(TAG, "health: cannot register callback. "
+                        + "BatteryService will be started with dummy values. Reason: "
+                        + ex.getClass().getSimpleName() + ": " + ex.getMessage());
+            update(new HealthInfo());
+            return;
+        }
+
+        // init register for new service notifications, and IServiceManager should return the
+        // existing service in a near future. Wait for this.update() to instantiate
+        // the initial mHealthInfo.
+        long timeWaited = 0;
+        synchronized (mLock) {
+            long beforeWait = SystemClock.uptimeMillis();
+            while (mHealthInfo == null &&
+                    (timeWaited = SystemClock.uptimeMillis() - beforeWait) < HEALTH_HAL_WAIT_MS) {
+                try {
+                    mLock.wait(HEALTH_HAL_WAIT_MS - timeWaited);
+                } catch (InterruptedException ex) {
+                    break;
+                }
+            }
+            if (mHealthInfo == null) {
+                Slog.w(TAG, "health: Waited " + timeWaited + "ms for callbacks but received "
+                        + "nothing. BatteryService will be started with dummy values.");
+                update(new HealthInfo());
+                return;
+            }
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "health: Waited " + timeWaited + "ms and received the update.");
+        }
+    }
+
     private void updateBatteryWarningLevelLocked() {
         final ContentResolver resolver = mContext.getContentResolver();
         int defWarnLevel = mContext.getResources().getInteger(
@@ -331,15 +379,15 @@
         }
     }
 
-    private void update(BatteryProperties props) {
+    private void update(HealthInfo info) {
         synchronized (mLock) {
             if (!mUpdatesStopped) {
-                mHealthInfo = new HealthInfo();
-                copy(mHealthInfo, props);
+                mHealthInfo = info;
                 // Process the new values.
                 processValuesLocked(false);
+                mLock.notifyAll(); // for any waiters on new info
             } else {
-                copy(mLastHealthInfo, props);
+                copy(mLastHealthInfo, info);
             }
         }
     }
@@ -366,24 +414,6 @@
         dst.energyCounter = src.energyCounter;
     }
 
-    // TODO(b/62229583): remove this function when BatteryProperties are completely replaced.
-    private static void copy(HealthInfo dst, BatteryProperties src) {
-        dst.legacy.chargerAcOnline = src.chargerAcOnline;
-        dst.legacy.chargerUsbOnline = src.chargerUsbOnline;
-        dst.legacy.chargerWirelessOnline = src.chargerWirelessOnline;
-        dst.legacy.maxChargingCurrent = src.maxChargingCurrent;
-        dst.legacy.maxChargingVoltage = src.maxChargingVoltage;
-        dst.legacy.batteryStatus = src.batteryStatus;
-        dst.legacy.batteryHealth = src.batteryHealth;
-        dst.legacy.batteryPresent = src.batteryPresent;
-        dst.legacy.batteryLevel = src.batteryLevel;
-        dst.legacy.batteryVoltage = src.batteryVoltage;
-        dst.legacy.batteryTemperature = src.batteryTemperature;
-        dst.legacy.batteryFullCharge = src.batteryFullCharge;
-        dst.legacy.batteryChargeCounter = src.batteryChargeCounter;
-        dst.legacy.batteryTechnology = src.batteryTechnology;
-    }
-
     private void processValuesLocked(boolean force) {
         boolean logOutlier = false;
         long dischargeDuration = 0;
@@ -962,15 +992,43 @@
         }
     }
 
-    private final class BatteryListener extends IBatteryPropertiesListener.Stub {
-        @Override public void batteryPropertiesChanged(BatteryProperties props) {
-            final long identity = Binder.clearCallingIdentity();
+    private final class HealthHalCallback extends IHealthInfoCallback.Stub
+            implements HealthServiceWrapper.Callback {
+        @Override public void healthInfoChanged(HealthInfo props) {
+            BatteryService.this.update(props);
+        }
+        // on new service registered
+        @Override public void onRegistration(IHealth oldService, IHealth newService,
+                String instance) {
+            if (newService == null) return;
+
             try {
-                BatteryService.this.update(props);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+                if (oldService != null) {
+                    int r = oldService.unregisterCallback(this);
+                    if (r != Result.SUCCESS) {
+                        Slog.w(TAG, "health: cannot unregister previous callback: " +
+                                Result.toString(r));
+                    }
+                }
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "health: cannot unregister previous callback (transaction error): "
+                            + ex.getMessage());
             }
-       }
+
+            try {
+                int r = newService.registerCallback(this);
+                if (r != Result.SUCCESS) {
+                    Slog.w(TAG, "health: cannot register callback: " + Result.toString(r));
+                    return;
+                }
+                // registerCallback does NOT guarantee that update is called
+                // immediately, so request a manual update here.
+                newService.update();
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "health: cannot register callback (transaction error): "
+                        + ex.getMessage());
+            }
+        }
     }
 
     private final class BinderService extends Binder {
@@ -991,6 +1049,63 @@
         }
     }
 
+    // Reduced IBatteryPropertiesRegistrar that only implements getProperty for usage
+    // in BatteryManager.
+    private final class BatteryPropertiesRegistrar extends IBatteryPropertiesRegistrar.Stub {
+        public void registerListener(IBatteryPropertiesListener listener) {
+            Slog.e(TAG, "health: must not call registerListener on battery properties");
+        }
+        public void unregisterListener(IBatteryPropertiesListener listener) {
+            Slog.e(TAG, "health: must not call unregisterListener on battery properties");
+        }
+        public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
+            IHealth service = mHealthServiceWrapper.getLastService();
+            final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
+            switch(id) {
+                case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
+                    service.getChargeCounter((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
+                    service.getCurrentNow((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
+                    service.getCurrentAverage((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CAPACITY:
+                    service.getCapacity((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_STATUS:
+                    service.getChargeStatus((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
+                    service.getEnergyCounter((int result, long value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+            }
+            return outResult.value;
+        }
+        public void scheduleUpdate() {
+            Slog.e(TAG, "health: must not call scheduleUpdate on battery properties");
+        }
+    }
+
     private final class LocalService extends BatteryManagerInternal {
         @Override
         public boolean isPowered(int plugTypeSet) {
@@ -1053,6 +1168,11 @@
         private Callback mCallback;
         private IHealthSupplier mHealthSupplier;
 
+        private final Object mLastServiceSetLock = new Object();
+        // Last IHealth service received.
+        // set must be also be guarded with mLastServiceSetLock to ensure ordering.
+        private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
+
         /**
          * init should be called after constructor. For testing purposes, init is not called by
          * constructor.
@@ -1060,6 +1180,10 @@
         HealthServiceWrapper() {
         }
 
+        IHealth getLastService() {
+            return mLastService.get();
+        }
+
         /**
          * Start monitoring registration of new IHealth services. Only instances that are in
          * {@code sAllInstances} and in device / framework manifest are used. This function should
@@ -1109,7 +1233,7 @@
              * into service.
              * @param instance instance name.
              */
-            void onRegistration(IHealth service, String instance);
+            void onRegistration(IHealth oldService, IHealth newService, String instance);
         }
 
         /**
@@ -1117,14 +1241,18 @@
          * Must not return null; throw {@link NoSuchElementException} if a service is not available.
          */
         interface IServiceManagerSupplier {
-            IServiceManager get() throws NoSuchElementException, RemoteException;
+            default IServiceManager get() throws NoSuchElementException, RemoteException {
+                return IServiceManager.getService();
+            }
         }
         /**
          * Supplier of services.
          * Must not return null; throw {@link NoSuchElementException} if a service is not available.
          */
         interface IHealthSupplier {
-            IHealth get(String instanceName) throws NoSuchElementException, RemoteException;
+            default IHealth get(String name) throws NoSuchElementException, RemoteException {
+                return IHealth.getService(name);
+            }
         }
 
         private class Notification extends IServiceNotification.Stub {
@@ -1134,9 +1262,13 @@
                 if (!IHealth.kInterfaceName.equals(interfaceName)) return;
                 if (!sAllInstances.contains(instanceName)) return;
                 try {
-                    IHealth service = mHealthSupplier.get(instanceName);
-                    Slog.i(TAG, "health: new instance registered " + instanceName);
-                    mCallback.onRegistration(service, instanceName);
+                    // ensures the order of multiple onRegistration on different threads.
+                    synchronized (mLastServiceSetLock) {
+                        IHealth newService = mHealthSupplier.get(instanceName);
+                        IHealth oldService = mLastService.getAndSet(newService);
+                        Slog.i(TAG, "health: new instance registered " + instanceName);
+                        mCallback.onRegistration(oldService, newService, instanceName);
+                    }
                 } catch (NoSuchElementException | RemoteException ex) {
                     Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " +
                            ex.getMessage() + ". Perhaps no permission?");
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 2e1f142..cf1d33c 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -882,8 +882,14 @@
         for (int direction : DIRECTIONS) {
             IpSecAlgorithm crypt = config.getEncryption(direction);
             IpSecAlgorithm auth = config.getAuthentication(direction);
-            if (crypt == null && auth == null) {
-                throw new IllegalArgumentException("Encryption and Authentication are both null");
+            IpSecAlgorithm authenticatedEncryption = config.getAuthenticatedEncryption(direction);
+            if (authenticatedEncryption == null && crypt == null && auth == null) {
+                throw new IllegalArgumentException(
+                        "No Encryption or Authentication algorithms specified");
+            } else if (authenticatedEncryption != null && (auth != null || crypt != null)) {
+                throw new IllegalArgumentException(
+                        "Authenticated Encryption is mutually"
+                                + " exclusive with other Authentication or Encryption algorithms");
             }
 
             if (mSpiRecords.getAndCheckOwner(config.getSpiResourceId(direction)) == null) {
@@ -922,6 +928,7 @@
         for (int direction : DIRECTIONS) {
             IpSecAlgorithm auth = c.getAuthentication(direction);
             IpSecAlgorithm crypt = c.getEncryption(direction);
+            IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction);
 
             spis[direction] = mSpiRecords.getAndCheckOwner(c.getSpiResourceId(direction));
             int spi = spis[direction].getSpi();
@@ -942,6 +949,9 @@
                                 (crypt != null) ? crypt.getName() : "",
                                 (crypt != null) ? crypt.getKey() : null,
                                 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+                                (authCrypt != null) ? authCrypt.getName() : "",
+                                (authCrypt != null) ? authCrypt.getKey() : null,
+                                (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
                                 encapType,
                                 encapLocalPort,
                                 encapRemotePort);
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index ba3afc3..c60d7b0 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -1980,7 +1980,8 @@
 
         final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
         final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
-        final boolean useTls = false;
+        final boolean useTls = Settings.Global.getInt(resolver,
+                Settings.Global.DNS_TLS_DISABLED, 0) == 0;
         final String tlsHostname = "";
         final String[] tlsFingerprints = new String[0];
         try {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 55391b3..cd25610 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -56,6 +56,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.IStoraged;
 import android.os.IVold;
 import android.os.IVoldListener;
 import android.os.IVoldTaskListener;
@@ -395,6 +396,7 @@
     private final Context mContext;
 
     private volatile IVold mVold;
+    private volatile IStoraged mStoraged;
 
     private volatile boolean mSystemReady = false;
     private volatile boolean mBootCompleted = false;
@@ -809,6 +811,7 @@
                 }
                 for (int userId : systemUnlockedUsers) {
                     mVold.onUserStarted(userId);
+                    mStoraged.onUserStarted(userId);
                 }
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
@@ -824,6 +827,7 @@
         // bind mount against.
         try {
             mVold.onUserStarted(userId);
+            mStoraged.onUserStarted(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -850,6 +854,7 @@
 
         try {
             mVold.onUserStopped(userId);
+            mStoraged.onUserStopped(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1335,13 +1340,36 @@
     }
 
     private void connect() {
-        IBinder binder = ServiceManager.getService("vold");
+        IBinder binder = ServiceManager.getService("storaged");
+        if (binder != null) {
+            try {
+                binder.linkToDeath(new DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        Slog.w(TAG, "storaged died; reconnecting");
+                        mStoraged = null;
+                        connect();
+                    }
+                }, 0);
+            } catch (RemoteException e) {
+                binder = null;
+            }
+        }
+
+        if (binder != null) {
+            mStoraged = IStoraged.Stub.asInterface(binder);
+        } else {
+            Slog.w(TAG, "storaged not found; trying again");
+        }
+
+        binder = ServiceManager.getService("vold");
         if (binder != null) {
             try {
                 binder.linkToDeath(new DeathRecipient() {
                     @Override
                     public void binderDied() {
                         Slog.w(TAG, "vold died; reconnecting");
+                        mVold = null;
                         connect();
                     }
                 }, 0);
@@ -1354,18 +1382,21 @@
             mVold = IVold.Stub.asInterface(binder);
             try {
                 mVold.setListener(mListener);
-                onDaemonConnected();
-                return;
             } catch (RemoteException e) {
+                mVold = null;
                 Slog.w(TAG, "vold listener rejected; trying again", e);
             }
         } else {
             Slog.w(TAG, "vold not found; trying again");
         }
 
-        BackgroundThread.getHandler().postDelayed(() -> {
-            connect();
-        }, DateUtils.SECOND_IN_MILLIS);
+        if (mStoraged == null || mVold == null) {
+            BackgroundThread.getHandler().postDelayed(() -> {
+                connect();
+            }, DateUtils.SECOND_IN_MILLIS);
+        } else {
+            onDaemonConnected();
+        }
     }
 
     private void systemReady() {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 4e15e5d..c684032 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2248,12 +2248,10 @@
                         Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
                                 + response);
                     }
-                    Bundle result2 = new Bundle();
-                    result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
                     try {
-                        response.onResult(result2);
+                        response.onResult(result);
                     } catch (RemoteException e) {
-                        // ignore
+                        Slog.e(TAG, "Error calling onResult()", e);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 6ed0555..089db87 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -168,9 +169,9 @@
         } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             return (T) mSplitScreenPrimaryStack;
         }
+
         for (int i = mStacks.size() - 1; i >= 0; --i) {
             final ActivityStack stack = mStacks.get(i);
-            // TODO: Should undefined windowing and activity type be compatible with standard type?
             if (stack.isCompatible(windowingMode, activityType)) {
                 return (T) stack;
             }
@@ -178,15 +179,28 @@
         return null;
     }
 
+    private boolean alwaysCreateStack(int windowingMode, int activityType) {
+        // Always create a stack for fullscreen, freeform, and split-screen-secondary windowing
+        // modes so that we can manage visual ordering and return types correctly.
+        return activityType == ACTIVITY_TYPE_STANDARD
+                && (windowingMode == WINDOWING_MODE_FULLSCREEN
+                || windowingMode == WINDOWING_MODE_FREEFORM
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+    }
+
     /**
+     * Returns an existing stack compatible with the windowing mode and activity type or creates one
+     * if a compatible stack doesn't exist.
      * @see #getStack(int, int)
      * @see #createStack(int, int, boolean)
      */
     <T extends ActivityStack> T getOrCreateStack(int windowingMode, int activityType,
             boolean onTop) {
-        T stack = getStack(windowingMode, activityType);
-        if (stack != null) {
-            return stack;
+        if (!alwaysCreateStack(windowingMode, activityType)) {
+            T stack = getStack(windowingMode, activityType);
+            if (stack != null) {
+                return stack;
+            }
         }
         return createStack(windowingMode, activityType, onTop);
     }
@@ -238,17 +252,7 @@
             }
         }
 
-        final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
-        if (!inSplitScreenMode
-                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
-            // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
-            // trying to launch in split-screen secondary.
-            windowingMode = WINDOWING_MODE_FULLSCREEN;
-        } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
-                && WindowConfiguration.supportSplitScreenWindowingMode(
-                        windowingMode, activityType)) {
-            windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-        }
+        windowingMode = updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
 
         final int stackId = mSupervisor.getNextStackId();
 
@@ -420,6 +424,21 @@
         return mPinnedStack != null;
     }
 
+    int updateWindowingModeForSplitScreenIfNeeded(int windowingMode, int activityType) {
+        final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
+        if (!inSplitScreenMode
+                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+            // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
+            // trying to launch in split-screen secondary.
+            return WINDOWING_MODE_FULLSCREEN;
+        } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
+                && WindowConfiguration.supportSplitScreenWindowingMode(
+                windowingMode, activityType)) {
+            return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+        }
+        return windowingMode;
+    }
+
     @Override
     public String toString() {
         return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f17c9ac..e1e53b3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4877,7 +4877,7 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                return mStackSupervisor.startActivityFromRecentsInner(taskId, bOptions);
+                return mStackSupervisor.startActivityFromRecents(taskId, bOptions);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -14890,6 +14890,10 @@
                 synchronized (this) {
                     dumpActivityStarterLocked(pw, dumpPackage);
                 }
+            } else if ("containers".equals(cmd)) {
+                synchronized (this) {
+                    dumpActivityContainersLocked(pw);
+                }
             } else if ("recents".equals(cmd) || "r".equals(cmd)) {
                 synchronized (this) {
                     if (mRecentTasks != null) {
@@ -15132,6 +15136,11 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
+                dumpActivityContainersLocked(pw);
+                pw.println();
+                if (dumpAll) {
+                    pw.println("-------------------------------------------------------------------------------");
+                }
                 dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
                 if (mAssociations.size() > 0) {
                     pw.println();
@@ -15204,6 +15213,11 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
+                dumpActivityContainersLocked(pw);
+                pw.println();
+                if (dumpAll) {
+                    pw.println("-------------------------------------------------------------------------------");
+                }
                 dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
                 if (mAssociations.size() > 0) {
                     pw.println();
@@ -15236,6 +15250,12 @@
         }
     }
 
+    private void dumpActivityContainersLocked(PrintWriter pw) {
+        pw.println("ACTIVITY MANAGER STARTER (dumpsys activity containers)");
+        mStackSupervisor.dumpChildrenNames(pw, " ");
+        pw.println(" ");
+    }
+
     private void dumpActivityStarterLocked(PrintWriter pw, String dumpPackage) {
         pw.println("ACTIVITY MANAGER STARTER (dumpsys activity starter)");
         mActivityStarter.dump(pw, "", dumpPackage);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index f0811dd..941c371 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -480,6 +480,29 @@
         }
     }
 
+    @Override
+    public boolean isCompatible(int windowingMode, int activityType) {
+        // TODO: Should we just move this to ConfigurationContainer?
+        if (activityType == ACTIVITY_TYPE_UNDEFINED) {
+            // Undefined activity types end up in a standard stack once the stack is created on a
+            // display, so they should be considered compatible.
+            activityType = ACTIVITY_TYPE_STANDARD;
+        }
+        final ActivityDisplay display = getDisplay();
+        if (display != null) {
+            if (activityType == ACTIVITY_TYPE_STANDARD
+                    && windowingMode == WINDOWING_MODE_UNDEFINED) {
+                // Standard activity types will mostly take on the windowing mode of the display if
+                // one isn't specified, so look-up a compatible stack based on the display's
+                // windowing mode.
+                windowingMode = display.getWindowingMode();
+            }
+            windowingMode =
+                    display.updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
+        }
+        return super.isCompatible(windowingMode, activityType);
+    }
+
     /** Adds the stack to specified display and calls WindowManager to do the same. */
     void reparent(ActivityDisplay activityDisplay, boolean onTop) {
         removeFromDisplay();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 5c91e3c..c15b5e2 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -706,7 +706,7 @@
     }
 
     TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode) {
-        return anyTaskForIdLocked(id, matchMode, null);
+        return anyTaskForIdLocked(id, matchMode, null, !ON_TOP);
     }
 
     /**
@@ -714,11 +714,11 @@
      * @param id Id of the task we would like returned.
      * @param matchMode The mode to match the given task id in.
      * @param aOptions The activity options to use for restoration. Can be null.
+     * @param onTop If the stack for the task should be the topmost on the display.
      */
     TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode,
-            @Nullable ActivityOptions aOptions) {
-        // If there is a stack id set, ensure that we are attempting to actually restore a task
-        // TODO: Don't really know if this is needed...
+            @Nullable ActivityOptions aOptions, boolean onTop) {
+        // If options are set, ensure that we are attempting to actually restore a task
         if (matchMode != MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE && aOptions != null) {
             throw new IllegalArgumentException("Should not specify activity options for non-restore"
                     + " lookup");
@@ -730,9 +730,21 @@
             for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getChildAt(stackNdx);
                 final TaskRecord task = stack.taskForIdLocked(id);
-                if (task != null) {
-                    return task;
+                if (task == null) {
+                    continue;
                 }
+                if (aOptions != null) {
+                    // Resolve the stack the task should be placed in now based on options
+                    // and reparent if needed.
+                    final ActivityStack launchStack = getLaunchStack(null, aOptions, task, onTop);
+                    if (launchStack != null && stack != launchStack) {
+                        final int reparentMode = onTop
+                                ? REPARENT_MOVE_STACK_TO_FRONT : REPARENT_LEAVE_STACK_IN_PLACE;
+                        task.reparent(launchStack, onTop, reparentMode, ANIMATE, DEFER_RESUME,
+                                "anyTaskForIdLocked");
+                    }
+                }
+                return task;
             }
         }
 
@@ -759,7 +771,7 @@
         }
 
         // Implicitly, this case is MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE
-        if (!restoreRecentTaskLocked(task, aOptions)) {
+        if (!restoreRecentTaskLocked(task, aOptions, onTop)) {
             if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,
                     "Couldn't restore task id=" + id + " found in recents");
             return null;
@@ -2248,11 +2260,13 @@
             }
         }
 
-        if (isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+        if (windowingMode != WINDOWING_MODE_UNDEFINED
+                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
                 supportsFreeform, supportsPip, activityType)) {
             return windowingMode;
         }
-        return WINDOWING_MODE_FULLSCREEN;
+        // Return root/systems windowing mode
+        return getWindowingMode();
     }
 
     int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
@@ -2266,7 +2280,10 @@
         if (activityType != ACTIVITY_TYPE_UNDEFINED) {
             return activityType;
         }
-        return options != null ? options.getLaunchActivityType() : ACTIVITY_TYPE_UNDEFINED;
+        if (options != null) {
+            activityType = options.getLaunchActivityType();
+        }
+        return activityType != ACTIVITY_TYPE_UNDEFINED ? activityType : ACTIVITY_TYPE_STANDARD;
     }
 
     <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
@@ -2304,7 +2321,7 @@
             // Temporarily set the task id to invalid in case in re-entry.
             options.setLaunchTaskId(INVALID_TASK_ID);
             final TaskRecord task = anyTaskForIdLocked(taskId,
-                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, options);
+                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, options, onTop);
             options.setLaunchTaskId(taskId);
             if (task != null) {
                 return task.getStack();
@@ -2330,13 +2347,10 @@
             }
             final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
             if (display != null) {
-                for (int i = display.getChildCount() - 1; i >= 0; --i) {
-                    stack = (T) display.getChildAt(i);
-                    if (stack.isCompatible(windowingMode, activityType)) {
-                        return stack;
-                    }
+                stack = display.getOrCreateStack(windowingMode, activityType, onTop);
+                if (stack != null) {
+                    return stack;
                 }
-                // TODO: We should create the stack we want on the display at this point.
             }
         }
 
@@ -2365,18 +2379,6 @@
             display = getDefaultDisplay();
         }
 
-        stack = display.getOrCreateStack(windowingMode, activityType, onTop);
-        if (stack != null) {
-            return stack;
-        }
-
-        // Whatever...return some default for now.
-        if (candidateTask != null && candidateTask.mBounds != null
-                && mService.mSupportsFreeformWindowManagement) {
-            windowingMode = WINDOWING_MODE_FREEFORM;
-        } else {
-            windowingMode = WINDOWING_MODE_FULLSCREEN;
-        }
         return display.getOrCreateStack(windowingMode, activityType, onTop);
     }
 
@@ -2967,10 +2969,11 @@
      *
      * @param task The recent task to be restored.
      * @param aOptions The activity options to use for restoration.
+     * @param onTop If the stack for the task should be the topmost on the display.
      * @return true if the task has been restored successfully.
      */
-    boolean restoreRecentTaskLocked(TaskRecord task, ActivityOptions aOptions) {
-        final ActivityStack stack = getLaunchStack(null, aOptions, task, !ON_TOP);
+    boolean restoreRecentTaskLocked(TaskRecord task, ActivityOptions aOptions, boolean onTop) {
+        final ActivityStack stack = getLaunchStack(null, aOptions, task, onTop);
         final ActivityStack currentStack = task.getStack();
         if (currentStack != null) {
             // Task has already been restored once. See if we need to do anything more
@@ -2983,9 +2986,9 @@
             currentStack.removeTask(task, "restoreRecentTaskLocked", REMOVE_TASK_MODE_MOVING);
         }
 
-        stack.addTask(task, !ON_TOP, "restoreRecentTask");
+        stack.addTask(task, onTop, "restoreRecentTask");
         // TODO: move call for creation here and other place into Stack.addTask()
-        task.createWindowContainer(!ON_TOP, true /* showForAllUsers */);
+        task.createWindowContainer(onTop, true /* showForAllUsers */);
         if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
                 "Added restored task=" + task + " to stack=" + stack);
         final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -3002,12 +3005,7 @@
 
     @Override
     public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
-        if (wasTrimmed) {
-            // Task was trimmed from the recent tasks list -- remove the active task record as well
-            // since the user won't really be able to go back to it
-            removeTaskByIdLocked(task.taskId, false /* killProcess */,
-                    false /* removeFromRecents */);
-        }
+        // TODO: Trim active task once b/68045330 is fixed
         task.removedFromRecents();
     }
 
@@ -4567,7 +4565,7 @@
         task.setTaskDockedResizing(true);
     }
 
-    final int startActivityFromRecentsInner(int taskId, Bundle bOptions) {
+    int startActivityFromRecents(int taskId, Bundle bOptions) {
         final TaskRecord task;
         final int callingUid;
         final String callingPackage;
@@ -4582,7 +4580,7 @@
             windowingMode = activityOptions.getLaunchWindowingMode();
         }
         if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
-            throw new IllegalArgumentException("startActivityFromRecentsInner: Task "
+            throw new IllegalArgumentException("startActivityFromRecents: Task "
                     + taskId + " can't be launch in the home/recents stack.");
         }
 
@@ -4600,21 +4598,12 @@
             }
 
             task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
-                    activityOptions);
+                    activityOptions, ON_TOP);
             if (task == null) {
                 continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
                 mWindowManager.executeAppTransition();
                 throw new IllegalArgumentException(
-                        "startActivityFromRecentsInner: Task " + taskId + " not found.");
-            }
-
-            // Since we don't have an actual source record here, we assume that the currently
-            // focused activity was the source.
-            final ActivityStack stack = getLaunchStack(null, activityOptions, task, ON_TOP);
-
-            if (stack != null && task.getStack() != stack) {
-                task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
-                        "startActivityFromRecents");
+                        "startActivityFromRecents: Task " + taskId + " not found.");
             }
 
             // If the user must confirm credentials (e.g. when first launching a work app and the
diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java
index a4e2e70..d42a3b9 100644
--- a/services/core/java/com/android/server/am/AppTaskImpl.java
+++ b/services/core/java/com/android/server/am/AppTaskImpl.java
@@ -94,7 +94,7 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                mService.mStackSupervisor.startActivityFromRecentsInner(mTaskId, null);
+                mService.mStackSupervisor.startActivityFromRecents(mTaskId, null);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -147,4 +147,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java
index 940f905..1c094c1 100644
--- a/services/core/java/com/android/server/am/LockTaskController.java
+++ b/services/core/java/com/android/server/am/LockTaskController.java
@@ -327,7 +327,9 @@
             if (getDevicePolicyManager() != null) {
                 getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
             }
-            getLockTaskNotify().show(false);
+            if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+                getLockTaskNotify().showPinningExitToast();
+            }
             try {
                 boolean shouldLockKeyguard = Settings.Secure.getIntForUser(
                         mContext.getContentResolver(),
@@ -349,10 +351,13 @@
     }
 
     /**
-     * Show the lock task violation toast.
+     * Show the lock task violation toast. Currently we only show toast for screen pinning mode, and
+     * no-op if the device is in locked mode.
      */
     void showLockTaskToast() {
-        mHandler.post(() -> getLockTaskNotify().showToast(mLockTaskModeState));
+        if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+            mHandler.post(() -> getLockTaskNotify().showEscapeToast());
+        }
     }
 
     // Starting lock task
@@ -439,7 +444,9 @@
     private void performStartLockTask(String packageName, int userId, int lockTaskModeState) {
         // When lock task starts, we disable the status bars.
         try {
-            getLockTaskNotify().show(true);
+            if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
+                getLockTaskNotify().showPinningStartToast();
+            }
             mLockTaskModeState = lockTaskModeState;
             if (getStatusBarService() != null) {
                 int flags = 0;
diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java
index 0412db5..1dcb0ad 100644
--- a/services/core/java/com/android/server/am/LockTaskNotify.java
+++ b/services/core/java/com/android/server/am/LockTaskNotify.java
@@ -16,10 +16,7 @@
 
 package com.android.server.am;
 
-import android.app.ActivityManager;
 import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
 import android.os.SystemClock;
 import android.util.Slog;
 import android.view.WindowManager;
@@ -28,37 +25,33 @@
 import com.android.internal.R;
 
 /**
- *  Helper to manage showing/hiding a image to notify them that they are entering
- *  or exiting lock-to-app mode.
+ *  Helper to manage showing/hiding a image to notify them that they are entering or exiting screen
+ *  pinning mode. All exposed methods should be called from a handler thread.
  */
 public class LockTaskNotify {
     private static final String TAG = "LockTaskNotify";
     private static final long SHOW_TOAST_MINIMUM_INTERVAL = 1000;
 
     private final Context mContext;
-    private final H mHandler;
     private Toast mLastToast;
     private long mLastShowToastTime;
 
     public LockTaskNotify(Context context) {
         mContext = context;
-        mHandler = new H();
     }
 
-    public void showToast(int lockTaskModeState) {
-        mHandler.obtainMessage(H.SHOW_TOAST, lockTaskModeState, 0 /* Not used */).sendToTarget();
+    /** Show "Screen pinned" toast. */
+    void showPinningStartToast() {
+        makeAllUserToastAndShow(R.string.lock_to_app_start);
     }
 
-    public void handleShowToast(int lockTaskModeState) {
-        String text = null;
-        if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_LOCKED) {
-            text = mContext.getString(R.string.lock_to_app_toast_locked);
-        } else if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_PINNED) {
-            text = mContext.getString(R.string.lock_to_app_toast);
-        }
-        if (text == null) {
-            return;
-        }
+    /** Show "Screen unpinned" toast. */
+    void showPinningExitToast() {
+        makeAllUserToastAndShow(R.string.lock_to_app_exit);
+    }
+
+    /** Show a toast that describes the gesture the user should use to escape pinned mode. */
+    void showEscapeToast() {
         long showToastTime = SystemClock.elapsedRealtime();
         if ((showToastTime - mLastShowToastTime) < SHOW_TOAST_MINIMUM_INTERVAL) {
             Slog.i(TAG, "Ignore toast since it is requested in very short interval.");
@@ -67,36 +60,15 @@
         if (mLastToast != null) {
             mLastToast.cancel();
         }
-        mLastToast = makeAllUserToastAndShow(text);
+        mLastToast = makeAllUserToastAndShow(R.string.lock_to_app_toast);
         mLastShowToastTime = showToastTime;
     }
 
-    public void show(boolean starting) {
-        int showString = R.string.lock_to_app_exit;
-        if (starting) {
-            showString = R.string.lock_to_app_start;
-        }
-        makeAllUserToastAndShow(mContext.getString(showString));
-    }
-
-    private Toast makeAllUserToastAndShow(String text) {
-        Toast toast = Toast.makeText(mContext, text, Toast.LENGTH_LONG);
+    private Toast makeAllUserToastAndShow(int resId) {
+        Toast toast = Toast.makeText(mContext, resId, Toast.LENGTH_LONG);
         toast.getWindowParams().privateFlags |=
                 WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
         toast.show();
         return toast;
     }
-
-    private final class H extends Handler {
-        private static final int SHOW_TOAST = 3;
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch(msg.what) {
-                case SHOW_TOAST:
-                    handleShowToast(msg.arg1);
-                    break;
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index a1b45a1..c451235 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -60,6 +60,7 @@
 import static com.android.server.am.ActivityRecord.STARTING_WINDOW_SHOWN;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING_TO_TOP;
+import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.am.proto.TaskRecordProto.ACTIVITIES;
@@ -512,7 +513,7 @@
             updateOverrideConfiguration(bounds);
             if (!inFreeformWindowingMode()) {
                 // re-restore the task so it can have the proper stack association.
-                mService.mStackSupervisor.restoreRecentTaskLocked(this, null);
+                mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
             }
             return true;
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ab8da0c..391deb7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -12443,7 +12443,8 @@
                             .getPrivAppDenyPermissions(pkg.packageName);
                     final boolean permissionViolation =
                             deniedPermissions == null || !deniedPermissions.contains(perm);
-                    if (permissionViolation) {
+                    if (permissionViolation
+                            && RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
                         if (mPrivappPermissionsViolations == null) {
                             mPrivappPermissionsViolations = new ArraySet<>();
                         }
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 12f490f..a3585bc 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -674,7 +674,8 @@
             }
             checked.add(activity);
 
-            if (!s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
+            if ((activity != null)
+                    && !s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
                 return false;
             }
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ceb0ad0..6520dc9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1654,11 +1654,23 @@
                 mScreenshotChordVolumeDownKeyConsumed = true;
                 mA11yShortcutChordVolumeUpKeyConsumed = true;
                 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT),
-                        ViewConfiguration.get(mContext).getAccessibilityShortcutKeyTimeout());
+                        getAccessibilityShortcutTimeout());
             }
         }
     }
 
+    private long getAccessibilityShortcutTimeout() {
+        ViewConfiguration config = ViewConfiguration.get(mContext);
+        try {
+            return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, mCurrentUserId) == 0
+                    ? config.getAccessibilityShortcutKeyTimeout()
+                    : config.getAccessibilityShortcutKeyTimeoutAfterConfirmation();
+        } catch (Settings.SettingNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     private long getScreenshotChordLongPressDelay() {
         if (mKeyguardDelegate.isShowing()) {
             // Double the time it takes to take a screenshot from the keyguard
@@ -3866,8 +3878,7 @@
                 mAccessibilityTvScheduled = true;
                 Message msg = Message.obtain(mHandler, MSG_ACCESSIBILITY_TV);
                 msg.setAsynchronous(true);
-                mHandler.sendMessageDelayed(msg,
-                        ViewConfiguration.get(mContext).getAccessibilityShortcutKeyTimeout());
+                mHandler.sendMessageDelayed(msg, getAccessibilityShortcutTimeout());
             }
         } else if (mAccessibilityTvScheduled) {
             mHandler.removeMessages(MSG_ACCESSIBILITY_TV);
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index ca3dd05..22d2bcf 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -33,12 +33,14 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.StatsLogEventWrapper;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.List;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.KernelWakelockReader;
 import com.android.internal.os.KernelWakelockStats;
@@ -49,6 +51,7 @@
 /**
  * Helper service for statsd (the native stats management service in cmds/statsd/).
  * Used for registering and receiving alarms on behalf of statsd.
+ *
  * @hide
  */
 public class StatsCompanionService extends IStatsCompanionService.Stub {
@@ -90,7 +93,7 @@
                         // Needed since the new user basically has a version of every app.
                         informAllUidsLocked(context);
                     } catch (RemoteException e) {
-                        Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
+                        Slog.e(TAG, "Failed to inform statsd latest update of all apps", e);
                         forgetEverything();
                     }
                 }
@@ -99,9 +102,9 @@
         Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED.");
     }
 
-    private final static int[] toIntArray(List<Integer> list){
+    private final static int[] toIntArray(List<Integer> list) {
         int[] ret = new int[list.size()];
-        for(int i = 0;i < ret.length;i++) {
+        for (int i = 0; i < ret.length; i++) {
             ret[i] = list.get(i);
         }
         return ret;
@@ -113,7 +116,7 @@
         PackageManager pm = context.getPackageManager();
         final List<UserInfo> users = um.getUsers(true);
         if (DEBUG) {
-            Slog.w(TAG, "Iterating over "+users.size() + " profiles.");
+            Slog.w(TAG, "Iterating over " + users.size() + " profiles.");
         }
 
         List<Integer> uids = new ArrayList();
@@ -122,23 +125,23 @@
 
         // Add in all the apps for every user/profile.
         for (UserInfo profile : users) {
-          List<PackageInfo> pi = pm.getInstalledPackagesAsUser(0, profile.id);
-          for (int j = 0; j < pi.size(); j++) {
-              if (pi.get(j).applicationInfo != null) {
-                  uids.add(pi.get(j).applicationInfo.uid);
-                  versions.add(pi.get(j).versionCode);
-                  apps.add(pi.get(j).packageName);
-              }
-          }
+            List<PackageInfo> pi = pm.getInstalledPackagesAsUser(0, profile.id);
+            for (int j = 0; j < pi.size(); j++) {
+                if (pi.get(j).applicationInfo != null) {
+                    uids.add(pi.get(j).applicationInfo.uid);
+                    versions.add(pi.get(j).versionCode);
+                    apps.add(pi.get(j).packageName);
+                }
+            }
         }
         sStatsd.informAllUidData(toIntArray(uids), toIntArray(versions), apps.toArray(new
-            String[apps.size()]));
+                String[apps.size()]));
         if (DEBUG) {
-            Slog.w(TAG, "Sent data for "+uids.size() +" apps");
+            Slog.w(TAG, "Sent data for " + uids.size() + " apps");
         }
     }
 
-    public final static class AppUpdateReceiver extends BroadcastReceiver  {
+    public final static class AppUpdateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
@@ -147,7 +150,7 @@
              * waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag.
              */
             if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) &&
-                intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                    intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                 return; // Keep only replacing or normal add and remove.
             }
             synchronized (sStatsdLock) {
@@ -180,9 +183,9 @@
                 }
             }
         }
-    };
+    }
 
-    public final static class AnomalyAlarmReceiver extends BroadcastReceiver  {
+    public final static class AnomalyAlarmReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred.");
@@ -200,7 +203,7 @@
             }
             // AlarmManager releases its own wakelock here.
         }
-    };
+    }
 
     public final static class PollingAlarmReceiver extends BroadcastReceiver {
         @Override
@@ -220,7 +223,7 @@
             }
             // AlarmManager releases its own wakelock here.
         }
-    };
+    }
 
     @Override // Binder call
     public void setAnomalyAlarm(long timestampMs) {
@@ -286,33 +289,34 @@
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
 
     @Override // Binder call
-    public String pullData(int pullCode) {
+    public StatsLogEventWrapper[] pullData(int pullCode) {
         enforceCallingPermission();
-        if (DEBUG) Slog.d(TAG, "Fetching " + pullCode);
+        if (DEBUG) {
+            Slog.d(TAG, "Pulling " + pullCode);
+        }
 
-        StringBuilder s = new StringBuilder(); // TODO: use and return a Parcel instead of a string
+        List<StatsLogEventWrapper> ret = new ArrayList<>();
         switch (pullCode) {
-            case PULL_CODE_KERNEL_WAKELOCKS:
+            case PULL_CODE_KERNEL_WAKELOCKS: {
                 final KernelWakelockStats wakelockStats =
                         mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
-
                 for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
                     String name = ent.getKey();
                     KernelWakelockStats.Entry kws = ent.getValue();
-                    s.append("Wakelock ")
-                            .append(name)
-                            .append(", time=")
-                            .append(kws.mTotalTime)
-                            .append(", count=")
-                            .append(kws.mCount)
-                            .append('\n');
+                    StatsLogEventWrapper e = new StatsLogEventWrapper(101, 4);
+                    e.writeInt(kws.mCount);
+                    e.writeInt(kws.mVersion);
+                    e.writeLong(kws.mTotalTime);
+                    e.writeString(name);
+                    ret.add(e);
                 }
                 break;
+            }
             default:
                 Slog.w(TAG, "No such pollable data as " + pullCode);
                 return null;
         }
-        return s.toString();
+        return ret.toArray(new StatsLogEventWrapper[ret.size()]);
     }
 
     @Override // Binder call
@@ -331,7 +335,9 @@
 
     // Lifecycle and related code
 
-    /** Fetches the statsd IBinder service */
+    /**
+     * Fetches the statsd IBinder service
+     */
     private static IStatsManager fetchStatsdService() {
         return IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
     }
@@ -363,13 +369,17 @@
         }
     }
 
-    /** Now that the android system is ready, StatsCompanion is ready too, so inform statsd. */
+    /**
+     * Now that the android system is ready, StatsCompanion is ready too, so inform statsd.
+     */
     private void systemReady() {
         if (DEBUG) Slog.d(TAG, "Learned that systemReady");
         sayHiToStatsd();
     }
 
-    /** Tells statsd that statscompanion is ready. If the binder call returns, link to statsd. */
+    /**
+     * Tells statsd that statscompanion is ready. If the binder call returns, link to statsd.
+     */
     private void sayHiToStatsd() {
         synchronized (sStatsdLock) {
             if (sStatsd != null) {
@@ -398,14 +408,14 @@
                 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
                 filter.addDataScheme("package");
                 mContext.registerReceiverAsUser(mAppUpdateReceiver, UserHandle.ALL, filter, null,
-                    null);
+                        null);
 
                 // Setup receiver for user initialize (which happens once for a new user) and
                 // if a user is removed.
                 filter = new IntentFilter(Intent.ACTION_USER_INITIALIZE);
                 filter.addAction(Intent.ACTION_USER_REMOVED);
                 mContext.registerReceiverAsUser(mUserUpdateReceiver, UserHandle.ALL,
-                    filter, null, null);
+                        filter, null, null);
 
                 // Pull the latest state of UID->app name, version mapping when statsd starts.
                 informAllUidsLocked(mContext);
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 5365e27..2ef7f25 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -295,13 +295,11 @@
     void updateThumbnailLayer() {
         if (thumbnail != null) {
             final int layer = mAppToken.getHighestAnimLayer();
-            if (layer != mThumbnailLayer) {
-                if (DEBUG_LAYERS) Slog.v(TAG,
-                        "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
-                thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
-                        - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
-                mThumbnailLayer = layer;
-            }
+            if (DEBUG_LAYERS) Slog.v(TAG,
+                    "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
+            thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
+                    - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
+            mThumbnailLayer = layer;
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 5bfea98..a4ab3ba 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -28,6 +28,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
 import static com.android.server.wm.proto.ConfigurationContainerProto.FULL_CONFIGURATION;
 import static com.android.server.wm.proto.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
 import static com.android.server.wm.proto.ConfigurationContainerProto.OVERRIDE_CONFIGURATION;
@@ -37,6 +38,7 @@
 import android.content.res.Configuration;
 import android.util.proto.ProtoOutputStream;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 /**
@@ -342,6 +344,26 @@
         protoOutputStream.end(token);
     }
 
+    /**
+     * Dumps the names of this container children in the input print writer indenting each
+     * level with the input prefix.
+     */
+    public void dumpChildrenNames(PrintWriter pw, String prefix) {
+        final String childPrefix = prefix + " ";
+        pw.println(getName()
+                + " type=" + activityTypeToString(getActivityType())
+                + " mode=" + windowingModeToString(getWindowingMode()));
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final E cc = getChildAt(i);
+            pw.print(childPrefix + "#" + i + " ");
+            cc.dumpChildrenNames(pw, childPrefix);
+        }
+    }
+
+    String getName() {
+        return toString();
+    }
+
     abstract protected int getChildCount();
 
     abstract protected E getChildAt(int index);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1b0825e..563eb9c 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -674,20 +674,6 @@
     }
 
     /**
-     * Dumps the names of this container children in the input print writer indenting each
-     * level with the input prefix.
-     */
-    void dumpChildrenNames(StringBuilder out, String prefix) {
-        final String childPrefix = prefix + " ";
-        out.append(getName() + "\n");
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = mChildren.get(i);
-            out.append(childPrefix + "#" + i + " ");
-            wc.dumpChildrenNames(out, childPrefix);
-        }
-    }
-
-    /**
      * Write to a protocol buffer output stream. Protocol buffer message definition is at
      * {@link com.android.server.wm.proto.WindowContainerProto}.
      *
@@ -704,10 +690,6 @@
         protoOutputStream.end(token);
     }
 
-    String getName() {
-        return toString();
-    }
-
     private ForAllWindowsConsumerWrapper obtainConsumerWrapper(Consumer<WindowState> consumer) {
         ForAllWindowsConsumerWrapper wrapper = mConsumerWrapperPool.acquire();
         if (wrapper == null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b133bd4..f0da474 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6921,9 +6921,7 @@
                 return;
             } else if ("containers".equals(cmd)) {
                 synchronized(mWindowMap) {
-                    StringBuilder output = new StringBuilder();
-                    mRoot.dumpChildrenNames(output, " ");
-                    pw.println(output.toString());
+                    mRoot.dumpChildrenNames(pw, " ");
                     pw.println(" ");
                     mRoot.forAllWindows(w -> {pw.println(w);}, true /* traverseTopToBottom */);
                 }
diff --git a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
index 9a17635..3eaf488 100644
--- a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
+++ b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
@@ -103,8 +103,8 @@
     // fd2   A file descriptor bound to the following netlink groups
     //       (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
     base::unique_fd
-            fd1(conntrackSocket(NFNLGRP_CONNTRACK_NEW | NFNLGRP_CONNTRACK_DESTROY)),
-            fd2(conntrackSocket(NFNLGRP_CONNTRACK_UPDATE | NFNLGRP_CONNTRACK_DESTROY));
+            fd1(conntrackSocket(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY)),
+            fd2(conntrackSocket(NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY));
     if (fd1.get() < 0 || fd2.get() < 0) {
         ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno));
         return false;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b950191..d9db22e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -206,7 +206,8 @@
             "com.android.server.autofill.AutofillManagerService";
     private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
             "com.android.server.timezone.RulesManagerService$Lifecycle";
-
+    private static final String IOT_SERVICE_CLASS =
+            "com.google.android.things.services.IoTSystemService";
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
 
     private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file";
@@ -1544,10 +1545,11 @@
             traceEnd();
         }
 
-        // Statsd helper
-        traceBeginAndSlog("StartStatsCompanionService");
-        mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
-        traceEnd();
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED)) {
+            traceBeginAndSlog("StartIoTSystemService");
+            mSystemServiceManager.startService(IOT_SERVICE_CLASS);
+            traceEnd();
+        }
 
         // Before things start rolling, be sure we have decided whether
         // we are in safe mode.
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 190b3a6..5c2b66f 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -33,7 +33,7 @@
 import android.net.apf.ApfGenerator;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
 import android.net.metrics.IpConnectivityLog;
@@ -238,7 +238,7 @@
     private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
 
     private final ApfCapabilities mApfCapabilities;
-    private final IpManager.Callback mIpManagerCallback;
+    private final IpClient.Callback mIpClientCallback;
     private final NetworkInterface mNetworkInterface;
     private final IpConnectivityLog mMetricsLog;
 
@@ -262,10 +262,10 @@
 
     @VisibleForTesting
     ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
-            IpManager.Callback ipManagerCallback, boolean multicastFilter,
+            IpClient.Callback ipClientCallback, boolean multicastFilter,
             boolean ieee802_3Filter, int[] ethTypeBlackList, IpConnectivityLog log) {
         mApfCapabilities = apfCapabilities;
-        mIpManagerCallback = ipManagerCallback;
+        mIpClientCallback = ipClientCallback;
         mNetworkInterface = networkInterface;
         mMulticastFilter = multicastFilter;
         mDrop802_3Frames = ieee802_3Filter;
@@ -275,7 +275,7 @@
 
         mMetricsLog = log;
 
-        // TODO: ApfFilter should not generate programs until IpManager sends provisioning success.
+        // TODO: ApfFilter should not generate programs until IpClient sends provisioning success.
         maybeStartFilter();
     }
 
@@ -1051,7 +1051,7 @@
         if (VDBG) {
             hexDump("Installing filter: ", program, program.length);
         }
-        mIpManagerCallback.installPacketFilter(program);
+        mIpClientCallback.installPacketFilter(program);
         logApfProgramEventLocked(now);
         mLastInstallEvent = new ApfProgramEvent();
         mLastInstallEvent.lifetime = programMinLifetime;
@@ -1161,7 +1161,7 @@
      * filtering using APF programs.
      */
     public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
-            NetworkInterface networkInterface, IpManager.Callback ipManagerCallback,
+            NetworkInterface networkInterface, IpClient.Callback ipClientCallback,
             boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList) {
         if (apfCapabilities == null || networkInterface == null) return null;
         if (apfCapabilities.apfVersionSupported == 0) return null;
@@ -1178,7 +1178,7 @@
             Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
             return null;
         }
-        return new ApfFilter(apfCapabilities, networkInterface, ipManagerCallback,
+        return new ApfFilter(apfCapabilities, networkInterface, ipClientCallback,
                 multicastFilter, ieee802_3Filter, ethTypeBlackList, new IpConnectivityLog());
     }
 
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
new file mode 100644
index 0000000..2359fab
--- /dev/null
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -0,0 +1,1712 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.WakeupMessage;
+
+import android.content.Context;
+import android.net.DhcpResults;
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties.ProvisioningChange;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.ProxyInfo;
+import android.net.RouteInfo;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
+import android.net.dhcp.DhcpClient;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.IpManagerEvent;
+import android.net.util.MultinetworkPolicyTracker;
+import android.net.util.NetdService;
+import android.net.util.NetworkConstants;
+import android.net.util.SharedLog;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.IState;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.net.NetlinkTracker;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+
+/**
+ * IpClient
+ *
+ * This class provides the interface to IP-layer provisioning and maintenance
+ * functionality that can be used by transport layers like Wi-Fi, Ethernet,
+ * et cetera.
+ *
+ * [ Lifetime ]
+ * IpClient is designed to be instantiated as soon as the interface name is
+ * known and can be as long-lived as the class containing it (i.e. declaring
+ * it "private final" is okay).
+ *
+ * @hide
+ */
+public class IpClient extends StateMachine {
+    private static final boolean DBG = false;
+
+    // For message logging.
+    private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
+    private static final SparseArray<String> sWhatToString =
+            MessageUtils.findMessageNames(sMessageClasses);
+
+    /**
+     * Callbacks for handling IpClient events.
+     */
+    public static class Callback {
+        // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
+        // when constructing a ProvisioningConfiguration.
+        //
+        // Implementations of onPreDhcpAction() must call
+        // IpClient#completedPreDhcpAction() to indicate that DHCP is clear
+        // to proceed.
+        public void onPreDhcpAction() {}
+        public void onPostDhcpAction() {}
+
+        // This is purely advisory and not an indication of provisioning
+        // success or failure.  This is only here for callers that want to
+        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+        // DHCPv4 or static IPv4 configuration failure or success can be
+        // determined by whether or not the passed-in DhcpResults object is
+        // null or not.
+        public void onNewDhcpResults(DhcpResults dhcpResults) {}
+
+        public void onProvisioningSuccess(LinkProperties newLp) {}
+        public void onProvisioningFailure(LinkProperties newLp) {}
+
+        // Invoked on LinkProperties changes.
+        public void onLinkPropertiesChange(LinkProperties newLp) {}
+
+        // Called when the internal IpReachabilityMonitor (if enabled) has
+        // detected the loss of a critical number of required neighbors.
+        public void onReachabilityLost(String logMsg) {}
+
+        // Called when the IpClient state machine terminates.
+        public void onQuit() {}
+
+        // Install an APF program to filter incoming packets.
+        public void installPacketFilter(byte[] filter) {}
+
+        // If multicast filtering cannot be accomplished with APF, this function will be called to
+        // actuate multicast filtering using another means.
+        public void setFallbackMulticastFilter(boolean enabled) {}
+
+        // Enabled/disable Neighbor Discover offload functionality. This is
+        // called, for example, whenever 464xlat is being started or stopped.
+        public void setNeighborDiscoveryOffload(boolean enable) {}
+    }
+
+    // Use a wrapper class to log in order to ensure complete and detailed
+    // logging. This method is lighter weight than annotations/reflection
+    // and has the following benefits:
+    //
+    //     - No invoked method can be forgotten.
+    //       Any new method added to IpClient.Callback must be overridden
+    //       here or it will never be called.
+    //
+    //     - No invoking call site can be forgotten.
+    //       Centralized logging in this way means call sites don't need to
+    //       remember to log, and therefore no call site can be forgotten.
+    //
+    //     - No variation in log format among call sites.
+    //       Encourages logging of any available arguments, and all call sites
+    //       are necessarily logged identically.
+    //
+    // TODO: Find an lighter weight approach.
+    private class LoggingCallbackWrapper extends Callback {
+        private static final String PREFIX = "INVOKE ";
+        private Callback mCallback;
+
+        public LoggingCallbackWrapper(Callback callback) {
+            mCallback = callback;
+        }
+
+        private void log(String msg) {
+            mLog.log(PREFIX + msg);
+        }
+
+        @Override
+        public void onPreDhcpAction() {
+            mCallback.onPreDhcpAction();
+            log("onPreDhcpAction()");
+        }
+        @Override
+        public void onPostDhcpAction() {
+            mCallback.onPostDhcpAction();
+            log("onPostDhcpAction()");
+        }
+        @Override
+        public void onNewDhcpResults(DhcpResults dhcpResults) {
+            mCallback.onNewDhcpResults(dhcpResults);
+            log("onNewDhcpResults({" + dhcpResults + "})");
+        }
+        @Override
+        public void onProvisioningSuccess(LinkProperties newLp) {
+            mCallback.onProvisioningSuccess(newLp);
+            log("onProvisioningSuccess({" + newLp + "})");
+        }
+        @Override
+        public void onProvisioningFailure(LinkProperties newLp) {
+            mCallback.onProvisioningFailure(newLp);
+            log("onProvisioningFailure({" + newLp + "})");
+        }
+        @Override
+        public void onLinkPropertiesChange(LinkProperties newLp) {
+            mCallback.onLinkPropertiesChange(newLp);
+            log("onLinkPropertiesChange({" + newLp + "})");
+        }
+        @Override
+        public void onReachabilityLost(String logMsg) {
+            mCallback.onReachabilityLost(logMsg);
+            log("onReachabilityLost(" + logMsg + ")");
+        }
+        @Override
+        public void onQuit() {
+            mCallback.onQuit();
+            log("onQuit()");
+        }
+        @Override
+        public void installPacketFilter(byte[] filter) {
+            mCallback.installPacketFilter(filter);
+            log("installPacketFilter(byte[" + filter.length + "])");
+        }
+        @Override
+        public void setFallbackMulticastFilter(boolean enabled) {
+            mCallback.setFallbackMulticastFilter(enabled);
+            log("setFallbackMulticastFilter(" + enabled + ")");
+        }
+        @Override
+        public void setNeighborDiscoveryOffload(boolean enable) {
+            mCallback.setNeighborDiscoveryOffload(enable);
+            log("setNeighborDiscoveryOffload(" + enable + ")");
+        }
+    }
+
+    /**
+     * This class encapsulates parameters to be passed to
+     * IpClient#startProvisioning(). A defensive copy is made by IpClient
+     * and the values specified herein are in force until IpClient#stop()
+     * is called.
+     *
+     * Example use:
+     *
+     *     final ProvisioningConfiguration config =
+     *             mIpClient.buildProvisioningConfiguration()
+     *                     .withPreDhcpAction()
+     *                     .withProvisioningTimeoutMs(36 * 1000)
+     *                     .build();
+     *     mIpClient.startProvisioning(config);
+     *     ...
+     *     mIpClient.stop();
+     *
+     * The specified provisioning configuration will only be active until
+     * IpClient#stop() is called. Future calls to IpClient#startProvisioning()
+     * must specify the configuration again.
+     */
+    public static class ProvisioningConfiguration {
+        // TODO: Delete this default timeout once those callers that care are
+        // fixed to pass in their preferred timeout.
+        //
+        // We pick 36 seconds so we can send DHCP requests at
+        //
+        //     t=0, t=2, t=6, t=14, t=30
+        //
+        // allowing for 10% jitter.
+        private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
+
+        public static class Builder {
+            private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
+
+            public Builder withoutIPv4() {
+                mConfig.mEnableIPv4 = false;
+                return this;
+            }
+
+            public Builder withoutIPv6() {
+                mConfig.mEnableIPv6 = false;
+                return this;
+            }
+
+            public Builder withoutIpReachabilityMonitor() {
+                mConfig.mUsingIpReachabilityMonitor = false;
+                return this;
+            }
+
+            public Builder withPreDhcpAction() {
+                mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
+                return this;
+            }
+
+            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+                mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
+                return this;
+            }
+
+            public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+                mConfig.mInitialConfig = initialConfig;
+                return this;
+            }
+
+            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+                mConfig.mStaticIpConfig = staticConfig;
+                return this;
+            }
+
+            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+                mConfig.mApfCapabilities = apfCapabilities;
+                return this;
+            }
+
+            public Builder withProvisioningTimeoutMs(int timeoutMs) {
+                mConfig.mProvisioningTimeoutMs = timeoutMs;
+                return this;
+            }
+
+            public Builder withIPv6AddrGenModeEUI64() {
+                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
+                return this;
+            }
+
+            public Builder withIPv6AddrGenModeStablePrivacy() {
+                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+                return this;
+            }
+
+            public Builder withNetwork(Network network) {
+                mConfig.mNetwork = network;
+                return this;
+            }
+
+            public Builder withDisplayName(String displayName) {
+                mConfig.mDisplayName = displayName;
+                return this;
+            }
+
+            public ProvisioningConfiguration build() {
+                return new ProvisioningConfiguration(mConfig);
+            }
+        }
+
+        /* package */ boolean mEnableIPv4 = true;
+        /* package */ boolean mEnableIPv6 = true;
+        /* package */ boolean mUsingIpReachabilityMonitor = true;
+        /* package */ int mRequestedPreDhcpActionMs;
+        /* package */ InitialConfiguration mInitialConfig;
+        /* package */ StaticIpConfiguration mStaticIpConfig;
+        /* package */ ApfCapabilities mApfCapabilities;
+        /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
+        /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+        /* package */ Network mNetwork = null;
+        /* package */ String mDisplayName = null;
+
+        public ProvisioningConfiguration() {} // used by Builder
+
+        public ProvisioningConfiguration(ProvisioningConfiguration other) {
+            mEnableIPv4 = other.mEnableIPv4;
+            mEnableIPv6 = other.mEnableIPv6;
+            mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
+            mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
+            mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
+            mStaticIpConfig = other.mStaticIpConfig;
+            mApfCapabilities = other.mApfCapabilities;
+            mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
+            mIPv6AddrGenMode = other.mIPv6AddrGenMode;
+            mNetwork = other.mNetwork;
+            mDisplayName = other.mDisplayName;
+        }
+
+        @Override
+        public String toString() {
+            return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+                    .add("mEnableIPv4: " + mEnableIPv4)
+                    .add("mEnableIPv6: " + mEnableIPv6)
+                    .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
+                    .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
+                    .add("mInitialConfig: " + mInitialConfig)
+                    .add("mStaticIpConfig: " + mStaticIpConfig)
+                    .add("mApfCapabilities: " + mApfCapabilities)
+                    .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
+                    .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
+                    .add("mNetwork: " + mNetwork)
+                    .add("mDisplayName: " + mDisplayName)
+                    .toString();
+        }
+
+        public boolean isValid() {
+            return (mInitialConfig == null) || mInitialConfig.isValid();
+        }
+    }
+
+    public static class InitialConfiguration {
+        public final Set<LinkAddress> ipAddresses = new HashSet<>();
+        public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
+        public final Set<InetAddress> dnsServers = new HashSet<>();
+        public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
+
+        public static InitialConfiguration copy(InitialConfiguration config) {
+            if (config == null) {
+                return null;
+            }
+            InitialConfiguration configCopy = new InitialConfiguration();
+            configCopy.ipAddresses.addAll(config.ipAddresses);
+            configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
+            configCopy.dnsServers.addAll(config.dnsServers);
+            return configCopy;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(
+                    "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
+                    join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
+                    join(", ", dnsServers), gateway);
+        }
+
+        public boolean isValid() {
+            if (ipAddresses.isEmpty()) {
+                return false;
+            }
+
+            // For every IP address, there must be at least one prefix containing that address.
+            for (LinkAddress addr : ipAddresses) {
+                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
+                    return false;
+                }
+            }
+            // For every dns server, there must be at least one prefix containing that address.
+            for (InetAddress addr : dnsServers) {
+                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
+                    return false;
+                }
+            }
+            // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
+            // (read: compliant with RFC4291#section2.5.4).
+            if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
+                return false;
+            }
+            // If directlyConnectedRoutes contains an IPv6 default route
+            // then ipAddresses MUST contain at least one non-ULA GUA.
+            if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
+                    && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
+                return false;
+            }
+            // The prefix length of routes in directlyConnectedRoutes be within reasonable
+            // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
+            if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
+                return false;
+            }
+            // There no more than one IPv4 address
+            if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) {
+                return false;
+            }
+
+            return true;
+        }
+
+        /**
+         * @return true if the given list of addressess and routes satisfies provisioning for this
+         * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
+         * because addresses and routes seen by Netlink will contain additional fields like flags,
+         * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
+         * provisioning check always fails.
+         *
+         * If the given list of routes is null, only addresses are taken into considerations.
+         */
+        public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
+            if (ipAddresses.isEmpty()) {
+                return false;
+            }
+
+            for (LinkAddress addr : ipAddresses) {
+                if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
+                    return false;
+                }
+            }
+
+            if (routes != null) {
+                for (IpPrefix prefix : directlyConnectedRoutes) {
+                    if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
+            return !route.hasGateway() && prefix.equals(route.getDestination());
+        }
+
+        private static boolean isPrefixLengthCompliant(LinkAddress addr) {
+            return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
+        }
+
+        private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
+            return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
+        }
+
+        private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
+            return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
+                    && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
+        }
+
+        private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
+            return prefix.getAddress().equals(Inet6Address.ANY);
+        }
+
+        private static boolean isIPv6GUA(LinkAddress addr) {
+            return addr.isIPv6() && addr.isGlobalPreferred();
+        }
+    }
+
+    public static final String DUMP_ARG = "ipclient";
+    public static final String DUMP_ARG_CONFIRM = "confirm";
+
+    private static final int CMD_TERMINATE_AFTER_STOP             = 1;
+    private static final int CMD_STOP                             = 2;
+    private static final int CMD_START                            = 3;
+    private static final int CMD_CONFIRM                          = 4;
+    private static final int EVENT_PRE_DHCP_ACTION_COMPLETE       = 5;
+    // Sent by NetlinkTracker to communicate netlink events.
+    private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
+    private static final int CMD_UPDATE_TCP_BUFFER_SIZES          = 7;
+    private static final int CMD_UPDATE_HTTP_PROXY                = 8;
+    private static final int CMD_SET_MULTICAST_FILTER             = 9;
+    private static final int EVENT_PROVISIONING_TIMEOUT           = 10;
+    private static final int EVENT_DHCPACTION_TIMEOUT             = 11;
+
+    private static final int MAX_LOG_RECORDS = 500;
+    private static final int MAX_PACKET_RECORDS = 100;
+
+    private static final boolean NO_CALLBACKS = false;
+    private static final boolean SEND_CALLBACKS = true;
+
+    // This must match the interface prefix in clatd.c.
+    // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
+    private static final String CLAT_PREFIX = "v4-";
+
+    private final State mStoppedState = new StoppedState();
+    private final State mStoppingState = new StoppingState();
+    private final State mStartedState = new StartedState();
+    private final State mRunningState = new RunningState();
+
+    private final String mTag;
+    private final Context mContext;
+    private final String mInterfaceName;
+    private final String mClatInterfaceName;
+    @VisibleForTesting
+    protected final Callback mCallback;
+    private final INetworkManagementService mNwService;
+    private final NetlinkTracker mNetlinkTracker;
+    private final WakeupMessage mProvisioningTimeoutAlarm;
+    private final WakeupMessage mDhcpActionTimeoutAlarm;
+    private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
+    private final SharedLog mLog;
+    private final LocalLog mConnectivityPacketLog;
+    private final MessageHandlingLogger mMsgStateLogger;
+    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+    private final InterfaceController mInterfaceCtrl;
+
+    private NetworkInterface mNetworkInterface;
+
+    /**
+     * Non-final member variables accessed only from within our StateMachine.
+     */
+    private LinkProperties mLinkProperties;
+    private ProvisioningConfiguration mConfiguration;
+    private IpReachabilityMonitor mIpReachabilityMonitor;
+    private DhcpClient mDhcpClient;
+    private DhcpResults mDhcpResults;
+    private String mTcpBufferSizes;
+    private ProxyInfo mHttpProxy;
+    private ApfFilter mApfFilter;
+    private boolean mMulticastFiltering;
+    private long mStartTimeMillis;
+
+    public IpClient(Context context, String ifName, Callback callback) {
+        this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
+                ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)),
+                NetdService.getInstance());
+    }
+
+    /**
+     * An expanded constructor, useful for dependency injection.
+     * TODO: migrate all test users to mock IpClient directly and remove this ctor.
+     */
+    public IpClient(Context context, String ifName, Callback callback,
+            INetworkManagementService nwService) {
+        this(context, ifName, callback, nwService, NetdService.getInstance());
+    }
+
+    @VisibleForTesting
+    IpClient(Context context, String ifName, Callback callback,
+            INetworkManagementService nwService, INetd netd) {
+        super(IpClient.class.getSimpleName() + "." + ifName);
+        mTag = getName();
+
+        mContext = context;
+        mInterfaceName = ifName;
+        mClatInterfaceName = CLAT_PREFIX + ifName;
+        mCallback = new LoggingCallbackWrapper(callback);
+        mNwService = nwService;
+
+        mLog = new SharedLog(MAX_LOG_RECORDS, mTag);
+        mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
+        mMsgStateLogger = new MessageHandlingLogger();
+
+        mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog);
+
+        mNetlinkTracker = new NetlinkTracker(
+                mInterfaceName,
+                new NetlinkTracker.Callback() {
+                    @Override
+                    public void update() {
+                        sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+                    }
+                }) {
+            @Override
+            public void interfaceAdded(String iface) {
+                super.interfaceAdded(iface);
+                if (mClatInterfaceName.equals(iface)) {
+                    mCallback.setNeighborDiscoveryOffload(false);
+                } else if (!mInterfaceName.equals(iface)) {
+                    return;
+                }
+
+                final String msg = "interfaceAdded(" + iface +")";
+                logMsg(msg);
+            }
+
+            @Override
+            public void interfaceRemoved(String iface) {
+                super.interfaceRemoved(iface);
+                // TODO: Also observe mInterfaceName going down and take some
+                // kind of appropriate action.
+                if (mClatInterfaceName.equals(iface)) {
+                    // TODO: consider sending a message to the IpClient main
+                    // StateMachine thread, in case "NDO enabled" state becomes
+                    // tied to more things that 464xlat operation.
+                    mCallback.setNeighborDiscoveryOffload(true);
+                } else if (!mInterfaceName.equals(iface)) {
+                    return;
+                }
+
+                final String msg = "interfaceRemoved(" + iface +")";
+                logMsg(msg);
+            }
+
+            private void logMsg(String msg) {
+                Log.d(mTag, msg);
+                getHandler().post(() -> { mLog.log("OBSERVED " + msg); });
+            }
+        };
+
+        mLinkProperties = new LinkProperties();
+        mLinkProperties.setInterfaceName(mInterfaceName);
+
+        mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(),
+                () -> { mLog.log("OBSERVED AvoidBadWifi changed"); });
+
+        mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+                mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
+        mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+                mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
+
+        // Anything the StateMachine may access must have been instantiated
+        // before this point.
+        configureAndStartStateMachine();
+
+        // Anything that may send messages to the StateMachine must only be
+        // configured to do so after the StateMachine has started (above).
+        startStateMachineUpdaters();
+    }
+
+    private void configureAndStartStateMachine() {
+        addState(mStoppedState);
+        addState(mStartedState);
+            addState(mRunningState, mStartedState);
+        addState(mStoppingState);
+
+        setInitialState(mStoppedState);
+
+        super.start();
+    }
+
+    private void startStateMachineUpdaters() {
+        try {
+            mNwService.registerObserver(mNetlinkTracker);
+        } catch (RemoteException e) {
+            logError("Couldn't register NetlinkTracker: %s", e);
+        }
+
+        mMultinetworkPolicyTracker.start();
+    }
+
+    private void stopStateMachineUpdaters() {
+        try {
+            mNwService.unregisterObserver(mNetlinkTracker);
+        } catch (RemoteException e) {
+            logError("Couldn't unregister NetlinkTracker: %s", e);
+        }
+
+        mMultinetworkPolicyTracker.shutdown();
+    }
+
+    @Override
+    protected void onQuitting() {
+        mCallback.onQuit();
+    }
+
+    // Shut down this IpClient instance altogether.
+    public void shutdown() {
+        stop();
+        sendMessage(CMD_TERMINATE_AFTER_STOP);
+    }
+
+    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+        return new ProvisioningConfiguration.Builder();
+    }
+
+    public void startProvisioning(ProvisioningConfiguration req) {
+        if (!req.isValid()) {
+            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+            return;
+        }
+
+        getNetworkInterface();
+
+        mCallback.setNeighborDiscoveryOffload(true);
+        sendMessage(CMD_START, new ProvisioningConfiguration(req));
+    }
+
+    // TODO: Delete this.
+    public void startProvisioning(StaticIpConfiguration staticIpConfig) {
+        startProvisioning(buildProvisioningConfiguration()
+                .withStaticConfiguration(staticIpConfig)
+                .build());
+    }
+
+    public void startProvisioning() {
+        startProvisioning(new ProvisioningConfiguration());
+    }
+
+    public void stop() {
+        sendMessage(CMD_STOP);
+    }
+
+    public void confirmConfiguration() {
+        sendMessage(CMD_CONFIRM);
+    }
+
+    public void completedPreDhcpAction() {
+        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+    }
+
+    /**
+     * Set the TCP buffer sizes to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public void setTcpBufferSizes(String tcpBufferSizes) {
+        sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
+    }
+
+    /**
+     * Set the HTTP Proxy configuration to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public void setHttpProxy(ProxyInfo proxyInfo) {
+        sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
+    }
+
+    /**
+     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
+     * if not, Callback.setFallbackMulticastFilter() is called.
+     */
+    public void setMulticastFilter(boolean enabled) {
+        sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
+            // Execute confirmConfiguration() and take no further action.
+            confirmConfiguration();
+            return;
+        }
+
+        // Thread-unsafe access to mApfFilter but just used for debugging.
+        final ApfFilter apfFilter = mApfFilter;
+        final ProvisioningConfiguration provisioningConfig = mConfiguration;
+        final ApfCapabilities apfCapabilities = (provisioningConfig != null)
+                ? provisioningConfig.mApfCapabilities : null;
+
+        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+        pw.println(mTag + " APF dump:");
+        pw.increaseIndent();
+        if (apfFilter != null) {
+            apfFilter.dump(pw);
+        } else {
+            pw.print("No active ApfFilter; ");
+            if (provisioningConfig == null) {
+                pw.println("IpClient not yet started.");
+            } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
+                pw.println("Hardware does not support APF.");
+            } else {
+                pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
+            }
+        }
+        pw.decreaseIndent();
+
+        pw.println();
+        pw.println(mTag + " current ProvisioningConfiguration:");
+        pw.increaseIndent();
+        pw.println(Objects.toString(provisioningConfig, "N/A"));
+        pw.decreaseIndent();
+
+        pw.println();
+        pw.println(mTag + " StateMachine dump:");
+        pw.increaseIndent();
+        mLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+
+        pw.println();
+        pw.println(mTag + " connectivity packet log:");
+        pw.println();
+        pw.println("Debug with python and scapy via:");
+        pw.println("shell$ python");
+        pw.println(">>> from scapy import all as scapy");
+        pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
+        pw.println();
+
+        pw.increaseIndent();
+        mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
+        pw.decreaseIndent();
+    }
+
+
+    /**
+     * Internals.
+     */
+
+    @Override
+    protected String getWhatToString(int what) {
+        return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
+    }
+
+    @Override
+    protected String getLogRecString(Message msg) {
+        final String logLine = String.format(
+                "%s/%d %d %d %s [%s]",
+                mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
+                msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
+
+        final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
+        mLog.log(richerLogLine);
+        if (DBG) {
+            Log.d(mTag, richerLogLine);
+        }
+
+        mMsgStateLogger.reset();
+        return logLine;
+    }
+
+    @Override
+    protected boolean recordLogRec(Message msg) {
+        // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
+        // and we already log any LinkProperties change that results in an
+        // invocation of IpClient.Callback#onLinkPropertiesChange().
+        final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+        if (!shouldLog) {
+            mMsgStateLogger.reset();
+        }
+        return shouldLog;
+    }
+
+    private void logError(String fmt, Object... args) {
+        final String msg = "ERROR " + String.format(fmt, args);
+        Log.e(mTag, msg);
+        mLog.log(msg);
+    }
+
+    private void getNetworkInterface() {
+        try {
+            mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
+        } catch (SocketException | NullPointerException e) {
+            // TODO: throw new IllegalStateException.
+            logError("Failed to get interface object: %s", e);
+        }
+    }
+
+    // This needs to be called with care to ensure that our LinkProperties
+    // are in sync with the actual LinkProperties of the interface. For example,
+    // we should only call this if we know for sure that there are no IP addresses
+    // assigned to the interface, etc.
+    private void resetLinkProperties() {
+        mNetlinkTracker.clearLinkProperties();
+        mConfiguration = null;
+        mDhcpResults = null;
+        mTcpBufferSizes = "";
+        mHttpProxy = null;
+
+        mLinkProperties = new LinkProperties();
+        mLinkProperties.setInterfaceName(mInterfaceName);
+    }
+
+    private void recordMetric(final int type) {
+        if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
+        final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
+        mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
+    }
+
+    // For now: use WifiStateMachine's historical notion of provisioned.
+    @VisibleForTesting
+    static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
+        // For historical reasons, we should connect even if all we have is
+        // an IPv4 address and nothing else.
+        if (lp.hasIPv4Address() || lp.isProvisioned()) {
+            return true;
+        }
+        if (config == null) {
+            return false;
+        }
+
+        // When an InitialConfiguration is specified, ignore any difference with previous
+        // properties and instead check if properties observed match the desired properties.
+        return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
+    }
+
+    // TODO: Investigate folding all this into the existing static function
+    // LinkProperties.compareProvisioning() or some other single function that
+    // takes two LinkProperties objects and returns a ProvisioningChange
+    // object that is a correct and complete assessment of what changed, taking
+    // account of the asymmetries described in the comments in this function.
+    // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
+    private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
+        ProvisioningChange delta;
+        InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
+        final boolean wasProvisioned = isProvisioned(oldLp, config);
+        final boolean isProvisioned = isProvisioned(newLp, config);
+
+        if (!wasProvisioned && isProvisioned) {
+            delta = ProvisioningChange.GAINED_PROVISIONING;
+        } else if (wasProvisioned && isProvisioned) {
+            delta = ProvisioningChange.STILL_PROVISIONED;
+        } else if (!wasProvisioned && !isProvisioned) {
+            delta = ProvisioningChange.STILL_NOT_PROVISIONED;
+        } else {
+            // (wasProvisioned && !isProvisioned)
+            //
+            // Note that this is true even if we lose a configuration element
+            // (e.g., a default gateway) that would not be required to advance
+            // into provisioned state. This is intended: if we have a default
+            // router and we lose it, that's a sure sign of a problem, but if
+            // we connect to a network with no IPv4 DNS servers, we consider
+            // that to be a network without DNS servers and connect anyway.
+            //
+            // See the comment below.
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
+        final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
+        final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
+
+        // If bad wifi avoidance is disabled, then ignore IPv6 loss of
+        // provisioning. Otherwise, when a hotspot that loses Internet
+        // access sends out a 0-lifetime RA to its clients, the clients
+        // will disconnect and then reconnect, avoiding the bad hotspot,
+        // instead of getting stuck on the bad hotspot. http://b/31827713 .
+        //
+        // This is incorrect because if the hotspot then regains Internet
+        // access with a different prefix, TCP connections on the
+        // deprecated addresses will remain stuck.
+        //
+        // Note that we can still be disconnected by IpReachabilityMonitor
+        // if the IPv6 default gateway (but not the IPv6 DNS servers; see
+        // accompanying code in IpReachabilityMonitor) is unreachable.
+        final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi();
+
+        // Additionally:
+        //
+        // Partial configurations (e.g., only an IPv4 address with no DNS
+        // servers and no default route) are accepted as long as DHCPv4
+        // succeeds. On such a network, isProvisioned() will always return
+        // false, because the configuration is not complete, but we want to
+        // connect anyway. It might be a disconnected network such as a
+        // Chromecast or a wireless printer, for example.
+        //
+        // Because on such a network isProvisioned() will always return false,
+        // delta will never be LOST_PROVISIONING. So check for loss of
+        // provisioning here too.
+        if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        // Additionally:
+        //
+        // If the previous link properties had a global IPv6 address and an
+        // IPv6 default route then also consider the loss of that default route
+        // to be a loss of provisioning. See b/27962810.
+        if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        return delta;
+    }
+
+    private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
+        switch (delta) {
+            case GAINED_PROVISIONING:
+                if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); }
+                recordMetric(IpManagerEvent.PROVISIONING_OK);
+                mCallback.onProvisioningSuccess(newLp);
+                break;
+
+            case LOST_PROVISIONING:
+                if (DBG) { Log.d(mTag, "onProvisioningFailure()"); }
+                recordMetric(IpManagerEvent.PROVISIONING_FAIL);
+                mCallback.onProvisioningFailure(newLp);
+                break;
+
+            default:
+                if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
+                mCallback.onLinkPropertiesChange(newLp);
+                break;
+        }
+    }
+
+    // Updates all IpClient-related state concerned with LinkProperties.
+    // Returns a ProvisioningChange for possibly notifying other interested
+    // parties that are not fronted by IpClient.
+    private ProvisioningChange setLinkProperties(LinkProperties newLp) {
+        if (mApfFilter != null) {
+            mApfFilter.setLinkProperties(newLp);
+        }
+        if (mIpReachabilityMonitor != null) {
+            mIpReachabilityMonitor.updateLinkProperties(newLp);
+        }
+
+        ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
+        mLinkProperties = new LinkProperties(newLp);
+
+        if (delta == ProvisioningChange.GAINED_PROVISIONING) {
+            // TODO: Add a proper ProvisionedState and cancel the alarm in
+            // its enter() method.
+            mProvisioningTimeoutAlarm.cancel();
+        }
+
+        return delta;
+    }
+
+    private LinkProperties assembleLinkProperties() {
+        // [1] Create a new LinkProperties object to populate.
+        LinkProperties newLp = new LinkProperties();
+        newLp.setInterfaceName(mInterfaceName);
+
+        // [2] Pull in data from netlink:
+        //         - IPv4 addresses
+        //         - IPv6 addresses
+        //         - IPv6 routes
+        //         - IPv6 DNS servers
+        //
+        // N.B.: this is fundamentally race-prone and should be fixed by
+        // changing NetlinkTracker from a hybrid edge/level model to an
+        // edge-only model, or by giving IpClient its own netlink socket(s)
+        // so as to track all required information directly.
+        LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
+        newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
+        for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
+            newLp.addRoute(route);
+        }
+        addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
+
+        // [3] Add in data from DHCPv4, if available.
+        //
+        // mDhcpResults is never shared with any other owner so we don't have
+        // to worry about concurrent modification.
+        if (mDhcpResults != null) {
+            for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
+                newLp.addRoute(route);
+            }
+            addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
+            newLp.setDomains(mDhcpResults.domains);
+
+            if (mDhcpResults.mtu != 0) {
+                newLp.setMtu(mDhcpResults.mtu);
+            }
+        }
+
+        // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
+        if (!TextUtils.isEmpty(mTcpBufferSizes)) {
+            newLp.setTcpBufferSizes(mTcpBufferSizes);
+        }
+        if (mHttpProxy != null) {
+            newLp.setHttpProxy(mHttpProxy);
+        }
+
+        // [5] Add data from InitialConfiguration
+        if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
+            InitialConfiguration config = mConfiguration.mInitialConfig;
+            // Add InitialConfiguration routes and dns server addresses once all addresses
+            // specified in the InitialConfiguration have been observed with Netlink.
+            if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
+                for (IpPrefix prefix : config.directlyConnectedRoutes) {
+                    newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
+                }
+            }
+            addAllReachableDnsServers(newLp, config.dnsServers);
+        }
+        final LinkProperties oldLp = mLinkProperties;
+        if (DBG) {
+            Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
+                    netlinkLinkProperties, newLp, oldLp));
+        }
+
+        // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
+        // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
+        return newLp;
+    }
+
+    private static void addAllReachableDnsServers(
+            LinkProperties lp, Iterable<InetAddress> dnses) {
+        // TODO: Investigate deleting this reachability check.  We should be
+        // able to pass everything down to netd and let netd do evaluation
+        // and RFC6724-style sorting.
+        for (InetAddress dns : dnses) {
+            if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
+                lp.addDnsServer(dns);
+            }
+        }
+    }
+
+    // Returns false if we have lost provisioning, true otherwise.
+    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
+        final LinkProperties newLp = assembleLinkProperties();
+        if (Objects.equals(newLp, mLinkProperties)) {
+            return true;
+        }
+        final ProvisioningChange delta = setLinkProperties(newLp);
+        if (sendCallbacks) {
+            dispatchCallback(delta, newLp);
+        }
+        return (delta != ProvisioningChange.LOST_PROVISIONING);
+    }
+
+    private void handleIPv4Success(DhcpResults dhcpResults) {
+        mDhcpResults = new DhcpResults(dhcpResults);
+        final LinkProperties newLp = assembleLinkProperties();
+        final ProvisioningChange delta = setLinkProperties(newLp);
+
+        if (DBG) {
+            Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
+        }
+        mCallback.onNewDhcpResults(dhcpResults);
+        dispatchCallback(delta, newLp);
+    }
+
+    private void handleIPv4Failure() {
+        // TODO: Investigate deleting this clearIPv4Address() call.
+        //
+        // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
+        // that could trigger a call to this function. If we missed handling
+        // that message in StartedState for some reason we would still clear
+        // any addresses upon entry to StoppedState.
+        mInterfaceCtrl.clearIPv4Address();
+        mDhcpResults = null;
+        if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
+        mCallback.onNewDhcpResults(null);
+
+        handleProvisioningFailure();
+    }
+
+    private void handleProvisioningFailure() {
+        final LinkProperties newLp = assembleLinkProperties();
+        ProvisioningChange delta = setLinkProperties(newLp);
+        // If we've gotten here and we're still not provisioned treat that as
+        // a total loss of provisioning.
+        //
+        // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
+        // there was no usable IPv6 obtained before a non-zero provisioning
+        // timeout expired.
+        //
+        // Regardless: GAME OVER.
+        if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        dispatchCallback(delta, newLp);
+        if (delta == ProvisioningChange.LOST_PROVISIONING) {
+            transitionTo(mStoppingState);
+        }
+    }
+
+    private void doImmediateProvisioningFailure(int failureType) {
+        logError("onProvisioningFailure(): %s", failureType);
+        recordMetric(failureType);
+        mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+    }
+
+    private boolean startIPv4() {
+        // If we have a StaticIpConfiguration attempt to apply it and
+        // handle the result accordingly.
+        if (mConfiguration.mStaticIpConfig != null) {
+            if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
+                handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
+            } else {
+                return false;
+            }
+        } else {
+            // Start DHCPv4.
+            mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceName);
+            mDhcpClient.registerForPreDhcpNotification();
+            mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+        }
+
+        return true;
+    }
+
+    private boolean startIPv6() {
+        return mInterfaceCtrl.setIPv6PrivacyExtensions(true) &&
+               mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) &&
+               mInterfaceCtrl.enableIPv6();
+    }
+
+    private boolean applyInitialConfig(InitialConfiguration config) {
+        // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
+        for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
+            if (!mInterfaceCtrl.addAddress(addr)) return false;
+        }
+
+        return true;
+    }
+
+    private boolean startIpReachabilityMonitor() {
+        try {
+            mIpReachabilityMonitor = new IpReachabilityMonitor(
+                    mContext,
+                    mInterfaceName,
+                    mLog,
+                    new IpReachabilityMonitor.Callback() {
+                        @Override
+                        public void notifyLost(InetAddress ip, String logMsg) {
+                            mCallback.onReachabilityLost(logMsg);
+                        }
+                    },
+                    mMultinetworkPolicyTracker);
+        } catch (IllegalArgumentException iae) {
+            // Failed to start IpReachabilityMonitor. Log it and call
+            // onProvisioningFailure() immediately.
+            //
+            // See http://b/31038971.
+            logError("IpReachabilityMonitor failure: %s", iae);
+            mIpReachabilityMonitor = null;
+        }
+
+        return (mIpReachabilityMonitor != null);
+    }
+
+    private void stopAllIP() {
+        // We don't need to worry about routes, just addresses, because:
+        //     - disableIpv6() will clear autoconf IPv6 routes as well, and
+        //     - we don't get IPv4 routes from netlink
+        // so we neither react to nor need to wait for changes in either.
+
+        mInterfaceCtrl.disableIPv6();
+        mInterfaceCtrl.clearAllAddresses();
+    }
+
+    class StoppedState extends State {
+        @Override
+        public void enter() {
+            stopAllIP();
+
+            resetLinkProperties();
+            if (mStartTimeMillis > 0) {
+                recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
+                mStartTimeMillis = 0;
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_TERMINATE_AFTER_STOP:
+                    stopStateMachineUpdaters();
+                    quit();
+                    break;
+
+                case CMD_STOP:
+                    break;
+
+                case CMD_START:
+                    mConfiguration = (ProvisioningConfiguration) msg.obj;
+                    transitionTo(mStartedState);
+                    break;
+
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_TCP_BUFFER_SIZES:
+                    mTcpBufferSizes = (String) msg.obj;
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_HTTP_PROXY:
+                    mHttpProxy = (ProxyInfo) msg.obj;
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_SET_MULTICAST_FILTER:
+                    mMulticastFiltering = (boolean) msg.obj;
+                    break;
+
+                case DhcpClient.CMD_ON_QUIT:
+                    // Everything is already stopped.
+                    logError("Unexpected CMD_ON_QUIT (already stopped).");
+                    break;
+
+                default:
+                    return NOT_HANDLED;
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+    }
+
+    class StoppingState extends State {
+        @Override
+        public void enter() {
+            if (mDhcpClient == null) {
+                // There's no DHCPv4 for which to wait; proceed to stopped.
+                transitionTo(mStoppedState);
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_STOP:
+                    break;
+
+                case DhcpClient.CMD_CLEAR_LINKADDRESS:
+                    mInterfaceCtrl.clearIPv4Address();
+                    break;
+
+                case DhcpClient.CMD_ON_QUIT:
+                    mDhcpClient = null;
+                    transitionTo(mStoppedState);
+                    break;
+
+                default:
+                    deferMessage(msg);
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+    }
+
+    class StartedState extends State {
+        @Override
+        public void enter() {
+            mStartTimeMillis = SystemClock.elapsedRealtime();
+
+            if (mConfiguration.mProvisioningTimeoutMs > 0) {
+                final long alarmTime = SystemClock.elapsedRealtime() +
+                        mConfiguration.mProvisioningTimeoutMs;
+                mProvisioningTimeoutAlarm.schedule(alarmTime);
+            }
+
+            if (readyToProceed()) {
+                transitionTo(mRunningState);
+            } else {
+                // Clear all IPv4 and IPv6 before proceeding to RunningState.
+                // Clean up any leftover state from an abnormal exit from
+                // tethering or during an IpClient restart.
+                stopAllIP();
+            }
+        }
+
+        @Override
+        public void exit() {
+            mProvisioningTimeoutAlarm.cancel();
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_STOP:
+                    transitionTo(mStoppingState);
+                    break;
+
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    if (readyToProceed()) {
+                        transitionTo(mRunningState);
+                    }
+                    break;
+
+                case EVENT_PROVISIONING_TIMEOUT:
+                    handleProvisioningFailure();
+                    break;
+
+                default:
+                    // It's safe to process messages out of order because the
+                    // only message that can both
+                    //     a) be received at this time and
+                    //     b) affect provisioning state
+                    // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
+                    deferMessage(msg);
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+
+        boolean readyToProceed() {
+            return (!mLinkProperties.hasIPv4Address() &&
+                    !mLinkProperties.hasGlobalIPv6Address());
+        }
+    }
+
+    class RunningState extends State {
+        private ConnectivityPacketTracker mPacketTracker;
+        private boolean mDhcpActionInFlight;
+
+        @Override
+        public void enter() {
+            // Get the Configuration for ApfFilter from Context
+            final boolean filter802_3Frames =
+                    mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
+
+            final int[] ethTypeBlackList = mContext.getResources().getIntArray(
+                    R.array.config_apfEthTypeBlackList);
+
+            mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
+                    mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
+            // TODO: investigate the effects of any multicast filtering racing/interfering with the
+            // rest of this IP configuration startup.
+            if (mApfFilter == null) {
+                mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+            }
+
+            mPacketTracker = createPacketTracker();
+            if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
+
+            if (mConfiguration.mEnableIPv6 && !startIPv6()) {
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
+                transitionTo(mStoppingState);
+                return;
+            }
+
+            if (mConfiguration.mEnableIPv4 && !startIPv4()) {
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
+                transitionTo(mStoppingState);
+                return;
+            }
+
+            final InitialConfiguration config = mConfiguration.mInitialConfig;
+            if ((config != null) && !applyInitialConfig(config)) {
+                // TODO introduce a new IpManagerEvent constant to distinguish this error case.
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+                transitionTo(mStoppingState);
+                return;
+            }
+
+            if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
+                doImmediateProvisioningFailure(
+                        IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
+                transitionTo(mStoppingState);
+                return;
+            }
+        }
+
+        @Override
+        public void exit() {
+            stopDhcpAction();
+
+            if (mIpReachabilityMonitor != null) {
+                mIpReachabilityMonitor.stop();
+                mIpReachabilityMonitor = null;
+            }
+
+            if (mDhcpClient != null) {
+                mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
+                mDhcpClient.doQuit();
+            }
+
+            if (mPacketTracker != null) {
+                mPacketTracker.stop();
+                mPacketTracker = null;
+            }
+
+            if (mApfFilter != null) {
+                mApfFilter.shutdown();
+                mApfFilter = null;
+            }
+
+            resetLinkProperties();
+        }
+
+        private ConnectivityPacketTracker createPacketTracker() {
+            try {
+                return new ConnectivityPacketTracker(
+                        getHandler(), mNetworkInterface, mConnectivityPacketLog);
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+        }
+
+        private void ensureDhcpAction() {
+            if (!mDhcpActionInFlight) {
+                mCallback.onPreDhcpAction();
+                mDhcpActionInFlight = true;
+                final long alarmTime = SystemClock.elapsedRealtime() +
+                        mConfiguration.mRequestedPreDhcpActionMs;
+                mDhcpActionTimeoutAlarm.schedule(alarmTime);
+            }
+        }
+
+        private void stopDhcpAction() {
+            mDhcpActionTimeoutAlarm.cancel();
+            if (mDhcpActionInFlight) {
+                mCallback.onPostDhcpAction();
+                mDhcpActionInFlight = false;
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_STOP:
+                    transitionTo(mStoppingState);
+                    break;
+
+                case CMD_START:
+                    logError("ALERT: START received in StartedState. Please fix caller.");
+                    break;
+
+                case CMD_CONFIRM:
+                    // TODO: Possibly introduce a second type of confirmation
+                    // that both probes (a) on-link neighbors and (b) does
+                    // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
+                    // roams.
+                    if (mIpReachabilityMonitor != null) {
+                        mIpReachabilityMonitor.probeAll();
+                    }
+                    break;
+
+                case EVENT_PRE_DHCP_ACTION_COMPLETE:
+                    // It's possible to reach here if, for example, someone
+                    // calls completedPreDhcpAction() after provisioning with
+                    // a static IP configuration.
+                    if (mDhcpClient != null) {
+                        mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
+                    }
+                    break;
+
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
+                        transitionTo(mStoppingState);
+                    }
+                    break;
+
+                case CMD_UPDATE_TCP_BUFFER_SIZES:
+                    mTcpBufferSizes = (String) msg.obj;
+                    // This cannot possibly change provisioning state.
+                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_HTTP_PROXY:
+                    mHttpProxy = (ProxyInfo) msg.obj;
+                    // This cannot possibly change provisioning state.
+                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
+                    break;
+
+                case CMD_SET_MULTICAST_FILTER: {
+                    mMulticastFiltering = (boolean) msg.obj;
+                    if (mApfFilter != null) {
+                        mApfFilter.setMulticastFilter(mMulticastFiltering);
+                    } else {
+                        mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+                    }
+                    break;
+                }
+
+                case EVENT_DHCPACTION_TIMEOUT:
+                    stopDhcpAction();
+                    break;
+
+                case DhcpClient.CMD_PRE_DHCP_ACTION:
+                    if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
+                        ensureDhcpAction();
+                    } else {
+                        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+                    }
+                    break;
+
+                case DhcpClient.CMD_CLEAR_LINKADDRESS:
+                    mInterfaceCtrl.clearIPv4Address();
+                    break;
+
+                case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
+                    final LinkAddress ipAddress = (LinkAddress) msg.obj;
+                    if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
+                        mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
+                    } else {
+                        logError("Failed to set IPv4 address.");
+                        dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
+                                new LinkProperties(mLinkProperties));
+                        transitionTo(mStoppingState);
+                    }
+                    break;
+                }
+
+                // This message is only received when:
+                //
+                //     a) initial address acquisition succeeds,
+                //     b) renew succeeds or is NAK'd,
+                //     c) rebind succeeds or is NAK'd, or
+                //     c) the lease expires,
+                //
+                // but never when initial address acquisition fails. The latter
+                // condition is now governed by the provisioning timeout.
+                case DhcpClient.CMD_POST_DHCP_ACTION:
+                    stopDhcpAction();
+
+                    switch (msg.arg1) {
+                        case DhcpClient.DHCP_SUCCESS:
+                            handleIPv4Success((DhcpResults) msg.obj);
+                            break;
+                        case DhcpClient.DHCP_FAILURE:
+                            handleIPv4Failure();
+                            break;
+                        default:
+                            logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
+                    }
+                    break;
+
+                case DhcpClient.CMD_ON_QUIT:
+                    // DHCPv4 quit early for some reason.
+                    logError("Unexpected CMD_ON_QUIT.");
+                    mDhcpClient = null;
+                    break;
+
+                default:
+                    return NOT_HANDLED;
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+    }
+
+    private static class MessageHandlingLogger {
+        public String processedInState;
+        public String receivedInState;
+
+        public void reset() {
+            processedInState = null;
+            receivedInState = null;
+        }
+
+        public void handled(State processedIn, IState receivedIn) {
+            processedInState = processedIn.getClass().getSimpleName();
+            receivedInState = receivedIn.getName();
+        }
+
+        public String toString() {
+            return String.format("rcvd_in=%s, proc_in=%s",
+                                 receivedInState, processedInState);
+        }
+    }
+
+    // TODO: extract out into CollectionUtils.
+    static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+        for (T t : coll) {
+            if (fn.test(t)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+        return !any(coll, not(fn));
+    }
+
+    static <T> Predicate<T> not(Predicate<T> fn) {
+        return (t) -> !fn.test(t);
+    }
+
+    static <T> String join(String delimiter, Collection<T> coll) {
+        return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
+    }
+
+    static <T> T find(Iterable<T> coll, Predicate<T> fn) {
+        for (T t: coll) {
+            if (fn.test(t)) {
+              return t;
+            }
+        }
+        return null;
+    }
+
+    static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
+        return coll.stream().filter(fn).collect(Collectors.toList());
+    }
+}
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index e33f6c9..b12cb32 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 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,132 +16,112 @@
 
 package android.net.ip;
 
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.WakeupMessage;
-
 import android.content.Context;
-import android.net.DhcpResults;
 import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties.ProvisioningChange;
 import android.net.LinkProperties;
 import android.net.Network;
-import android.net.ProxyInfo;
-import android.net.RouteInfo;
 import android.net.StaticIpConfiguration;
 import android.net.apf.ApfCapabilities;
-import android.net.apf.ApfFilter;
-import android.net.dhcp.DhcpClient;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.IpManagerEvent;
-import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
-import android.net.util.NetworkConstants;
-import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
-import android.os.Message;
-import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.Log;
-import android.util.SparseArray;
+import android.net.apf.ApfCapabilities;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.R;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.IState;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.server.net.NetlinkTracker;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.List;
-import java.util.Set;
-import java.util.StringJoiner;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 
-/**
- * IpManager
- *
- * This class provides the interface to IP-layer provisioning and maintenance
- * functionality that can be used by transport layers like Wi-Fi, Ethernet,
- * et cetera.
- *
- * [ Lifetime ]
- * IpManager is designed to be instantiated as soon as the interface name is
- * known and can be as long-lived as the class containing it (i.e. declaring
- * it "private final" is okay).
+/*
+ * TODO: Delete this altogether in favor of its renamed successor: IpClient.
  *
  * @hide
  */
-public class IpManager extends StateMachine {
-    private static final boolean DBG = false;
+public class IpManager extends IpClient {
+    public static class ProvisioningConfiguration extends IpClient.ProvisioningConfiguration {
+        public ProvisioningConfiguration(IpClient.ProvisioningConfiguration ipcConfig) {
+            super(ipcConfig);
+        }
 
-    // For message logging.
-    private static final Class[] sMessageClasses = { IpManager.class, DhcpClient.class };
-    private static final SparseArray<String> sWhatToString =
-            MessageUtils.findMessageNames(sMessageClasses);
+        public static class Builder extends IpClient.ProvisioningConfiguration.Builder {
+            @Override
+            public Builder withoutIPv4() {
+                super.withoutIPv4();
+                return this;
+            }
+            @Override
+            public Builder withoutIPv6() {
+                super.withoutIPv6();
+                return this;
+            }
+            @Override
+            public Builder withoutIpReachabilityMonitor() {
+                super.withoutIpReachabilityMonitor();
+                return this;
+            }
+            @Override
+            public Builder withPreDhcpAction() {
+                super.withPreDhcpAction();
+                return this;
+            }
+            @Override
+            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+                super.withPreDhcpAction(dhcpActionTimeoutMs);
+                return this;
+            }
+            // No Override; locally defined type.
+            public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+                super.withInitialConfiguration((IpClient.InitialConfiguration) initialConfig);
+                return this;
+            }
+            @Override
+            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+                super.withStaticConfiguration(staticConfig);
+                return this;
+            }
+            @Override
+            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+                super.withApfCapabilities(apfCapabilities);
+                return this;
+            }
+            @Override
+            public Builder withProvisioningTimeoutMs(int timeoutMs) {
+                super.withProvisioningTimeoutMs(timeoutMs);
+                return this;
+            }
+            @Override
+            public Builder withIPv6AddrGenModeEUI64() {
+                super.withIPv6AddrGenModeEUI64();
+                return this;
+            }
+            @Override
+            public Builder withIPv6AddrGenModeStablePrivacy() {
+                super.withIPv6AddrGenModeStablePrivacy();
+                return this;
+            }
+            @Override
+            public Builder withNetwork(Network network) {
+                super.withNetwork(network);
+                return this;
+            }
+            @Override
+            public Builder withDisplayName(String displayName) {
+                super.withDisplayName(displayName);
+                return this;
+            }
+            @Override
+            public ProvisioningConfiguration build() {
+                return new ProvisioningConfiguration(super.build());
+            }
+        }
+    }
 
-    /**
-     * Callbacks for handling IpManager events.
-     */
-    public static class Callback {
-        // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
-        // when constructing a ProvisioningConfiguration.
-        //
-        // Implementations of onPreDhcpAction() must call
-        // IpManager#completedPreDhcpAction() to indicate that DHCP is clear
-        // to proceed.
-        public void onPreDhcpAction() {}
-        public void onPostDhcpAction() {}
+    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+        return new ProvisioningConfiguration.Builder();
+    }
 
-        // This is purely advisory and not an indication of provisioning
-        // success or failure.  This is only here for callers that want to
-        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
-        // DHCPv4 or static IPv4 configuration failure or success can be
-        // determined by whether or not the passed-in DhcpResults object is
-        // null or not.
-        public void onNewDhcpResults(DhcpResults dhcpResults) {}
+    public static class InitialConfiguration extends IpClient.InitialConfiguration {
+    }
 
-        public void onProvisioningSuccess(LinkProperties newLp) {}
-        public void onProvisioningFailure(LinkProperties newLp) {}
-
-        // Invoked on LinkProperties changes.
-        public void onLinkPropertiesChange(LinkProperties newLp) {}
-
-        // Called when the internal IpReachabilityMonitor (if enabled) has
-        // detected the loss of a critical number of required neighbors.
-        public void onReachabilityLost(String logMsg) {}
-
-        // Called when the IpManager state machine terminates.
-        public void onQuit() {}
-
-        // Install an APF program to filter incoming packets.
-        public void installPacketFilter(byte[] filter) {}
-
-        // If multicast filtering cannot be accomplished with APF, this function will be called to
-        // actuate multicast filtering using another means.
-        public void setFallbackMulticastFilter(boolean enabled) {}
-
-        // Enabled/disable Neighbor Discover offload functionality. This is
-        // called, for example, whenever 464xlat is being started or stopped.
-        public void setNeighborDiscoveryOffload(boolean enable) {}
+    public static class Callback extends IpClient.Callback {
     }
 
     public static class WaitForProvisioningCallback extends Callback {
@@ -173,1569 +153,24 @@
         }
     }
 
-    // Use a wrapper class to log in order to ensure complete and detailed
-    // logging. This method is lighter weight than annotations/reflection
-    // and has the following benefits:
-    //
-    //     - No invoked method can be forgotten.
-    //       Any new method added to IpManager.Callback must be overridden
-    //       here or it will never be called.
-    //
-    //     - No invoking call site can be forgotten.
-    //       Centralized logging in this way means call sites don't need to
-    //       remember to log, and therefore no call site can be forgotten.
-    //
-    //     - No variation in log format among call sites.
-    //       Encourages logging of any available arguments, and all call sites
-    //       are necessarily logged identically.
-    //
-    // TODO: Find an lighter weight approach.
-    private class LoggingCallbackWrapper extends Callback {
-        private static final String PREFIX = "INVOKE ";
-        private Callback mCallback;
-
-        public LoggingCallbackWrapper(Callback callback) {
-            mCallback = callback;
-        }
-
-        private void log(String msg) {
-            mLog.log(PREFIX + msg);
-        }
-
-        @Override
-        public void onPreDhcpAction() {
-            mCallback.onPreDhcpAction();
-            log("onPreDhcpAction()");
-        }
-        @Override
-        public void onPostDhcpAction() {
-            mCallback.onPostDhcpAction();
-            log("onPostDhcpAction()");
-        }
-        @Override
-        public void onNewDhcpResults(DhcpResults dhcpResults) {
-            mCallback.onNewDhcpResults(dhcpResults);
-            log("onNewDhcpResults({" + dhcpResults + "})");
-        }
-        @Override
-        public void onProvisioningSuccess(LinkProperties newLp) {
-            mCallback.onProvisioningSuccess(newLp);
-            log("onProvisioningSuccess({" + newLp + "})");
-        }
-        @Override
-        public void onProvisioningFailure(LinkProperties newLp) {
-            mCallback.onProvisioningFailure(newLp);
-            log("onProvisioningFailure({" + newLp + "})");
-        }
-        @Override
-        public void onLinkPropertiesChange(LinkProperties newLp) {
-            mCallback.onLinkPropertiesChange(newLp);
-            log("onLinkPropertiesChange({" + newLp + "})");
-        }
-        @Override
-        public void onReachabilityLost(String logMsg) {
-            mCallback.onReachabilityLost(logMsg);
-            log("onReachabilityLost(" + logMsg + ")");
-        }
-        @Override
-        public void onQuit() {
-            mCallback.onQuit();
-            log("onQuit()");
-        }
-        @Override
-        public void installPacketFilter(byte[] filter) {
-            mCallback.installPacketFilter(filter);
-            log("installPacketFilter(byte[" + filter.length + "])");
-        }
-        @Override
-        public void setFallbackMulticastFilter(boolean enabled) {
-            mCallback.setFallbackMulticastFilter(enabled);
-            log("setFallbackMulticastFilter(" + enabled + ")");
-        }
-        @Override
-        public void setNeighborDiscoveryOffload(boolean enable) {
-            mCallback.setNeighborDiscoveryOffload(enable);
-            log("setNeighborDiscoveryOffload(" + enable + ")");
-        }
-    }
-
-    /**
-     * This class encapsulates parameters to be passed to
-     * IpManager#startProvisioning(). A defensive copy is made by IpManager
-     * and the values specified herein are in force until IpManager#stop()
-     * is called.
-     *
-     * Example use:
-     *
-     *     final ProvisioningConfiguration config =
-     *             mIpManager.buildProvisioningConfiguration()
-     *                     .withPreDhcpAction()
-     *                     .withProvisioningTimeoutMs(36 * 1000)
-     *                     .build();
-     *     mIpManager.startProvisioning(config);
-     *     ...
-     *     mIpManager.stop();
-     *
-     * The specified provisioning configuration will only be active until
-     * IpManager#stop() is called. Future calls to IpManager#startProvisioning()
-     * must specify the configuration again.
-     */
-    public static class ProvisioningConfiguration {
-        // TODO: Delete this default timeout once those callers that care are
-        // fixed to pass in their preferred timeout.
-        //
-        // We pick 36 seconds so we can send DHCP requests at
-        //
-        //     t=0, t=2, t=6, t=14, t=30
-        //
-        // allowing for 10% jitter.
-        private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
-
-        public static class Builder {
-            private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
-
-            public Builder withoutIPv4() {
-                mConfig.mEnableIPv4 = false;
-                return this;
-            }
-
-            public Builder withoutIPv6() {
-                mConfig.mEnableIPv6 = false;
-                return this;
-            }
-
-            public Builder withoutIpReachabilityMonitor() {
-                mConfig.mUsingIpReachabilityMonitor = false;
-                return this;
-            }
-
-            public Builder withPreDhcpAction() {
-                mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
-                return this;
-            }
-
-            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
-                mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
-                return this;
-            }
-
-            public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
-                mConfig.mInitialConfig = initialConfig;
-                return this;
-            }
-
-            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
-                mConfig.mStaticIpConfig = staticConfig;
-                return this;
-            }
-
-            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
-                mConfig.mApfCapabilities = apfCapabilities;
-                return this;
-            }
-
-            public Builder withProvisioningTimeoutMs(int timeoutMs) {
-                mConfig.mProvisioningTimeoutMs = timeoutMs;
-                return this;
-            }
-
-            public Builder withIPv6AddrGenModeEUI64() {
-                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
-                return this;
-            }
-
-            public Builder withIPv6AddrGenModeStablePrivacy() {
-                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
-                return this;
-            }
-
-            public Builder withNetwork(Network network) {
-                mConfig.mNetwork = network;
-                return this;
-            }
-
-            public Builder withDisplayName(String displayName) {
-                mConfig.mDisplayName = displayName;
-                return this;
-            }
-
-            public ProvisioningConfiguration build() {
-                return new ProvisioningConfiguration(mConfig);
-            }
-        }
-
-        /* package */ boolean mEnableIPv4 = true;
-        /* package */ boolean mEnableIPv6 = true;
-        /* package */ boolean mUsingIpReachabilityMonitor = true;
-        /* package */ int mRequestedPreDhcpActionMs;
-        /* package */ InitialConfiguration mInitialConfig;
-        /* package */ StaticIpConfiguration mStaticIpConfig;
-        /* package */ ApfCapabilities mApfCapabilities;
-        /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
-        /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
-        /* package */ Network mNetwork = null;
-        /* package */ String mDisplayName = null;
-
-        public ProvisioningConfiguration() {} // used by Builder
-
-        public ProvisioningConfiguration(ProvisioningConfiguration other) {
-            mEnableIPv4 = other.mEnableIPv4;
-            mEnableIPv6 = other.mEnableIPv6;
-            mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
-            mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
-            mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
-            mStaticIpConfig = other.mStaticIpConfig;
-            mApfCapabilities = other.mApfCapabilities;
-            mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
-            mIPv6AddrGenMode = other.mIPv6AddrGenMode;
-            mNetwork = other.mNetwork;
-            mDisplayName = other.mDisplayName;
-        }
-
-        @Override
-        public String toString() {
-            return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
-                    .add("mEnableIPv4: " + mEnableIPv4)
-                    .add("mEnableIPv6: " + mEnableIPv6)
-                    .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
-                    .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
-                    .add("mInitialConfig: " + mInitialConfig)
-                    .add("mStaticIpConfig: " + mStaticIpConfig)
-                    .add("mApfCapabilities: " + mApfCapabilities)
-                    .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
-                    .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
-                    .add("mNetwork: " + mNetwork)
-                    .add("mDisplayName: " + mDisplayName)
-                    .toString();
-        }
-
-        public boolean isValid() {
-            return (mInitialConfig == null) || mInitialConfig.isValid();
-        }
-    }
-
-    public static class InitialConfiguration {
-        public final Set<LinkAddress> ipAddresses = new HashSet<>();
-        public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
-        public final Set<InetAddress> dnsServers = new HashSet<>();
-        public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
-
-        public static InitialConfiguration copy(InitialConfiguration config) {
-            if (config == null) {
-                return null;
-            }
-            InitialConfiguration configCopy = new InitialConfiguration();
-            configCopy.ipAddresses.addAll(config.ipAddresses);
-            configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
-            configCopy.dnsServers.addAll(config.dnsServers);
-            return configCopy;
-        }
-
-        @Override
-        public String toString() {
-            return String.format(
-                    "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
-                    join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
-                    join(", ", dnsServers), gateway);
-        }
-
-        public boolean isValid() {
-            if (ipAddresses.isEmpty()) {
-                return false;
-            }
-
-            // For every IP address, there must be at least one prefix containing that address.
-            for (LinkAddress addr : ipAddresses) {
-                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
-                    return false;
-                }
-            }
-            // For every dns server, there must be at least one prefix containing that address.
-            for (InetAddress addr : dnsServers) {
-                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
-                    return false;
-                }
-            }
-            // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
-            // (read: compliant with RFC4291#section2.5.4).
-            if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
-                return false;
-            }
-            // If directlyConnectedRoutes contains an IPv6 default route
-            // then ipAddresses MUST contain at least one non-ULA GUA.
-            if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
-                    && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
-                return false;
-            }
-            // The prefix length of routes in directlyConnectedRoutes be within reasonable
-            // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
-            if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
-                return false;
-            }
-            // There no more than one IPv4 address
-            if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) {
-                return false;
-            }
-
-            return true;
-        }
-
-        /**
-         * @return true if the given list of addressess and routes satisfies provisioning for this
-         * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
-         * because addresses and routes seen by Netlink will contain additional fields like flags,
-         * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
-         * provisioning check always fails.
-         *
-         * If the given list of routes is null, only addresses are taken into considerations.
-         */
-        public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
-            if (ipAddresses.isEmpty()) {
-                return false;
-            }
-
-            for (LinkAddress addr : ipAddresses) {
-                if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
-                    return false;
-                }
-            }
-
-            if (routes != null) {
-                for (IpPrefix prefix : directlyConnectedRoutes) {
-                    if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
-                        return false;
-                    }
-                }
-            }
-
-            return true;
-        }
-
-        private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
-            return !route.hasGateway() && prefix.equals(route.getDestination());
-        }
-
-        private static boolean isPrefixLengthCompliant(LinkAddress addr) {
-            return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
-        }
-
-        private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
-            return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
-        }
-
-        private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
-            return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
-                    && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
-        }
-
-        private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
-            return prefix.getAddress().equals(Inet6Address.ANY);
-        }
-
-        private static boolean isIPv6GUA(LinkAddress addr) {
-            return addr.isIPv6() && addr.isGlobalPreferred();
-        }
-    }
-
-    public static final String DUMP_ARG = "ipmanager";
-    public static final String DUMP_ARG_CONFIRM = "confirm";
-
-    private static final int CMD_TERMINATE_AFTER_STOP             = 1;
-    private static final int CMD_STOP                             = 2;
-    private static final int CMD_START                            = 3;
-    private static final int CMD_CONFIRM                          = 4;
-    private static final int EVENT_PRE_DHCP_ACTION_COMPLETE       = 5;
-    // Sent by NetlinkTracker to communicate netlink events.
-    private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
-    private static final int CMD_UPDATE_TCP_BUFFER_SIZES          = 7;
-    private static final int CMD_UPDATE_HTTP_PROXY                = 8;
-    private static final int CMD_SET_MULTICAST_FILTER             = 9;
-    private static final int EVENT_PROVISIONING_TIMEOUT           = 10;
-    private static final int EVENT_DHCPACTION_TIMEOUT             = 11;
-
-    private static final int MAX_LOG_RECORDS = 500;
-    private static final int MAX_PACKET_RECORDS = 100;
-
-    private static final boolean NO_CALLBACKS = false;
-    private static final boolean SEND_CALLBACKS = true;
-
-    // This must match the interface prefix in clatd.c.
-    // TODO: Revert this hack once IpManager and Nat464Xlat work in concert.
-    private static final String CLAT_PREFIX = "v4-";
-
-    private final State mStoppedState = new StoppedState();
-    private final State mStoppingState = new StoppingState();
-    private final State mStartedState = new StartedState();
-    private final State mRunningState = new RunningState();
-
-    private final String mTag;
-    private final Context mContext;
-    private final String mInterfaceName;
-    private final String mClatInterfaceName;
-    @VisibleForTesting
-    protected final Callback mCallback;
-    private final INetworkManagementService mNwService;
-    private final NetlinkTracker mNetlinkTracker;
-    private final WakeupMessage mProvisioningTimeoutAlarm;
-    private final WakeupMessage mDhcpActionTimeoutAlarm;
-    private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
-    private final SharedLog mLog;
-    private final LocalLog mConnectivityPacketLog;
-    private final MessageHandlingLogger mMsgStateLogger;
-    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-    private final InterfaceController mInterfaceCtrl;
-
-    private NetworkInterface mNetworkInterface;
-
-    /**
-     * Non-final member variables accessed only from within our StateMachine.
-     */
-    private LinkProperties mLinkProperties;
-    private ProvisioningConfiguration mConfiguration;
-    private IpReachabilityMonitor mIpReachabilityMonitor;
-    private DhcpClient mDhcpClient;
-    private DhcpResults mDhcpResults;
-    private String mTcpBufferSizes;
-    private ProxyInfo mHttpProxy;
-    private ApfFilter mApfFilter;
-    private boolean mMulticastFiltering;
-    private long mStartTimeMillis;
-
     public IpManager(Context context, String ifName, Callback callback) {
         this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
                 ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)),
                 NetdService.getInstance());
     }
 
-    /**
-     * An expanded constructor, useful for dependency injection.
-     * TODO: migrate all test users to mock IpManager directly and remove this ctor.
-     */
     public IpManager(Context context, String ifName, Callback callback,
             INetworkManagementService nwService) {
         this(context, ifName, callback, nwService, NetdService.getInstance());
     }
 
     @VisibleForTesting
-    IpManager(Context context, String ifName, Callback callback,
+    public IpManager(Context context, String ifName, Callback callback,
             INetworkManagementService nwService, INetd netd) {
-        super(IpManager.class.getSimpleName() + "." + ifName);
-        mTag = getName();
-
-        mContext = context;
-        mInterfaceName = ifName;
-        mClatInterfaceName = CLAT_PREFIX + ifName;
-        mCallback = new LoggingCallbackWrapper(callback);
-        mNwService = nwService;
-
-        mLog = new SharedLog(MAX_LOG_RECORDS, mTag);
-        mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
-        mMsgStateLogger = new MessageHandlingLogger();
-
-        mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog);
-
-        mNetlinkTracker = new NetlinkTracker(
-                mInterfaceName,
-                new NetlinkTracker.Callback() {
-                    @Override
-                    public void update() {
-                        sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
-                    }
-                }) {
-            @Override
-            public void interfaceAdded(String iface) {
-                super.interfaceAdded(iface);
-                if (mClatInterfaceName.equals(iface)) {
-                    mCallback.setNeighborDiscoveryOffload(false);
-                } else if (!mInterfaceName.equals(iface)) {
-                    return;
-                }
-
-                final String msg = "interfaceAdded(" + iface +")";
-                logMsg(msg);
-            }
-
-            @Override
-            public void interfaceRemoved(String iface) {
-                super.interfaceRemoved(iface);
-                // TODO: Also observe mInterfaceName going down and take some
-                // kind of appropriate action.
-                if (mClatInterfaceName.equals(iface)) {
-                    // TODO: consider sending a message to the IpManager main
-                    // StateMachine thread, in case "NDO enabled" state becomes
-                    // tied to more things that 464xlat operation.
-                    mCallback.setNeighborDiscoveryOffload(true);
-                } else if (!mInterfaceName.equals(iface)) {
-                    return;
-                }
-
-                final String msg = "interfaceRemoved(" + iface +")";
-                logMsg(msg);
-            }
-
-            private void logMsg(String msg) {
-                Log.d(mTag, msg);
-                getHandler().post(() -> { mLog.log("OBSERVED " + msg); });
-            }
-        };
-
-        mLinkProperties = new LinkProperties();
-        mLinkProperties.setInterfaceName(mInterfaceName);
-
-        mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(),
-                () -> { mLog.log("OBSERVED AvoidBadWifi changed"); });
-
-        mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
-                mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
-        mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
-                mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
-
-        // Anything the StateMachine may access must have been instantiated
-        // before this point.
-        configureAndStartStateMachine();
-
-        // Anything that may send messages to the StateMachine must only be
-        // configured to do so after the StateMachine has started (above).
-        startStateMachineUpdaters();
-    }
-
-    private void configureAndStartStateMachine() {
-        addState(mStoppedState);
-        addState(mStartedState);
-            addState(mRunningState, mStartedState);
-        addState(mStoppingState);
-
-        setInitialState(mStoppedState);
-
-        super.start();
-    }
-
-    private void startStateMachineUpdaters() {
-        try {
-            mNwService.registerObserver(mNetlinkTracker);
-        } catch (RemoteException e) {
-            logError("Couldn't register NetlinkTracker: %s", e);
-        }
-
-        mMultinetworkPolicyTracker.start();
-    }
-
-    private void stopStateMachineUpdaters() {
-        try {
-            mNwService.unregisterObserver(mNetlinkTracker);
-        } catch (RemoteException e) {
-            logError("Couldn't unregister NetlinkTracker: %s", e);
-        }
-
-        mMultinetworkPolicyTracker.shutdown();
-    }
-
-    @Override
-    protected void onQuitting() {
-        mCallback.onQuit();
-    }
-
-    // Shut down this IpManager instance altogether.
-    public void shutdown() {
-        stop();
-        sendMessage(CMD_TERMINATE_AFTER_STOP);
-    }
-
-    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
-        return new ProvisioningConfiguration.Builder();
+        super(context, ifName, callback, nwService, netd);
     }
 
     public void startProvisioning(ProvisioningConfiguration req) {
-        if (!req.isValid()) {
-            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
-            return;
-        }
-
-        getNetworkInterface();
-
-        mCallback.setNeighborDiscoveryOffload(true);
-        sendMessage(CMD_START, new ProvisioningConfiguration(req));
-    }
-
-    // TODO: Delete this.
-    public void startProvisioning(StaticIpConfiguration staticIpConfig) {
-        startProvisioning(buildProvisioningConfiguration()
-                .withStaticConfiguration(staticIpConfig)
-                .build());
-    }
-
-    public void startProvisioning() {
-        startProvisioning(new ProvisioningConfiguration());
-    }
-
-    public void stop() {
-        sendMessage(CMD_STOP);
-    }
-
-    public void confirmConfiguration() {
-        sendMessage(CMD_CONFIRM);
-    }
-
-    public void completedPreDhcpAction() {
-        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
-    }
-
-    /**
-     * Set the TCP buffer sizes to use.
-     *
-     * This may be called, repeatedly, at any time before or after a call to
-     * #startProvisioning(). The setting is cleared upon calling #stop().
-     */
-    public void setTcpBufferSizes(String tcpBufferSizes) {
-        sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
-    }
-
-    /**
-     * Set the HTTP Proxy configuration to use.
-     *
-     * This may be called, repeatedly, at any time before or after a call to
-     * #startProvisioning(). The setting is cleared upon calling #stop().
-     */
-    public void setHttpProxy(ProxyInfo proxyInfo) {
-        sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
-    }
-
-    /**
-     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
-     * if not, Callback.setFallbackMulticastFilter() is called.
-     */
-    public void setMulticastFilter(boolean enabled) {
-        sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
-            // Execute confirmConfiguration() and take no further action.
-            confirmConfiguration();
-            return;
-        }
-
-        // Thread-unsafe access to mApfFilter but just used for debugging.
-        final ApfFilter apfFilter = mApfFilter;
-        final ProvisioningConfiguration provisioningConfig = mConfiguration;
-        final ApfCapabilities apfCapabilities = (provisioningConfig != null)
-                ? provisioningConfig.mApfCapabilities : null;
-
-        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        pw.println(mTag + " APF dump:");
-        pw.increaseIndent();
-        if (apfFilter != null) {
-            apfFilter.dump(pw);
-        } else {
-            pw.print("No active ApfFilter; ");
-            if (provisioningConfig == null) {
-                pw.println("IpManager not yet started.");
-            } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
-                pw.println("Hardware does not support APF.");
-            } else {
-                pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
-            }
-        }
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println(mTag + " current ProvisioningConfiguration:");
-        pw.increaseIndent();
-        pw.println(Objects.toString(provisioningConfig, "N/A"));
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println(mTag + " StateMachine dump:");
-        pw.increaseIndent();
-        mLog.dump(fd, pw, args);
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println(mTag + " connectivity packet log:");
-        pw.println();
-        pw.println("Debug with python and scapy via:");
-        pw.println("shell$ python");
-        pw.println(">>> from scapy import all as scapy");
-        pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
-        pw.println();
-
-        pw.increaseIndent();
-        mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
-        pw.decreaseIndent();
-    }
-
-
-    /**
-     * Internals.
-     */
-
-    @Override
-    protected String getWhatToString(int what) {
-        return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
-    }
-
-    @Override
-    protected String getLogRecString(Message msg) {
-        final String logLine = String.format(
-                "%s/%d %d %d %s [%s]",
-                mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
-                msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
-
-        final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
-        mLog.log(richerLogLine);
-        if (DBG) {
-            Log.d(mTag, richerLogLine);
-        }
-
-        mMsgStateLogger.reset();
-        return logLine;
-    }
-
-    @Override
-    protected boolean recordLogRec(Message msg) {
-        // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
-        // and we already log any LinkProperties change that results in an
-        // invocation of IpManager.Callback#onLinkPropertiesChange().
-        final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
-        if (!shouldLog) {
-            mMsgStateLogger.reset();
-        }
-        return shouldLog;
-    }
-
-    private void logError(String fmt, Object... args) {
-        final String msg = "ERROR " + String.format(fmt, args);
-        Log.e(mTag, msg);
-        mLog.log(msg);
-    }
-
-    private void getNetworkInterface() {
-        try {
-            mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
-        } catch (SocketException | NullPointerException e) {
-            // TODO: throw new IllegalStateException.
-            logError("Failed to get interface object: %s", e);
-        }
-    }
-
-    // This needs to be called with care to ensure that our LinkProperties
-    // are in sync with the actual LinkProperties of the interface. For example,
-    // we should only call this if we know for sure that there are no IP addresses
-    // assigned to the interface, etc.
-    private void resetLinkProperties() {
-        mNetlinkTracker.clearLinkProperties();
-        mConfiguration = null;
-        mDhcpResults = null;
-        mTcpBufferSizes = "";
-        mHttpProxy = null;
-
-        mLinkProperties = new LinkProperties();
-        mLinkProperties.setInterfaceName(mInterfaceName);
-    }
-
-    private void recordMetric(final int type) {
-        if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
-        final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
-        mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
-    }
-
-    // For now: use WifiStateMachine's historical notion of provisioned.
-    @VisibleForTesting
-    static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
-        // For historical reasons, we should connect even if all we have is
-        // an IPv4 address and nothing else.
-        if (lp.hasIPv4Address() || lp.isProvisioned()) {
-            return true;
-        }
-        if (config == null) {
-            return false;
-        }
-
-        // When an InitialConfiguration is specified, ignore any difference with previous
-        // properties and instead check if properties observed match the desired properties.
-        return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
-    }
-
-    // TODO: Investigate folding all this into the existing static function
-    // LinkProperties.compareProvisioning() or some other single function that
-    // takes two LinkProperties objects and returns a ProvisioningChange
-    // object that is a correct and complete assessment of what changed, taking
-    // account of the asymmetries described in the comments in this function.
-    // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
-    private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
-        ProvisioningChange delta;
-        InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
-        final boolean wasProvisioned = isProvisioned(oldLp, config);
-        final boolean isProvisioned = isProvisioned(newLp, config);
-
-        if (!wasProvisioned && isProvisioned) {
-            delta = ProvisioningChange.GAINED_PROVISIONING;
-        } else if (wasProvisioned && isProvisioned) {
-            delta = ProvisioningChange.STILL_PROVISIONED;
-        } else if (!wasProvisioned && !isProvisioned) {
-            delta = ProvisioningChange.STILL_NOT_PROVISIONED;
-        } else {
-            // (wasProvisioned && !isProvisioned)
-            //
-            // Note that this is true even if we lose a configuration element
-            // (e.g., a default gateway) that would not be required to advance
-            // into provisioned state. This is intended: if we have a default
-            // router and we lose it, that's a sure sign of a problem, but if
-            // we connect to a network with no IPv4 DNS servers, we consider
-            // that to be a network without DNS servers and connect anyway.
-            //
-            // See the comment below.
-            delta = ProvisioningChange.LOST_PROVISIONING;
-        }
-
-        final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
-        final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
-        final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
-
-        // If bad wifi avoidance is disabled, then ignore IPv6 loss of
-        // provisioning. Otherwise, when a hotspot that loses Internet
-        // access sends out a 0-lifetime RA to its clients, the clients
-        // will disconnect and then reconnect, avoiding the bad hotspot,
-        // instead of getting stuck on the bad hotspot. http://b/31827713 .
-        //
-        // This is incorrect because if the hotspot then regains Internet
-        // access with a different prefix, TCP connections on the
-        // deprecated addresses will remain stuck.
-        //
-        // Note that we can still be disconnected by IpReachabilityMonitor
-        // if the IPv6 default gateway (but not the IPv6 DNS servers; see
-        // accompanying code in IpReachabilityMonitor) is unreachable.
-        final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi();
-
-        // Additionally:
-        //
-        // Partial configurations (e.g., only an IPv4 address with no DNS
-        // servers and no default route) are accepted as long as DHCPv4
-        // succeeds. On such a network, isProvisioned() will always return
-        // false, because the configuration is not complete, but we want to
-        // connect anyway. It might be a disconnected network such as a
-        // Chromecast or a wireless printer, for example.
-        //
-        // Because on such a network isProvisioned() will always return false,
-        // delta will never be LOST_PROVISIONING. So check for loss of
-        // provisioning here too.
-        if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
-            delta = ProvisioningChange.LOST_PROVISIONING;
-        }
-
-        // Additionally:
-        //
-        // If the previous link properties had a global IPv6 address and an
-        // IPv6 default route then also consider the loss of that default route
-        // to be a loss of provisioning. See b/27962810.
-        if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
-            delta = ProvisioningChange.LOST_PROVISIONING;
-        }
-
-        return delta;
-    }
-
-    private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
-        switch (delta) {
-            case GAINED_PROVISIONING:
-                if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); }
-                recordMetric(IpManagerEvent.PROVISIONING_OK);
-                mCallback.onProvisioningSuccess(newLp);
-                break;
-
-            case LOST_PROVISIONING:
-                if (DBG) { Log.d(mTag, "onProvisioningFailure()"); }
-                recordMetric(IpManagerEvent.PROVISIONING_FAIL);
-                mCallback.onProvisioningFailure(newLp);
-                break;
-
-            default:
-                if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
-                mCallback.onLinkPropertiesChange(newLp);
-                break;
-        }
-    }
-
-    // Updates all IpManager-related state concerned with LinkProperties.
-    // Returns a ProvisioningChange for possibly notifying other interested
-    // parties that are not fronted by IpManager.
-    private ProvisioningChange setLinkProperties(LinkProperties newLp) {
-        if (mApfFilter != null) {
-            mApfFilter.setLinkProperties(newLp);
-        }
-        if (mIpReachabilityMonitor != null) {
-            mIpReachabilityMonitor.updateLinkProperties(newLp);
-        }
-
-        ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
-        mLinkProperties = new LinkProperties(newLp);
-
-        if (delta == ProvisioningChange.GAINED_PROVISIONING) {
-            // TODO: Add a proper ProvisionedState and cancel the alarm in
-            // its enter() method.
-            mProvisioningTimeoutAlarm.cancel();
-        }
-
-        return delta;
-    }
-
-    private LinkProperties assembleLinkProperties() {
-        // [1] Create a new LinkProperties object to populate.
-        LinkProperties newLp = new LinkProperties();
-        newLp.setInterfaceName(mInterfaceName);
-
-        // [2] Pull in data from netlink:
-        //         - IPv4 addresses
-        //         - IPv6 addresses
-        //         - IPv6 routes
-        //         - IPv6 DNS servers
-        //
-        // N.B.: this is fundamentally race-prone and should be fixed by
-        // changing NetlinkTracker from a hybrid edge/level model to an
-        // edge-only model, or by giving IpManager its own netlink socket(s)
-        // so as to track all required information directly.
-        LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
-        newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
-        for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
-            newLp.addRoute(route);
-        }
-        addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
-
-        // [3] Add in data from DHCPv4, if available.
-        //
-        // mDhcpResults is never shared with any other owner so we don't have
-        // to worry about concurrent modification.
-        if (mDhcpResults != null) {
-            for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
-                newLp.addRoute(route);
-            }
-            addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
-            newLp.setDomains(mDhcpResults.domains);
-
-            if (mDhcpResults.mtu != 0) {
-                newLp.setMtu(mDhcpResults.mtu);
-            }
-        }
-
-        // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
-        if (!TextUtils.isEmpty(mTcpBufferSizes)) {
-            newLp.setTcpBufferSizes(mTcpBufferSizes);
-        }
-        if (mHttpProxy != null) {
-            newLp.setHttpProxy(mHttpProxy);
-        }
-
-        // [5] Add data from InitialConfiguration
-        if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
-            InitialConfiguration config = mConfiguration.mInitialConfig;
-            // Add InitialConfiguration routes and dns server addresses once all addresses
-            // specified in the InitialConfiguration have been observed with Netlink.
-            if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
-                for (IpPrefix prefix : config.directlyConnectedRoutes) {
-                    newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
-                }
-            }
-            addAllReachableDnsServers(newLp, config.dnsServers);
-        }
-        final LinkProperties oldLp = mLinkProperties;
-        if (DBG) {
-            Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
-                    netlinkLinkProperties, newLp, oldLp));
-        }
-
-        // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
-        // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
-        return newLp;
-    }
-
-    private static void addAllReachableDnsServers(
-            LinkProperties lp, Iterable<InetAddress> dnses) {
-        // TODO: Investigate deleting this reachability check.  We should be
-        // able to pass everything down to netd and let netd do evaluation
-        // and RFC6724-style sorting.
-        for (InetAddress dns : dnses) {
-            if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
-                lp.addDnsServer(dns);
-            }
-        }
-    }
-
-    // Returns false if we have lost provisioning, true otherwise.
-    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
-        final LinkProperties newLp = assembleLinkProperties();
-        if (Objects.equals(newLp, mLinkProperties)) {
-            return true;
-        }
-        final ProvisioningChange delta = setLinkProperties(newLp);
-        if (sendCallbacks) {
-            dispatchCallback(delta, newLp);
-        }
-        return (delta != ProvisioningChange.LOST_PROVISIONING);
-    }
-
-    private void handleIPv4Success(DhcpResults dhcpResults) {
-        mDhcpResults = new DhcpResults(dhcpResults);
-        final LinkProperties newLp = assembleLinkProperties();
-        final ProvisioningChange delta = setLinkProperties(newLp);
-
-        if (DBG) {
-            Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
-        }
-        mCallback.onNewDhcpResults(dhcpResults);
-        dispatchCallback(delta, newLp);
-    }
-
-    private void handleIPv4Failure() {
-        // TODO: Investigate deleting this clearIPv4Address() call.
-        //
-        // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
-        // that could trigger a call to this function. If we missed handling
-        // that message in StartedState for some reason we would still clear
-        // any addresses upon entry to StoppedState.
-        mInterfaceCtrl.clearIPv4Address();
-        mDhcpResults = null;
-        if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
-        mCallback.onNewDhcpResults(null);
-
-        handleProvisioningFailure();
-    }
-
-    private void handleProvisioningFailure() {
-        final LinkProperties newLp = assembleLinkProperties();
-        ProvisioningChange delta = setLinkProperties(newLp);
-        // If we've gotten here and we're still not provisioned treat that as
-        // a total loss of provisioning.
-        //
-        // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
-        // there was no usable IPv6 obtained before a non-zero provisioning
-        // timeout expired.
-        //
-        // Regardless: GAME OVER.
-        if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
-            delta = ProvisioningChange.LOST_PROVISIONING;
-        }
-
-        dispatchCallback(delta, newLp);
-        if (delta == ProvisioningChange.LOST_PROVISIONING) {
-            transitionTo(mStoppingState);
-        }
-    }
-
-    private void doImmediateProvisioningFailure(int failureType) {
-        logError("onProvisioningFailure(): %s", failureType);
-        recordMetric(failureType);
-        mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
-    }
-
-    private boolean startIPv4() {
-        // If we have a StaticIpConfiguration attempt to apply it and
-        // handle the result accordingly.
-        if (mConfiguration.mStaticIpConfig != null) {
-            if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
-                handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
-            } else {
-                return false;
-            }
-        } else {
-            // Start DHCPv4.
-            mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpManager.this, mInterfaceName);
-            mDhcpClient.registerForPreDhcpNotification();
-            mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
-        }
-
-        return true;
-    }
-
-    private boolean startIPv6() {
-        return mInterfaceCtrl.setIPv6PrivacyExtensions(true) &&
-               mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) &&
-               mInterfaceCtrl.enableIPv6();
-    }
-
-    private boolean applyInitialConfig(InitialConfiguration config) {
-        // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
-        for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
-            if (!mInterfaceCtrl.addAddress(addr)) return false;
-        }
-
-        return true;
-    }
-
-    private boolean startIpReachabilityMonitor() {
-        try {
-            mIpReachabilityMonitor = new IpReachabilityMonitor(
-                    mContext,
-                    mInterfaceName,
-                    mLog,
-                    new IpReachabilityMonitor.Callback() {
-                        @Override
-                        public void notifyLost(InetAddress ip, String logMsg) {
-                            mCallback.onReachabilityLost(logMsg);
-                        }
-                    },
-                    mMultinetworkPolicyTracker);
-        } catch (IllegalArgumentException iae) {
-            // Failed to start IpReachabilityMonitor. Log it and call
-            // onProvisioningFailure() immediately.
-            //
-            // See http://b/31038971.
-            logError("IpReachabilityMonitor failure: %s", iae);
-            mIpReachabilityMonitor = null;
-        }
-
-        return (mIpReachabilityMonitor != null);
-    }
-
-    private void stopAllIP() {
-        // We don't need to worry about routes, just addresses, because:
-        //     - disableIpv6() will clear autoconf IPv6 routes as well, and
-        //     - we don't get IPv4 routes from netlink
-        // so we neither react to nor need to wait for changes in either.
-
-        mInterfaceCtrl.disableIPv6();
-        mInterfaceCtrl.clearAllAddresses();
-    }
-
-    class StoppedState extends State {
-        @Override
-        public void enter() {
-            stopAllIP();
-
-            resetLinkProperties();
-            if (mStartTimeMillis > 0) {
-                recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
-                mStartTimeMillis = 0;
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_TERMINATE_AFTER_STOP:
-                    stopStateMachineUpdaters();
-                    quit();
-                    break;
-
-                case CMD_STOP:
-                    break;
-
-                case CMD_START:
-                    mConfiguration = (ProvisioningConfiguration) msg.obj;
-                    transitionTo(mStartedState);
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_TCP_BUFFER_SIZES:
-                    mTcpBufferSizes = (String) msg.obj;
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_HTTP_PROXY:
-                    mHttpProxy = (ProxyInfo) msg.obj;
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_SET_MULTICAST_FILTER:
-                    mMulticastFiltering = (boolean) msg.obj;
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    // Everything is already stopped.
-                    logError("Unexpected CMD_ON_QUIT (already stopped).");
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    class StoppingState extends State {
-        @Override
-        public void enter() {
-            if (mDhcpClient == null) {
-                // There's no DHCPv4 for which to wait; proceed to stopped.
-                transitionTo(mStoppedState);
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_STOP:
-                    break;
-
-                case DhcpClient.CMD_CLEAR_LINKADDRESS:
-                    mInterfaceCtrl.clearIPv4Address();
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    mDhcpClient = null;
-                    transitionTo(mStoppedState);
-                    break;
-
-                default:
-                    deferMessage(msg);
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    class StartedState extends State {
-        @Override
-        public void enter() {
-            mStartTimeMillis = SystemClock.elapsedRealtime();
-
-            if (mConfiguration.mProvisioningTimeoutMs > 0) {
-                final long alarmTime = SystemClock.elapsedRealtime() +
-                        mConfiguration.mProvisioningTimeoutMs;
-                mProvisioningTimeoutAlarm.schedule(alarmTime);
-            }
-
-            if (readyToProceed()) {
-                transitionTo(mRunningState);
-            } else {
-                // Clear all IPv4 and IPv6 before proceeding to RunningState.
-                // Clean up any leftover state from an abnormal exit from
-                // tethering or during an IpManager restart.
-                stopAllIP();
-            }
-        }
-
-        @Override
-        public void exit() {
-            mProvisioningTimeoutAlarm.cancel();
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_STOP:
-                    transitionTo(mStoppingState);
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    if (readyToProceed()) {
-                        transitionTo(mRunningState);
-                    }
-                    break;
-
-                case EVENT_PROVISIONING_TIMEOUT:
-                    handleProvisioningFailure();
-                    break;
-
-                default:
-                    // It's safe to process messages out of order because the
-                    // only message that can both
-                    //     a) be received at this time and
-                    //     b) affect provisioning state
-                    // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
-                    deferMessage(msg);
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-
-        boolean readyToProceed() {
-            return (!mLinkProperties.hasIPv4Address() &&
-                    !mLinkProperties.hasGlobalIPv6Address());
-        }
-    }
-
-    class RunningState extends State {
-        private ConnectivityPacketTracker mPacketTracker;
-        private boolean mDhcpActionInFlight;
-
-        @Override
-        public void enter() {
-            // Get the Configuration for ApfFilter from Context
-            final boolean filter802_3Frames =
-                    mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
-
-            final int[] ethTypeBlackList = mContext.getResources().getIntArray(
-                    R.array.config_apfEthTypeBlackList);
-
-            mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
-                    mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
-            // TODO: investigate the effects of any multicast filtering racing/interfering with the
-            // rest of this IP configuration startup.
-            if (mApfFilter == null) {
-                mCallback.setFallbackMulticastFilter(mMulticastFiltering);
-            }
-
-            mPacketTracker = createPacketTracker();
-            if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
-
-            if (mConfiguration.mEnableIPv6 && !startIPv6()) {
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
-                transitionTo(mStoppingState);
-                return;
-            }
-
-            if (mConfiguration.mEnableIPv4 && !startIPv4()) {
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
-                transitionTo(mStoppingState);
-                return;
-            }
-
-            final InitialConfiguration config = mConfiguration.mInitialConfig;
-            if ((config != null) && !applyInitialConfig(config)) {
-                // TODO introduce a new IpManagerEvent constant to distinguish this error case.
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
-                transitionTo(mStoppingState);
-                return;
-            }
-
-            if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
-                doImmediateProvisioningFailure(
-                        IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
-                transitionTo(mStoppingState);
-                return;
-            }
-        }
-
-        @Override
-        public void exit() {
-            stopDhcpAction();
-
-            if (mIpReachabilityMonitor != null) {
-                mIpReachabilityMonitor.stop();
-                mIpReachabilityMonitor = null;
-            }
-
-            if (mDhcpClient != null) {
-                mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
-                mDhcpClient.doQuit();
-            }
-
-            if (mPacketTracker != null) {
-                mPacketTracker.stop();
-                mPacketTracker = null;
-            }
-
-            if (mApfFilter != null) {
-                mApfFilter.shutdown();
-                mApfFilter = null;
-            }
-
-            resetLinkProperties();
-        }
-
-        private ConnectivityPacketTracker createPacketTracker() {
-            try {
-                return new ConnectivityPacketTracker(
-                        getHandler(), mNetworkInterface, mConnectivityPacketLog);
-            } catch (IllegalArgumentException e) {
-                return null;
-            }
-        }
-
-        private void ensureDhcpAction() {
-            if (!mDhcpActionInFlight) {
-                mCallback.onPreDhcpAction();
-                mDhcpActionInFlight = true;
-                final long alarmTime = SystemClock.elapsedRealtime() +
-                        mConfiguration.mRequestedPreDhcpActionMs;
-                mDhcpActionTimeoutAlarm.schedule(alarmTime);
-            }
-        }
-
-        private void stopDhcpAction() {
-            mDhcpActionTimeoutAlarm.cancel();
-            if (mDhcpActionInFlight) {
-                mCallback.onPostDhcpAction();
-                mDhcpActionInFlight = false;
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_STOP:
-                    transitionTo(mStoppingState);
-                    break;
-
-                case CMD_START:
-                    logError("ALERT: START received in StartedState. Please fix caller.");
-                    break;
-
-                case CMD_CONFIRM:
-                    // TODO: Possibly introduce a second type of confirmation
-                    // that both probes (a) on-link neighbors and (b) does
-                    // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
-                    // roams.
-                    if (mIpReachabilityMonitor != null) {
-                        mIpReachabilityMonitor.probeAll();
-                    }
-                    break;
-
-                case EVENT_PRE_DHCP_ACTION_COMPLETE:
-                    // It's possible to reach here if, for example, someone
-                    // calls completedPreDhcpAction() after provisioning with
-                    // a static IP configuration.
-                    if (mDhcpClient != null) {
-                        mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
-                    }
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
-                        transitionTo(mStoppingState);
-                    }
-                    break;
-
-                case CMD_UPDATE_TCP_BUFFER_SIZES:
-                    mTcpBufferSizes = (String) msg.obj;
-                    // This cannot possibly change provisioning state.
-                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_HTTP_PROXY:
-                    mHttpProxy = (ProxyInfo) msg.obj;
-                    // This cannot possibly change provisioning state.
-                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
-                    break;
-
-                case CMD_SET_MULTICAST_FILTER: {
-                    mMulticastFiltering = (boolean) msg.obj;
-                    if (mApfFilter != null) {
-                        mApfFilter.setMulticastFilter(mMulticastFiltering);
-                    } else {
-                        mCallback.setFallbackMulticastFilter(mMulticastFiltering);
-                    }
-                    break;
-                }
-
-                case EVENT_DHCPACTION_TIMEOUT:
-                    stopDhcpAction();
-                    break;
-
-                case DhcpClient.CMD_PRE_DHCP_ACTION:
-                    if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
-                        ensureDhcpAction();
-                    } else {
-                        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
-                    }
-                    break;
-
-                case DhcpClient.CMD_CLEAR_LINKADDRESS:
-                    mInterfaceCtrl.clearIPv4Address();
-                    break;
-
-                case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
-                    final LinkAddress ipAddress = (LinkAddress) msg.obj;
-                    if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
-                        mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
-                    } else {
-                        logError("Failed to set IPv4 address.");
-                        dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
-                                new LinkProperties(mLinkProperties));
-                        transitionTo(mStoppingState);
-                    }
-                    break;
-                }
-
-                // This message is only received when:
-                //
-                //     a) initial address acquisition succeeds,
-                //     b) renew succeeds or is NAK'd,
-                //     c) rebind succeeds or is NAK'd, or
-                //     c) the lease expires,
-                //
-                // but never when initial address acquisition fails. The latter
-                // condition is now governed by the provisioning timeout.
-                case DhcpClient.CMD_POST_DHCP_ACTION:
-                    stopDhcpAction();
-
-                    switch (msg.arg1) {
-                        case DhcpClient.DHCP_SUCCESS:
-                            handleIPv4Success((DhcpResults) msg.obj);
-                            break;
-                        case DhcpClient.DHCP_FAILURE:
-                            handleIPv4Failure();
-                            break;
-                        default:
-                            logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
-                    }
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    // DHCPv4 quit early for some reason.
-                    logError("Unexpected CMD_ON_QUIT.");
-                    mDhcpClient = null;
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    private static class MessageHandlingLogger {
-        public String processedInState;
-        public String receivedInState;
-
-        public void reset() {
-            processedInState = null;
-            receivedInState = null;
-        }
-
-        public void handled(State processedIn, IState receivedIn) {
-            processedInState = processedIn.getClass().getSimpleName();
-            receivedInState = receivedIn.getName();
-        }
-
-        public String toString() {
-            return String.format("rcvd_in=%s, proc_in=%s",
-                                 receivedInState, processedInState);
-        }
-    }
-
-    // TODO: extract out into CollectionUtils.
-    static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
-        for (T t : coll) {
-            if (fn.test(t)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
-        return !any(coll, not(fn));
-    }
-
-    static <T> Predicate<T> not(Predicate<T> fn) {
-        return (t) -> !fn.test(t);
-    }
-
-    static <T> String join(String delimiter, Collection<T> coll) {
-        return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
-    }
-
-    static <T> T find(Iterable<T> coll, Predicate<T> fn) {
-        for (T t: coll) {
-            if (fn.test(t)) {
-              return t;
-            }
-        }
-        return null;
-    }
-
-    static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
-        return coll.stream().filter(fn).collect(Collectors.toList());
+        super.startProvisioning((IpClient.ProvisioningConfiguration) req);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
index 5d1d07b..daaad7a8 100644
--- a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
@@ -98,14 +98,14 @@
     public void testWrapPreferVendor() throws Exception {
         initForInstances(VENDOR, HEALTHD);
         mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
-        verify(mCallback).onRegistration(same(mMockedHal), eq(VENDOR));
+        verify(mCallback).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
     }
 
     @SmallTest
     public void testUseHealthd() throws Exception {
         initForInstances(HEALTHD);
         mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
-        verify(mCallback).onRegistration(same(mMockedHal), eq(HEALTHD));
+        verify(mCallback).onRegistration(same(null), same(mMockedHal), eq(HEALTHD));
     }
 
     @SmallTest
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
index 6b93d0e..60a55fa 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -77,7 +77,7 @@
     @Test
     public void testRestoringInvalidTask() throws Exception {
         TaskRecord task = mSupervisor.anyTaskForIdLocked(0 /*taskId*/,
-                MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, null);
+                MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */);
         assertNull(task);
     }
 
@@ -116,9 +116,9 @@
         mSupervisor.moveActivityToPinnedStackLocked(secondActivity, sourceBounds,
                 0f /*aspectRatio*/, false, "secondMove");
 
-        // Need to get pinned stack again as a new instance might have been created.
+        // Need to get stacks again as a new instance might have been created.
         pinnedStack = display.getPinnedStack();
-
+        mFullscreenStack = display.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
         // Ensure stacks have swapped tasks.
         ensureStackPlacement(pinnedStack, secondTask);
         ensureStackPlacement(mFullscreenStack, firstTask);
diff --git a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
index f9d7f9d4..4c1d3e9 100644
--- a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
@@ -189,6 +189,8 @@
 
         // THEN lock task mode should be started
         verifyLockTaskStarted(STATUS_BAR_MASK_PINNED);
+        // THEN screen pinning toast should be shown
+        verify(mLockTaskNotify).showPinningStartToast();
     }
 
     @Test
@@ -255,8 +257,6 @@
         // WHEN system calls stopLockTaskMode
         mLockTaskController.stopLockTaskMode(true, SYSTEM_UID);
 
-        // THEN a lock tash toast should be shown
-        verify(mLockTaskNotify).showToast(LOCK_TASK_MODE_LOCKED);
         // THEN lock task mode should still be active
         assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
     }
@@ -302,6 +302,8 @@
         verifyLockTaskStopped(times(1));
         // THEN the keyguard should be shown
         verify(mLockPatternUtils).requireCredentialEntry(UserHandle.USER_ALL);
+        // THEN screen pinning toast should be shown
+        verify(mLockTaskNotify).showPinningExitToast();
     }
 
     @Test
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 56a3fb0..b8e8946 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -89,6 +89,9 @@
     public void setUp() throws Exception {
         if (!sOneTimeSetupDone) {
             sOneTimeSetupDone = true;
+
+            // Allows to mock package local classes and methods
+            System.setProperty("dexmaker.share_classloader", "true");
             MockitoAnnotations.initMocks(this);
         }
 
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 9e02399..c8b4776 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -19,7 +19,6 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
 import android.util.Log;
 import android.content.res.Resources;
 
@@ -429,6 +428,15 @@
     }
 
     /**
+     * Fix {@link #isGsm} based on the signal strength data.
+     *
+     * @hide
+     */
+    public void fixType() {
+        isGsm = getCdmaRelatedSignalStrength() == SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+    }
+
+    /**
      * @param true - Gsm, Lte phones
      *        false - Cdma phones
      *
@@ -541,30 +549,7 @@
      *     while 4 represents a very strong signal strength.
      */
     public int getLevel() {
-        int level = 0;
-
-        if (isGsm) {
-            level = getLteLevel();
-            if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                level = getTdScdmaLevel();
-                if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                    level = getGsmLevel();
-                }
-            }
-        } else {
-            int cdmaLevel = getCdmaLevel();
-            int evdoLevel = getEvdoLevel();
-            if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                /* We don't know evdo, use cdma */
-                level = cdmaLevel;
-            } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                /* We don't know cdma, use evdo */
-                level = evdoLevel;
-            } else {
-                /* We know both, use the lowest level */
-                level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
-            }
-        }
+        int level = isGsm ? getGsmRelatedSignalStrength() : getCdmaRelatedSignalStrength();
         if (DBG) log("getLevel=" + level);
         return level;
     }
@@ -1049,6 +1034,36 @@
                 + " " + (isGsm ? "gsm|lte" : "cdma"));
     }
 
+    /** Returns the signal strength related to GSM. */
+    private int getGsmRelatedSignalStrength() {
+        int level = getLteLevel();
+        if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+            level = getTdScdmaLevel();
+            if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+                level = getGsmLevel();
+            }
+        }
+        return level;
+    }
+
+    /** Returns the signal strength related to CDMA. */
+    private int getCdmaRelatedSignalStrength() {
+        int level;
+        int cdmaLevel = getCdmaLevel();
+        int evdoLevel = getEvdoLevel();
+        if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+            /* We don't know evdo, use cdma */
+            level = cdmaLevel;
+        } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+            /* We don't know cdma, use evdo */
+            level = evdoLevel;
+        } else {
+            /* We know both, use the lowest level */
+            level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
+        }
+        return level;
+    }
+
     /**
      * Set SignalStrength based on intent notifier map
      *
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index c3b2c48..9ccdd56 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -118,14 +118,8 @@
         }
 
         final int uid = Binder.getCallingUid();
-        callback.asBinder().linkToDeath(new DeathRecipient() {
-            @Override
-            public void binderDied() {
-                onAppCallbackDied(uid, subscriptionId);
-            }
-        }, 0);
 
-        return initialize(subscriptionId, new MbmsDownloadSessionCallback() {
+        int result = initialize(subscriptionId, new MbmsDownloadSessionCallback() {
             @Override
             public void onError(int errorCode, String message) {
                 try {
@@ -153,6 +147,17 @@
                 }
             }
         });
+
+        if (result == MbmsErrors.SUCCESS) {
+            callback.asBinder().linkToDeath(new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }, 0);
+        }
+
+        return result;
     }
 
     /**
@@ -251,17 +256,6 @@
             throw new NullPointerException("Callback must not be null");
         }
 
-        DeathRecipient deathRecipient = new DeathRecipient() {
-            @Override
-            public void binderDied() {
-                onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
-                mDownloadCallbackBinderMap.remove(callback.asBinder());
-                mDownloadCallbackDeathRecipients.remove(callback.asBinder());
-            }
-        };
-        mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
-        callback.asBinder().linkToDeath(deathRecipient, 0);
-
         DownloadStateCallback exposedCallback = new FilteredDownloadStateCallback(callback, flags) {
             @Override
             protected void onRemoteException(RemoteException e) {
@@ -269,9 +263,23 @@
             }
         };
 
-        mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+        int result = registerStateCallback(downloadRequest, exposedCallback);
 
-        return registerStateCallback(downloadRequest, exposedCallback);
+        if (result == MbmsErrors.SUCCESS) {
+            DeathRecipient deathRecipient = new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+                    mDownloadCallbackBinderMap.remove(callback.asBinder());
+                    mDownloadCallbackDeathRecipients.remove(callback.asBinder());
+                }
+            };
+            mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
+            callback.asBinder().linkToDeath(deathRecipient, 0);
+            mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+        }
+
+        return result;
     }
 
     /**
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index 65b726d..a238153 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -70,14 +70,8 @@
         }
 
         final int uid = Binder.getCallingUid();
-        callback.asBinder().linkToDeath(new DeathRecipient() {
-            @Override
-            public void binderDied() {
-                onAppCallbackDied(uid, subscriptionId);
-            }
-        }, 0);
 
-        return initialize(new MbmsStreamingSessionCallback() {
+        int result = initialize(new MbmsStreamingSessionCallback() {
             @Override
             public void onError(final int errorCode, final String message) {
                 try {
@@ -105,6 +99,17 @@
                 }
             }
         }, subscriptionId);
+
+        if (result == MbmsErrors.SUCCESS) {
+            callback.asBinder().linkToDeath(new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }, 0);
+        }
+
+        return result;
     }
 
 
@@ -161,14 +166,8 @@
         }
 
         final int uid = Binder.getCallingUid();
-        callback.asBinder().linkToDeath(new DeathRecipient() {
-            @Override
-            public void binderDied() {
-                onAppCallbackDied(uid, subscriptionId);
-            }
-        }, 0);
 
-        return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
+        int result = startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
             @Override
             public void onError(final int errorCode, final String message) {
                 try {
@@ -215,6 +214,17 @@
                 }
             }
         });
+
+        if (result == MbmsErrors.SUCCESS) {
+            callback.asBinder().linkToDeath(new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }, 0);
+        }
+
+        return result;
     }
 
     /**
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
index 25bfa53..047be16 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
@@ -17,6 +17,7 @@
 package android.security.net.config;
 
 import android.app.Activity;
+import android.content.pm.ApplicationInfo;
 import android.os.Build;
 import android.test.ActivityUnitTestCase;
 import android.util.ArraySet;
@@ -227,7 +228,8 @@
     public void testConfigBuilderUsesParents() throws Exception {
         // Check that a builder with a parent uses the parent's values when non is set.
         NetworkSecurityConfig config = new NetworkSecurityConfig.Builder()
-                .setParent(NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.N, 1))
+                .setParent(NetworkSecurityConfig
+                        .getDefaultBuilder(TestUtils.makeApplicationInfo()))
                 .build();
         assert(!config.getTrustAnchors().isEmpty());
     }
@@ -268,11 +270,22 @@
             // Install the test CA.
             store.installCertificate(TEST_CA_CERT);
             NetworkSecurityConfig preNConfig =
-                    NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.M, 1).build();
+                    NetworkSecurityConfig
+                    .getDefaultBuilder(TestUtils.makeApplicationInfo(Build.VERSION_CODES.M))
+                    .build();
             NetworkSecurityConfig nConfig =
-                    NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.N, 1).build();
+                    NetworkSecurityConfig
+                    .getDefaultBuilder(TestUtils.makeApplicationInfo(Build.VERSION_CODES.N))
+                    .build();
+            ApplicationInfo privInfo = TestUtils.makeApplicationInfo(Build.VERSION_CODES.M);
+            privInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+            NetworkSecurityConfig privConfig =
+                    NetworkSecurityConfig
+                    .getDefaultBuilder(privInfo)
+                    .build();
             Set<TrustAnchor> preNAnchors = preNConfig.getTrustAnchors();
             Set<TrustAnchor> nAnchors = nConfig.getTrustAnchors();
+            Set<TrustAnchor> privAnchors = privConfig.getTrustAnchors();
             Set<X509Certificate> preNCerts = new HashSet<X509Certificate>();
             for (TrustAnchor anchor : preNAnchors) {
                 preNCerts.add(anchor.certificate);
@@ -281,8 +294,13 @@
             for (TrustAnchor anchor : nAnchors) {
                 nCerts.add(anchor.certificate);
             }
+            Set<X509Certificate> privCerts = new HashSet<X509Certificate>();
+            for (TrustAnchor anchor : privAnchors) {
+                privCerts.add(anchor.certificate);
+            }
             assertTrue(preNCerts.contains(TEST_CA_CERT));
             assertFalse(nCerts.contains(TEST_CA_CERT));
+            assertFalse(privCerts.contains(TEST_CA_CERT));
         } finally {
             // Delete the user added CA. We don't know the alias so just delete them all.
             for (String alias : store.aliases()) {
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
index f7590fd..9dec21b 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
@@ -16,6 +16,8 @@
 
 package android.security.net.config;
 
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
 import java.net.Socket;
 import java.net.URL;
 import javax.net.ssl.HttpsURLConnection;
@@ -77,4 +79,17 @@
         context.init(null, tmf.getTrustManagers(), null);
         return context;
     }
+
+    public static ApplicationInfo makeApplicationInfo() {
+        ApplicationInfo info = new ApplicationInfo();
+        info.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+        info.targetSandboxVersion = 1;
+        return info;
+    }
+
+    public static ApplicationInfo makeApplicationInfo(int targetSdkVersion) {
+        ApplicationInfo info = makeApplicationInfo();
+        info.targetSdkVersion = targetSdkVersion;
+        return info;
+    }
 }
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
index f7066a6..4b7a014 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -17,6 +17,7 @@
 package android.security.net.config;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
 import android.util.ArraySet;
@@ -44,7 +45,8 @@
     private final static String DEBUG_CA_SUBJ = "O=AOSP, CN=Test debug CA";
 
     public void testEmptyConfigFile() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_config);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_config,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertFalse(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
@@ -63,7 +65,8 @@
     }
 
     public void testEmptyAnchors() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_trust);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_trust,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertFalse(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
@@ -81,7 +84,8 @@
     }
 
     public void testBasicDomainConfig() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.domain1);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.domain1,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
@@ -117,7 +121,8 @@
     }
 
     public void testBasicPinning() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.pins1);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.pins1,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Check android.com.
@@ -132,7 +137,8 @@
     }
 
     public void testExpiredPin() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.expired_pin);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.expired_pin,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Check android.com.
@@ -146,7 +152,8 @@
     }
 
     public void testOverridesPins() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_pins);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_pins,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Check android.com.
@@ -160,7 +167,8 @@
     }
 
     public void testBadPin() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Check android.com.
@@ -175,7 +183,8 @@
     }
 
     public void testMultipleDomains() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_domains);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_domains,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
@@ -196,7 +205,8 @@
     }
 
     public void testMultipleDomainConfigs() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_configs);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_configs,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Should be two different config objects
@@ -211,7 +221,8 @@
     }
 
     public void testIncludeSubdomains() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.subdomains);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.subdomains,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Try connections.
@@ -224,7 +235,8 @@
     }
 
     public void testAttributes() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.attributes);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.attributes,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertFalse(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
@@ -233,7 +245,8 @@
     }
 
     public void testResourcePemCertificateSource() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_pem);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_pem,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         // Check android.com.
         NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
@@ -249,7 +262,8 @@
     }
 
     public void testResourceDerCertificateSource() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_der);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_der,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         // Check android.com.
         NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
@@ -265,7 +279,8 @@
     }
 
     public void testNestedDomainConfigs() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
@@ -283,7 +298,8 @@
     }
 
     public void testNestedDomainConfigsOverride() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains_override);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains_override,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
@@ -294,7 +310,8 @@
     }
 
     public void testDebugOverridesDisabled() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, false);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
         Set<TrustAnchor> anchors = config.getTrustAnchors();
@@ -305,7 +322,9 @@
     }
 
     public void testBasicDebugOverrides() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, true);
+        ApplicationInfo info = TestUtils.makeApplicationInfo();
+        info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, info);
         ApplicationConfig appConfig = new ApplicationConfig(source);
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
         Set<TrustAnchor> anchors = config.getTrustAnchors();
@@ -319,7 +338,9 @@
     }
 
     public void testDebugOverridesWithDomain() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
+        ApplicationInfo info = TestUtils.makeApplicationInfo();
+        info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, info);
         ApplicationConfig appConfig = new ApplicationConfig(source);
         NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
         Set<TrustAnchor> anchors = config.getTrustAnchors();
@@ -337,7 +358,9 @@
     }
 
     public void testDebugInherit() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
+        ApplicationInfo info = TestUtils.makeApplicationInfo();
+        info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, info);
         ApplicationConfig appConfig = new ApplicationConfig(source);
         NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
         Set<TrustAnchor> anchors = config.getTrustAnchors();
@@ -357,7 +380,8 @@
 
     private void testBadConfig(int configId) throws Exception {
         try {
-            XmlConfigSource source = new XmlConfigSource(getContext(), configId);
+            XmlConfigSource source = new XmlConfigSource(getContext(), configId,
+                    TestUtils.makeApplicationInfo());
             ApplicationConfig appConfig = new ApplicationConfig(source);
             appConfig.getConfigForHostname("android.com");
             fail("Bad config " + getContext().getResources().getResourceName(configId)
@@ -393,7 +417,8 @@
     }
 
     public void testTrustManagerKeystore() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin, true);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         Provider provider = new NetworkSecurityConfigProvider();
         TrustManagerFactory tmf =
@@ -415,7 +440,9 @@
     }
 
     public void testDebugDedup() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_dedup, true);
+        ApplicationInfo info = TestUtils.makeApplicationInfo();
+        info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_dedup, info);
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Check android.com.
@@ -433,15 +460,18 @@
     }
 
     public void testExtraDebugResource() throws Exception {
+        ApplicationInfo info = TestUtils.makeApplicationInfo();
+        info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
         XmlConfigSource source =
-                new XmlConfigSource(getContext(), R.xml.extra_debug_resource, true);
+                new XmlConfigSource(getContext(), R.xml.extra_debug_resource, info);
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertFalse(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
         MoreAsserts.assertNotEmpty(config.getTrustAnchors());
 
         // Check that the _debug file is ignored if debug is false.
-        source = new XmlConfigSource(getContext(), R.xml.extra_debug_resource, false);
+        source = new XmlConfigSource(getContext(), R.xml.extra_debug_resource,
+                TestUtils.makeApplicationInfo());
         appConfig = new ApplicationConfig(source);
         assertFalse(appConfig.hasPerDomainConfigs());
         config = appConfig.getConfigForHostname("");
@@ -451,12 +481,15 @@
     public void testExtraDebugResourceIgnored() throws Exception {
         // Verify that parsing the extra debug config resource fails only when debugging is true.
         XmlConfigSource source =
-                new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, false);
+                new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource,
+                        TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         // Force parsing the config file.
         appConfig.getConfigForHostname("");
 
-        source = new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, true);
+        ApplicationInfo info = TestUtils.makeApplicationInfo();
+        info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        source = new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, info);
         appConfig = new ApplicationConfig(source);
         try {
             appConfig.getConfigForHostname("");
@@ -467,7 +500,8 @@
 
     public void testDomainWhitespaceTrimming() throws Exception {
         XmlConfigSource source =
-                new XmlConfigSource(getContext(), R.xml.domain_whitespace, false);
+                new XmlConfigSource(getContext(), R.xml.domain_whitespace,
+                        TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         NetworkSecurityConfig defaultConfig = appConfig.getConfigForHostname("");
         MoreAsserts.assertNotEqual(defaultConfig, appConfig.getConfigForHostname("developer.android.com"));
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 9057a10..b4b8094 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -17,10 +17,12 @@
 package com.android.server;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -37,7 +39,6 @@
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.support.test.filters.SmallTest;
-import android.system.OsConstants;
 
 import java.net.Socket;
 import java.util.Arrays;
@@ -53,8 +54,8 @@
 @RunWith(Parameterized.class)
 public class IpSecServiceParameterizedTest {
 
-    private static final int DROID_SPI = 0xD1201D;
-    private static final int DROID_SPI2 = DROID_SPI + 1;
+    private static final int TEST_SPI_OUT = 0xD1201D;
+    private static final int TEST_SPI_IN = TEST_SPI_OUT + 1;
 
     private final String mRemoteAddr;
 
@@ -81,6 +82,16 @@
     IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
     IpSecService mIpSecService;
 
+    private static final IpSecAlgorithm AUTH_ALGO =
+            new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4);
+    private static final IpSecAlgorithm CRYPT_ALGO =
+            new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
+    private static final IpSecAlgorithm AEAD_ALGO =
+            new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, CRYPT_KEY, CRYPT_KEY.length * 4);
+
+    private static final int[] DIRECTIONS =
+            new int[] {IpSecTransform.DIRECTION_IN, IpSecTransform.DIRECTION_OUT};
+
     public IpSecServiceParameterizedTest(String remoteAddr) {
         mRemoteAddr = remoteAddr;
     }
@@ -103,14 +114,14 @@
                         eq(IpSecTransform.DIRECTION_OUT),
                         anyString(),
                         eq(mRemoteAddr),
-                        eq(DROID_SPI)))
-                .thenReturn(DROID_SPI);
+                        eq(TEST_SPI_OUT)))
+                .thenReturn(TEST_SPI_OUT);
 
         IpSecSpiResponse spiResp =
                 mIpSecService.reserveSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT, mRemoteAddr, DROID_SPI, new Binder());
+                        IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
         assertEquals(IpSecManager.Status.OK, spiResp.status);
-        assertEquals(DROID_SPI, spiResp.spi);
+        assertEquals(TEST_SPI_OUT, spiResp.spi);
     }
 
     @Test
@@ -120,56 +131,60 @@
                         eq(IpSecTransform.DIRECTION_OUT),
                         anyString(),
                         eq(mRemoteAddr),
-                        eq(DROID_SPI)))
-                .thenReturn(DROID_SPI);
+                        eq(TEST_SPI_OUT)))
+                .thenReturn(TEST_SPI_OUT);
 
         IpSecSpiResponse spiResp =
                 mIpSecService.reserveSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT, mRemoteAddr, DROID_SPI, new Binder());
+                        IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
 
         mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
 
         verify(mMockNetd)
                 .ipSecDeleteSecurityAssociation(
-                        eq(spiResp.resourceId), anyInt(), anyString(), anyString(), eq(DROID_SPI));
+                        eq(spiResp.resourceId),
+                        anyInt(),
+                        anyString(),
+                        anyString(),
+                        eq(TEST_SPI_OUT));
     }
 
-    IpSecConfig buildIpSecConfig() throws Exception {
-        IpSecManager ipSecManager = new IpSecManager(mIpSecService);
-
-        // Mocking the netd to allocate SPI
+    private int getNewSpiResourceId(int direction, String remoteAddress, int returnSpi)
+            throws Exception {
         when(mMockNetd.ipSecAllocateSpi(anyInt(), anyInt(), anyString(), anyString(), anyInt()))
-                .thenReturn(DROID_SPI)
-                .thenReturn(DROID_SPI2);
+                .thenReturn(returnSpi);
 
-        IpSecAlgorithm encryptAlgo = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm authAlgo =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 8);
+        IpSecSpiResponse spi =
+                mIpSecService.reserveSecurityParameterIndex(
+                        direction,
+                        NetworkUtils.numericToInetAddress(remoteAddress).getHostAddress(),
+                        IpSecManager.INVALID_SECURITY_PARAMETER_INDEX,
+                        new Binder());
+        return spi.resourceId;
+    }
 
-        /** Allocate and add SPI records in the IpSecService through IpSecManager interface. */
-        IpSecManager.SecurityParameterIndex outSpi =
-                ipSecManager.reserveSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT,
-                        NetworkUtils.numericToInetAddress(mRemoteAddr));
-        IpSecManager.SecurityParameterIndex inSpi =
-                ipSecManager.reserveSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_IN,
-                        NetworkUtils.numericToInetAddress(mRemoteAddr));
-
-        IpSecConfig config = new IpSecConfig();
-        config.setSpiResourceId(IpSecTransform.DIRECTION_IN, inSpi.getResourceId());
-        config.setSpiResourceId(IpSecTransform.DIRECTION_OUT, outSpi.getResourceId());
-        config.setEncryption(IpSecTransform.DIRECTION_OUT, encryptAlgo);
-        config.setAuthentication(IpSecTransform.DIRECTION_OUT, authAlgo);
-        config.setEncryption(IpSecTransform.DIRECTION_IN, encryptAlgo);
-        config.setAuthentication(IpSecTransform.DIRECTION_IN, authAlgo);
+    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);
-        return config;
+    }
+
+    private void addAuthAndCryptToIpSecConfig(IpSecConfig config) throws Exception {
+        for (int direction : DIRECTIONS) {
+            config.setEncryption(direction, CRYPT_ALGO);
+            config.setAuthentication(direction, AUTH_ALGO);
+        }
     }
 
     @Test
     public void testCreateTransportModeTransform() throws Exception {
-        IpSecConfig ipSecConfig = buildIpSecConfig();
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+        addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
                 mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
@@ -183,13 +198,72 @@
                         anyString(),
                         anyString(),
                         anyLong(),
-                        eq(DROID_SPI),
+                        eq(TEST_SPI_OUT),
                         eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
                         eq(AUTH_KEY),
                         anyInt(),
                         eq(IpSecAlgorithm.CRYPT_AES_CBC),
                         eq(CRYPT_KEY),
                         anyInt(),
+                        eq(""),
+                        isNull(),
+                        eq(0),
+                        anyInt(),
+                        anyInt(),
+                        anyInt());
+        verify(mMockNetd)
+                .ipSecAddSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        anyInt(),
+                        eq(IpSecTransform.DIRECTION_IN),
+                        anyString(),
+                        anyString(),
+                        anyLong(),
+                        eq(TEST_SPI_IN),
+                        eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
+                        eq(AUTH_KEY),
+                        anyInt(),
+                        eq(IpSecAlgorithm.CRYPT_AES_CBC),
+                        eq(CRYPT_KEY),
+                        anyInt(),
+                        eq(""),
+                        isNull(),
+                        eq(0),
+                        anyInt(),
+                        anyInt(),
+                        anyInt());
+    }
+
+    @Test
+    public void testCreateTransportModeTransformAead() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+
+        ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_OUT, AEAD_ALGO);
+        ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
+
+        IpSecTransformResponse createTransformResp =
+                mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+        assertEquals(IpSecManager.Status.OK, createTransformResp.status);
+
+        verify(mMockNetd)
+                .ipSecAddSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        anyInt(),
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        anyString(),
+                        anyString(),
+                        anyLong(),
+                        eq(TEST_SPI_OUT),
+                        eq(""),
+                        isNull(),
+                        eq(0),
+                        eq(""),
+                        isNull(),
+                        eq(0),
+                        eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM),
+                        eq(CRYPT_KEY),
+                        anyInt(),
                         anyInt(),
                         anyInt(),
                         anyInt());
@@ -201,11 +275,14 @@
                         anyString(),
                         anyString(),
                         anyLong(),
-                        eq(DROID_SPI2),
-                        eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
-                        eq(AUTH_KEY),
-                        anyInt(),
-                        eq(IpSecAlgorithm.CRYPT_AES_CBC),
+                        eq(TEST_SPI_IN),
+                        eq(""),
+                        isNull(),
+                        eq(0),
+                        eq(""),
+                        isNull(),
+                        eq(0),
+                        eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM),
                         eq(CRYPT_KEY),
                         anyInt(),
                         anyInt(),
@@ -214,8 +291,68 @@
     }
 
     @Test
+    public void testCreateInvalidConfigAeadWithAuth() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+
+        for (int direction : DIRECTIONS) {
+            ipSecConfig.setAuthentication(direction, AUTH_ALGO);
+            ipSecConfig.setAuthenticatedEncryption(direction, AEAD_ALGO);
+        }
+
+        try {
+            mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+            fail(
+                    "IpSecService should have thrown an error on authentication being"
+                            + " enabled with authenticated encryption");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testCreateInvalidConfigAeadWithCrypt() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+
+        for (int direction : DIRECTIONS) {
+            ipSecConfig.setEncryption(direction, CRYPT_ALGO);
+            ipSecConfig.setAuthenticatedEncryption(direction, AEAD_ALGO);
+        }
+
+        try {
+            mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+            fail(
+                    "IpSecService should have thrown an error on encryption being"
+                            + " enabled with authenticated encryption");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testCreateInvalidConfigAeadWithAuthAndCrypt() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+
+        for (int direction : DIRECTIONS) {
+            ipSecConfig.setAuthentication(direction, AUTH_ALGO);
+            ipSecConfig.setEncryption(direction, CRYPT_ALGO);
+            ipSecConfig.setAuthenticatedEncryption(direction, AEAD_ALGO);
+        }
+
+        try {
+            mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+            fail(
+                    "IpSecService should have thrown an error on authentication and encryption being"
+                            + " enabled with authenticated encryption");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
     public void testDeleteTransportModeTransform() throws Exception {
-        IpSecConfig ipSecConfig = buildIpSecConfig();
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+        addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
                 mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
@@ -227,19 +364,21 @@
                         eq(IpSecTransform.DIRECTION_OUT),
                         anyString(),
                         anyString(),
-                        eq(DROID_SPI));
+                        eq(TEST_SPI_OUT));
         verify(mMockNetd)
                 .ipSecDeleteSecurityAssociation(
                         eq(createTransformResp.resourceId),
                         eq(IpSecTransform.DIRECTION_IN),
                         anyString(),
                         anyString(),
-                        eq(DROID_SPI2));
+                        eq(TEST_SPI_IN));
     }
 
     @Test
     public void testApplyTransportModeTransform() throws Exception {
-        IpSecConfig ipSecConfig = buildIpSecConfig();
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+        addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
                 mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
@@ -255,7 +394,7 @@
                         eq(IpSecTransform.DIRECTION_OUT),
                         anyString(),
                         anyString(),
-                        eq(DROID_SPI));
+                        eq(TEST_SPI_OUT));
         verify(mMockNetd)
                 .ipSecApplyTransportModeTransform(
                         eq(pfd.getFileDescriptor()),
@@ -263,7 +402,7 @@
                         eq(IpSecTransform.DIRECTION_IN),
                         anyString(),
                         anyString(),
-                        eq(DROID_SPI2));
+                        eq(TEST_SPI_IN));
     }
 
     @Test
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index ff51d51..058504d 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -84,17 +84,18 @@
         "filter/AbiFilter.cpp",
         "filter/ConfigFilter.cpp",
         "format/Archive.cpp",
+        "format/Container.cpp",
         "format/binary/BinaryResourceParser.cpp",
         "format/binary/ResChunkPullParser.cpp",
         "format/binary/TableFlattener.cpp",
         "format/binary/XmlFlattener.cpp",
         "format/proto/ProtoDeserialize.cpp",
         "format/proto/ProtoSerialize.cpp",
-        "io/BigBufferStreams.cpp",
+        "io/BigBufferStream.cpp",
         "io/File.cpp",
-        "io/FileInputStream.cpp",
+        "io/FileStream.cpp",
         "io/FileSystem.cpp",
-        "io/StringInputStream.cpp",
+        "io/StringStream.cpp",
         "io/Util.cpp",
         "io/ZipArchive.cpp",
         "link/AutoVersioner.cpp",
@@ -167,7 +168,7 @@
         "test/Builders.cpp",
         "test/Common.cpp",
         "**/*_test.cpp",
-    ],
+    ] + toolSources,
     static_libs: [
         "libaapt2",
         "libgmock",
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 1555d6126..61c304b 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -35,11 +35,11 @@
 
 namespace {
 
-class PrintVisitor : public DescendingValueVisitor {
+class PrintVisitor : public ConstValueVisitor {
  public:
-  using DescendingValueVisitor::Visit;
+  using ConstValueVisitor::Visit;
 
-  void Visit(Attribute* attr) override {
+  void Visit(const Attribute* attr) override {
     std::cout << "(attr) type=";
     attr->PrintMask(&std::cout);
     static constexpr uint32_t kMask =
@@ -55,7 +55,7 @@
     }
   }
 
-  void Visit(Style* style) override {
+  void Visit(const Style* style) override {
     std::cout << "(style)";
     if (style->parent) {
       const Reference& parent_ref = style->parent.value();
@@ -90,15 +90,15 @@
     }
   }
 
-  void Visit(Array* array) override {
+  void Visit(const Array* array) override {
     array->Print(&std::cout);
   }
 
-  void Visit(Plural* plural) override {
+  void Visit(const Plural* plural) override {
     plural->Print(&std::cout);
   }
 
-  void Visit(Styleable* styleable) override {
+  void Visit(const Styleable* styleable) override {
     std::cout << "(styleable)";
     for (const auto& attr : styleable->entries) {
       std::cout << "\n        ";
@@ -116,17 +116,17 @@
     }
   }
 
-  void VisitItem(Item* item) override {
+  void VisitItem(const Item* item) override {
     item->Print(&std::cout);
   }
 };
 
 }  // namespace
 
-void Debug::PrintTable(ResourceTable* table, const DebugPrintTableOptions& options) {
+void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options) {
   PrintVisitor visitor;
 
-  for (auto& package : table->packages) {
+  for (const auto& package : table.packages) {
     std::cout << "Package name=" << package->name;
     if (package->id) {
       std::cout << " id=" << std::hex << (int)package->id.value() << std::dec;
@@ -261,11 +261,11 @@
 
 namespace {
 
-class XmlPrinter : public xml::Visitor {
+class XmlPrinter : public xml::ConstVisitor {
  public:
-  using xml::Visitor::Visit;
+  using xml::ConstVisitor::Visit;
 
-  void Visit(xml::Element* el) override {
+  void Visit(const xml::Element* el) override {
     const size_t previous_size = prefix_.size();
 
     for (const xml::NamespaceDecl& decl : el->namespace_decls) {
@@ -301,11 +301,11 @@
     }
 
     prefix_ += "  ";
-    xml::Visitor::Visit(el);
+    xml::ConstVisitor::Visit(el);
     prefix_.resize(previous_size);
   }
 
-  void Visit(xml::Text* text) override {
+  void Visit(const xml::Text* text) override {
     std::cerr << prefix_ << "T: '" << text->text << "'\n";
   }
 
@@ -315,9 +315,9 @@
 
 }  // namespace
 
-void Debug::DumpXml(xml::XmlResource* doc) {
+void Debug::DumpXml(const xml::XmlResource& doc) {
   XmlPrinter printer;
-  doc->root->Accept(&printer);
+  doc.root->Accept(&printer);
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index e2456c7..296d04b 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -31,13 +31,11 @@
 };
 
 struct Debug {
-  static void PrintTable(ResourceTable* table,
-                         const DebugPrintTableOptions& options = {});
+  static void PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options = {});
   static void PrintStyleGraph(ResourceTable* table,
                               const ResourceName& target_style);
   static void DumpHex(const void* data, size_t len);
-  static void DumpXml(xml::XmlResource* doc);
-  static std::string ToString(xml::XmlResource* doc);
+  static void DumpXml(const xml::XmlResource& doc);
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index c1815c8..ae32ee9 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -21,7 +21,7 @@
 #include "format/Archive.h"
 #include "format/binary/TableFlattener.h"
 #include "format/binary/XmlFlattener.h"
-#include "io/BigBufferInputStream.h"
+#include "io/BigBufferStream.h"
 #include "io/Util.h"
 #include "xml/XmlDom.h"
 
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index cbcc8fb..87b9867 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -157,12 +157,22 @@
 };
 
 struct ResourceFile {
+  enum class Type {
+    kUnknown,
+    kPng,
+    kBinaryXml,
+    kProtoXml,
+  };
+
   // Name
   ResourceName name;
 
   // Configuration
   ConfigDescription config;
 
+  // Type
+  Type type;
+
   // Source
   Source source;
 
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index f08b03e..9a5cd3e 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -22,7 +22,7 @@
 #include "ResourceTable.h"
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
 #include "test/Test.h"
 #include "xml/XmlPullParser.h"
 
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 6fac6e9..24187d9 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -704,8 +704,15 @@
       } else {
         if (type != ResourceType::kString && util::StartsWith(str, "res/")) {
           // This must be a FileReference.
-          return util::make_unique<FileReference>(dst_pool->MakeRef(
-              str, StringPool::Context(StringPool::Context::kHighPriority, config)));
+          std::unique_ptr<FileReference> file_ref =
+              util::make_unique<FileReference>(dst_pool->MakeRef(
+                  str, StringPool::Context(StringPool::Context::kHighPriority, config)));
+          if (util::EndsWith(*file_ref->path, ".xml")) {
+            file_ref->type = ResourceFile::Type::kBinaryXml;
+          } else if (util::EndsWith(*file_ref->path, ".png")) {
+            file_ref->type = ResourceFile::Type::kPng;
+          }
+          return std::move(file_ref);
         }
 
         // There are no styles associated with this string, so treat it as a simple string.
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index e013729..082fd86 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -65,7 +65,7 @@
 }
 
 RawString* RawString::Clone(StringPool* new_pool) const {
-  RawString* rs = new RawString(new_pool->MakeRef(*value));
+  RawString* rs = new RawString(new_pool->MakeRef(value));
   rs->comment_ = comment_;
   rs->source_ = source_;
   return rs;
@@ -207,7 +207,7 @@
 }
 
 String* String::Clone(StringPool* new_pool) const {
-  String* str = new String(new_pool->MakeRef(*value));
+  String* str = new String(new_pool->MakeRef(value));
   str->comment_ = comment_;
   str->source_ = source_;
   str->untranslatable_sections = untranslatable_sections;
@@ -290,8 +290,9 @@
 }
 
 FileReference* FileReference::Clone(StringPool* new_pool) const {
-  FileReference* fr = new FileReference(new_pool->MakeRef(*path));
+  FileReference* fr = new FileReference(new_pool->MakeRef(path));
   fr->file = file;
+  fr->type = type;
   fr->comment_ = comment_;
   fr->source_ = source_;
   return fr;
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 742765d..fd242a1 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -249,6 +249,10 @@
   // This field is NOT persisted in any format. It is transient.
   io::IFile* file = nullptr;
 
+  // FileType of the file pointed to by `file`. This is used to know how to inflate the file,
+  // or if to inflate at all (just copy).
+  ResourceFile::Type type = ResourceFile::Type::kUnknown;
+
   FileReference() = default;
   explicit FileReference(const StringPool::Ref& path);
 
diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp
index 10f9b55..a80a9dc 100644
--- a/tools/aapt2/ResourceValues_test.cpp
+++ b/tools/aapt2/ResourceValues_test.cpp
@@ -18,6 +18,10 @@
 
 #include "test/Test.h"
 
+using ::testing::Eq;
+using ::testing::SizeIs;
+using ::testing::StrEq;
+
 namespace aapt {
 
 TEST(ResourceValuesTest, PluralEquals) {
@@ -148,6 +152,22 @@
   EXPECT_TRUE(a->Equals(b.get()));
 }
 
+TEST(ResourcesValuesTest, StringClones) {
+  StringPool pool_a;
+  StringPool pool_b;
+
+  String str_a(pool_a.MakeRef("hello", StringPool::Context(test::ParseConfigOrDie("en"))));
+
+  ASSERT_THAT(pool_a, SizeIs(1u));
+  EXPECT_THAT(pool_a.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en")));
+  EXPECT_THAT(pool_a.strings()[0]->value, StrEq("hello"));
+
+  std::unique_ptr<String> str_b(str_a.Clone(&pool_b));
+  ASSERT_THAT(pool_b, SizeIs(1u));
+  EXPECT_THAT(pool_b.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en")));
+  EXPECT_THAT(pool_b.strings()[0]->value, StrEq("hello"));
+}
+
 TEST(ResourceValuesTest, StyleMerges) {
   StringPool pool_a;
   StringPool pool_b;
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 174b7f6..7e7c86d 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -269,8 +269,19 @@
 
 // A value that is a reference to an external entity, like an XML file or a PNG.
 message FileReference {
+  enum Type {
+    UNKNOWN = 0;
+    PNG = 1;
+    BINARY_XML = 2;
+    PROTO_XML = 3;
+  }
+
   // Path to a file within the APK (typically res/type-config/entry.ext).
   string path = 1;
+
+  // The type of file this path points to. For UAM bundle, this cannot be
+  // BINARY_XML.
+  Type type = 2;
 }
 
 // A value that represents a primitive data type (float, int, boolean, etc.).
diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto
index 0b0a252..520b242 100644
--- a/tools/aapt2/ResourcesInternal.proto
+++ b/tools/aapt2/ResourcesInternal.proto
@@ -41,13 +41,13 @@
   // The configuration for which the resource is defined.
   aapt.pb.Configuration config = 2;
 
+  // The type of the file.
+  aapt.pb.FileReference.Type type = 3;
+
   // The filesystem path to where the source file originated.
   // Mainly used to display helpful error messages.
-  string source_path = 3;
+  string source_path = 4;
 
   // Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file).
-  repeated Symbol exported_symbol = 4;
-
-  // If this is a compiled XML file, this is the root node.
-  aapt.pb.XmlNode xml_root = 5;
+  repeated Symbol exported_symbol = 5;
 }
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index 705b1ab..3a1a18c 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -191,6 +191,13 @@
   return Ref(borrow);
 }
 
+StringPool::Ref StringPool::MakeRef(const Ref& ref) {
+  if (ref.entry_->pool_ == this) {
+    return ref;
+  }
+  return MakeRef(ref.entry_->value, ref.entry_->context);
+}
+
 StringPool::StyleRef StringPool::MakeRef(const StyleString& str) {
   return MakeRef(str, Context{});
 }
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 8350d0d..3c1f3dc 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -49,6 +49,8 @@
 // Otherwise, the style data array would have to be sparse and take up more space.
 class StringPool {
  public:
+  using size_type = size_t;
+
   class Context {
    public:
     enum : uint32_t {
@@ -165,6 +167,9 @@
   // when sorting the string pool. Returns a reference to the string in the pool.
   Ref MakeRef(const android::StringPiece& str, const Context& context);
 
+  // Adds a string from another string pool. Returns a reference to the string in the string pool.
+  Ref MakeRef(const Ref& ref);
+
   // Adds a style to the string pool and returns a reference to it.
   StyleRef MakeRef(const StyleString& str);
 
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index a5e6aef..53910af 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -36,10 +36,11 @@
 #include "compile/PseudolocaleGenerator.h"
 #include "compile/XmlIdCollector.h"
 #include "format/Archive.h"
-#include "format/binary/XmlFlattener.h"
+#include "format/Container.h"
 #include "format/proto/ProtoSerialize.h"
-#include "io/BigBufferOutputStream.h"
-#include "io/FileInputStream.h"
+#include "io/BigBufferStream.h"
+#include "io/FileStream.h"
+#include "io/StringStream.h"
 #include "io/Util.h"
 #include "util/Files.h"
 #include "util/Maybe.h"
@@ -49,6 +50,7 @@
 
 using ::aapt::io::FileInputStream;
 using ::android::StringPiece;
+using ::android::base::SystemErrorCodeToString;
 using ::google::protobuf::io::CopyingOutputStreamAdaptor;
 
 namespace aapt {
@@ -116,7 +118,7 @@
   bool verbose = false;
 };
 
-static std::string BuildIntermediateFilename(const ResourcePathData& data) {
+static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) {
   std::stringstream name;
   name << data.resource_dir;
   if (!data.config_str.empty()) {
@@ -141,7 +143,7 @@
   std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
   if (!d) {
     context->GetDiagnostics()->Error(DiagMessage(root_dir) << "failed to open directory: "
-                                     << android::base::SystemErrorCodeToString(errno));
+                                                           << SystemErrorCodeToString(errno));
     return false;
   }
 
@@ -160,7 +162,7 @@
     std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
     if (!subdir) {
       context->GetDiagnostics()->Error(DiagMessage(prefix_path) << "failed to open directory: "
-                                       << android::base::SystemErrorCodeToString(errno));
+                                                                << SystemErrorCodeToString(errno));
       return false;
     }
 
@@ -241,16 +243,15 @@
     return false;
   }
 
-  // Make sure CopyingOutputStreamAdaptor is deleted before we call
-  // writer->FinishEntry().
+  // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
   {
-    // Wrap our IArchiveWriter with an adaptor that implements the
-    // ZeroCopyOutputStream interface.
+    // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
     CopyingOutputStreamAdaptor copying_adaptor(writer);
+    ContainerWriter container_writer(&copying_adaptor, 1u);
 
     pb::ResourceTable pb_table;
     SerializeTableToPb(table, &pb_table);
-    if (!pb_table.SerializeToZeroCopyStream(&copying_adaptor)) {
+    if (!container_writer.AddResTableEntry(pb_table)) {
       context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write");
       return false;
     }
@@ -263,46 +264,8 @@
   return true;
 }
 
-static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, const ResourceFile& file,
-                                         const BigBuffer& buffer, IArchiveWriter* writer,
-                                         IDiagnostics* diag) {
-  // Start the entry so we can write the header.
-  if (!writer->StartEntry(output_path, 0)) {
-    diag->Error(DiagMessage(output_path) << "failed to open file");
-    return false;
-  }
-
-  // Make sure CopyingOutputStreamAdaptor is deleted before we call
-  // writer->FinishEntry().
-  {
-    // Wrap our IArchiveWriter with an adaptor that implements the
-    // ZeroCopyOutputStream interface.
-    CopyingOutputStreamAdaptor copying_adaptor(writer);
-    CompiledFileOutputStream output_stream(&copying_adaptor);
-
-    // Number of CompiledFiles.
-    output_stream.WriteLittleEndian32(1);
-
-    pb::internal::CompiledFile pb_compiled_file;
-    SerializeCompiledFileToPb(file, &pb_compiled_file);
-    output_stream.WriteCompiledFile(pb_compiled_file);
-    output_stream.WriteData(buffer);
-
-    if (output_stream.HadError()) {
-      diag->Error(DiagMessage(output_path) << "failed to write data");
-      return false;
-    }
-  }
-
-  if (!writer->FinishEntry()) {
-    diag->Error(DiagMessage(output_path) << "failed to finish writing data");
-    return false;
-  }
-  return true;
-}
-
-static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const ResourceFile& file,
-                                       const android::FileMap& map, IArchiveWriter* writer,
+static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file,
+                                       io::KnownSizeInputStream* in, IArchiveWriter* writer,
                                        IDiagnostics* diag) {
   // Start the entry so we can write the header.
   if (!writer->StartEntry(output_path, 0)) {
@@ -310,24 +273,17 @@
     return false;
   }
 
-  // Make sure CopyingOutputStreamAdaptor is deleted before we call
-  // writer->FinishEntry().
+  // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
   {
-    // Wrap our IArchiveWriter with an adaptor that implements the
-    // ZeroCopyOutputStream interface.
+    // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
     CopyingOutputStreamAdaptor copying_adaptor(writer);
-    CompiledFileOutputStream output_stream(&copying_adaptor);
-
-    // Number of CompiledFiles.
-    output_stream.WriteLittleEndian32(1);
+    ContainerWriter container_writer(&copying_adaptor, 1u);
 
     pb::internal::CompiledFile pb_compiled_file;
     SerializeCompiledFileToPb(file, &pb_compiled_file);
-    output_stream.WriteCompiledFile(pb_compiled_file);
-    output_stream.WriteData(map.getDataPtr(), map.getDataLength());
 
-    if (output_stream.HadError()) {
-      diag->Error(DiagMessage(output_path) << "failed to write data");
+    if (!container_writer.AddResFileEntry(pb_compiled_file, in)) {
+      diag->Error(DiagMessage(output_path) << "failed to write entry data");
       return false;
     }
   }
@@ -339,23 +295,19 @@
   return true;
 }
 
-static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& output_path,
-                                  xml::XmlResource* xmlres, CompiledFileOutputStream* out) {
-  BigBuffer buffer(1024);
-  XmlFlattenerOptions xml_flattener_options;
-  xml_flattener_options.keep_raw_values = true;
-  XmlFlattener flattener(&buffer, xml_flattener_options);
-  if (!flattener.Consume(context, xmlres)) {
-    return false;
-  }
-
+static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres,
+                                  ContainerWriter* container_writer, IDiagnostics* diag) {
   pb::internal::CompiledFile pb_compiled_file;
-  SerializeCompiledFileToPb(xmlres->file, &pb_compiled_file);
-  out->WriteCompiledFile(pb_compiled_file);
-  out->WriteData(buffer);
+  SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file);
 
-  if (out->HadError()) {
-    context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write XML data");
+  pb::XmlNode pb_xml_node;
+  SerializeXmlToPb(*xmlres.root, &pb_xml_node);
+
+  std::string serialized_xml = pb_xml_node.SerializeAsString();
+  io::StringInputStream serialized_in(serialized_xml);
+
+  if (!container_writer->AddResFileEntry(pb_compiled_file, &serialized_in)) {
+    diag->Error(DiagMessage(output_path) << "failed to write entry data");
     return false;
   }
   return true;
@@ -404,6 +356,7 @@
   xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
   xmlres->file.config = path_data.config;
   xmlres->file.source = path_data.source;
+  xmlres->file.type = ResourceFile::Type::kProtoXml;
 
   // Collect IDs that are defined here.
   XmlIdCollector collector;
@@ -423,24 +376,23 @@
     return false;
   }
 
+  std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
+      inline_xml_format_parser.GetExtractedInlineXmlDocuments();
+
   // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
   {
     // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
     CopyingOutputStreamAdaptor copying_adaptor(writer);
-    CompiledFileOutputStream output_stream(&copying_adaptor);
+    ContainerWriter container_writer(&copying_adaptor, 1u + inline_documents.size());
 
-    std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
-        inline_xml_format_parser.GetExtractedInlineXmlDocuments();
-
-    // Number of CompiledFiles.
-    output_stream.WriteLittleEndian32(1 + inline_documents.size());
-
-    if (!FlattenXmlToOutStream(context, output_path, xmlres.get(), &output_stream)) {
+    if (!FlattenXmlToOutStream(output_path, *xmlres, &container_writer,
+                               context->GetDiagnostics())) {
       return false;
     }
 
-    for (auto& inline_xml_doc : inline_documents) {
-      if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(), &output_stream)) {
+    for (const std::unique_ptr<xml::XmlResource>& inline_xml_doc : inline_documents) {
+      if (!FlattenXmlToOutStream(output_path, *inline_xml_doc, &container_writer,
+                                 context->GetDiagnostics())) {
         return false;
       }
     }
@@ -465,6 +417,7 @@
   res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
   res_file.config = path_data.config;
   res_file.source = path_data.source;
+  res_file.type = ResourceFile::Type::kPng;
 
   {
     std::string content;
@@ -472,7 +425,7 @@
                                          true /*follow_symlinks*/)) {
       context->GetDiagnostics()->Error(DiagMessage(path_data.source)
                                        << "failed to open file: "
-                                       << android::base::SystemErrorCodeToString(errno));
+                                       << SystemErrorCodeToString(errno));
       return false;
     }
 
@@ -556,8 +509,9 @@
     }
   }
 
-  if (!WriteHeaderAndBufferToWriter(output_path, res_file, buffer, writer,
-                                    context->GetDiagnostics())) {
+  io::BigBufferInputStream buffer_in(&buffer);
+  if (!WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer,
+                                  context->GetDiagnostics())) {
     return false;
   }
   return true;
@@ -575,6 +529,7 @@
   res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
   res_file.config = path_data.config;
   res_file.source = path_data.source;
+  res_file.type = ResourceFile::Type::kUnknown;
 
   std::string error_str;
   Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
@@ -584,7 +539,8 @@
     return false;
   }
 
-  if (!WriteHeaderAndMmapToWriter(output_path, res_file, f.value(), writer,
+  io::MmappedData mmapped_in(std::move(f.value()));
+  if (!WriteHeaderAndDataToWriter(output_path, res_file, &mmapped_in, writer,
                                   context->GetDiagnostics())) {
     return false;
   }
@@ -614,7 +570,7 @@
   }
 
   NameMangler* GetNameMangler() override {
-    abort();
+    UNIMPLEMENTED(FATAL) << "No name mangling should be needed in compile phase";
     return nullptr;
   }
 
@@ -628,7 +584,7 @@
   }
 
   SymbolTable* GetExternalSymbols() override {
-    abort();
+    UNIMPLEMENTED(FATAL) << "No symbols should be needed in compile phase";
     return nullptr;
   }
 
@@ -637,14 +593,13 @@
   }
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(CompileContext);
+
   IDiagnostics* diagnostics_;
   bool verbose_ = false;
 };
 
-/**
- * Entry point for compilation phase. Parses arguments and dispatches to the
- * correct steps.
- */
+// Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
 int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
   CompileContext context(diagnostics);
   CompileOptions options;
@@ -717,50 +672,34 @@
       continue;
     }
 
+    // Determine how to compile the file based on its type.
+    auto compile_func = &CompileFile;
     if (path_data.resource_dir == "values") {
-      // Overwrite the extension.
+      compile_func = &CompileTable;
+      // We use a different extension (not necessary anymore, but avoids altering the existing
+      // build system logic).
       path_data.extension = "arsc";
-
-      const std::string output_filename = BuildIntermediateFilename(path_data);
-      if (!CompileTable(&context, options, path_data, archive_writer.get(), output_filename)) {
-        error = true;
-      }
-
-    } else {
-      const std::string output_filename = BuildIntermediateFilename(path_data);
-      if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
-        if (*type != ResourceType::kRaw) {
-          if (path_data.extension == "xml") {
-            if (!CompileXml(&context, options, path_data, archive_writer.get(), output_filename)) {
-              error = true;
-            }
-          } else if (!options.no_png_crunch &&
-                     (path_data.extension == "png" || path_data.extension == "9.png")) {
-            if (!CompilePng(&context, options, path_data, archive_writer.get(), output_filename)) {
-              error = true;
-            }
-          } else {
-            if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
-              error = true;
-            }
-          }
-        } else {
-          if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
-            error = true;
-          }
+    } else if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
+      if (*type != ResourceType::kRaw) {
+        if (path_data.extension == "xml") {
+          compile_func = &CompileXml;
+        } else if (!options.no_png_crunch &&
+                   (path_data.extension == "png" || path_data.extension == "9.png")) {
+          compile_func = &CompilePng;
         }
-      } else {
-        context.GetDiagnostics()->Error(DiagMessage() << "invalid file path '" << path_data.source
-                                                      << "'");
-        error = true;
       }
+    } else {
+      context.GetDiagnostics()->Error(DiagMessage()
+                                      << "invalid file path '" << path_data.source << "'");
+      error = true;
+      continue;
     }
-  }
 
-  if (error) {
-    return 1;
+    // Compile the file.
+    const std::string out_path = BuildIntermediateContainerFilename(path_data);
+    error |= !compile_func(&context, options, path_data, archive_writer.get(), out_path);
   }
-  return 0;
+  return error ? 1 : 0;
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 44032f6..090c3fb 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -21,8 +21,10 @@
 #include "Debug.h"
 #include "Diagnostics.h"
 #include "Flags.h"
+#include "format/Container.h"
 #include "format/binary/BinaryResourceParser.h"
 #include "format/proto/ProtoDeserialize.h"
+#include "io/FileStream.h"
 #include "io/ZipArchive.h"
 #include "process/IResourceTableConsumer.h"
 #include "util/Files.h"
@@ -31,42 +33,51 @@
 
 namespace aapt {
 
-bool DumpCompiledFile(const pb::internal::CompiledFile& pb_file, const void* data, size_t len,
-                      const Source& source, IAaptContext* context) {
-  ResourceFile file;
-  std::string error;
-  if (!DeserializeCompiledFileFromPb(pb_file, &file, &error)) {
-    context->GetDiagnostics()->Warn(DiagMessage(source)
-                                    << "failed to read compiled file: " << error);
-    return false;
+static const char* ResourceFileTypeToString(const ResourceFile::Type& type) {
+  switch (type) {
+    case ResourceFile::Type::kPng:
+      return "PNG";
+    case ResourceFile::Type::kBinaryXml:
+      return "BINARY_XML";
+    case ResourceFile::Type::kProtoXml:
+      return "PROTO_XML";
+    default:
+      break;
   }
-
-  std::cout << "Resource: " << file.name << "\n"
-            << "Config:   " << file.config << "\n"
-            << "Source:   " << file.source << "\n";
-  return true;
+  return "UNKNOWN";
 }
 
-bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
+static void DumpCompiledFile(const ResourceFile& file, const Source& source, off64_t offset,
+                             size_t len) {
+  std::cout << "Resource: " << file.name << "\n"
+            << "Config:   " << file.config << "\n"
+            << "Source:   " << file.source << "\n"
+            << "Type:     " << ResourceFileTypeToString(file.type) << "\n"
+            << "DataOff:  " << offset << "\n"
+            << "DataLen:  " << len << "\n";
+}
+
+static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
+  DebugPrintTableOptions print_options;
+  print_options.show_sources = true;
+
   std::string err;
   std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err);
   if (zip) {
     ResourceTable table;
-    if (io::IFile* file = zip->FindFile("resources.arsc.flat")) {
+    if (io::IFile* file = zip->FindFile("resources.pb")) {
       std::unique_ptr<io::IData> data = file->OpenAsData();
       if (data == nullptr) {
-        context->GetDiagnostics()->Error(DiagMessage(file_path)
-                                         << "failed to open resources.arsc.flat");
+        context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.pb");
         return false;
       }
 
       pb::ResourceTable pb_table;
       if (!pb_table.ParseFromArray(data->data(), data->size())) {
-        context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.arsc.flat");
+        context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.pb");
         return false;
       }
 
-      ResourceTable table;
       if (!DeserializeTableFromPb(pb_table, &table, &err)) {
         context->GetDiagnostics()->Error(DiagMessage(file_path)
                                          << "failed to parse table: " << err);
@@ -85,62 +96,72 @@
       }
     }
 
-    DebugPrintTableOptions options;
-    options.show_sources = true;
-    Debug::PrintTable(&table, options);
+    Debug::PrintTable(table, print_options);
     return true;
   }
 
   err.clear();
 
-  Maybe<android::FileMap> file = file::MmapPath(file_path, &err);
-  if (!file) {
-    context->GetDiagnostics()->Error(DiagMessage(file_path) << err);
+  io::FileInputStream input(file_path);
+  if (input.HadError()) {
+    context->GetDiagnostics()->Error(DiagMessage(file_path)
+                                     << "failed to open file: " << input.GetError());
     return false;
   }
 
-  android::FileMap* file_map = &file.value();
-
-  // Check to see if this is a loose ResourceTable.
-  pb::ResourceTable pb_table;
-  if (pb_table.ParseFromArray(file_map->getDataPtr(), file_map->getDataLength())) {
-    ResourceTable table;
-    if (DeserializeTableFromPb(pb_table, &table, &err)) {
-      DebugPrintTableOptions options;
-      options.show_sources = true;
-      Debug::PrintTable(&table, options);
-      return true;
-    }
-  }
-
   // Try as a compiled file.
-  CompiledFileInputStream input(file_map->getDataPtr(), file_map->getDataLength());
-  uint32_t num_files = 0;
-  if (!input.ReadLittleEndian32(&num_files)) {
+  ContainerReader reader(&input);
+  if (reader.HadError()) {
+    context->GetDiagnostics()->Error(DiagMessage(file_path)
+                                     << "failed to read container: " << reader.GetError());
     return false;
   }
 
-  for (uint32_t i = 0; i < num_files; i++) {
-    pb::internal::CompiledFile compiled_file;
-    if (!input.ReadCompiledFile(&compiled_file)) {
-      context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file");
-      return false;
-    }
+  ContainerReaderEntry* entry;
+  while ((entry = reader.Next()) != nullptr) {
+    if (entry->Type() == ContainerEntryType::kResTable) {
+      pb::ResourceTable pb_table;
+      if (!entry->GetResTable(&pb_table)) {
+        context->GetDiagnostics()->Error(DiagMessage(file_path)
+                                         << "failed to parse proto table: " << entry->GetError());
+        continue;
+      }
 
-    uint64_t offset, len;
-    if (!input.ReadDataMetaData(&offset, &len)) {
-      context->GetDiagnostics()->Warn(DiagMessage() << "failed to read meta data");
-      return false;
-    }
+      ResourceTable table;
+      err.clear();
+      if (!DeserializeTableFromPb(pb_table, &table, &err)) {
+        context->GetDiagnostics()->Error(DiagMessage(file_path)
+                                         << "failed to parse table: " << err);
+        continue;
+      }
 
-    const void* data = static_cast<const uint8_t*>(file_map->getDataPtr()) + offset;
-    if (!DumpCompiledFile(compiled_file, data, len, Source(file_path), context)) {
-      return false;
+      Debug::PrintTable(table, print_options);
+    } else if (entry->Type() == ContainerEntryType::kResFile) {
+      pb::internal::CompiledFile pb_compiled_file;
+      off64_t offset;
+      size_t length;
+      if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &length)) {
+        context->GetDiagnostics()->Error(
+            DiagMessage(file_path) << "failed to parse compiled proto file: " << entry->GetError());
+        continue;
+      }
+
+      ResourceFile file;
+      std::string error;
+      if (!DeserializeCompiledFileFromPb(pb_compiled_file, &file, &error)) {
+        context->GetDiagnostics()->Warn(DiagMessage(file_path)
+                                        << "failed to parse compiled file: " << error);
+        continue;
+      }
+
+      DumpCompiledFile(file, Source(file_path), offset, length);
     }
   }
   return true;
 }
 
+namespace {
+
 class DumpContext : public IAaptContext {
  public:
   PackageType GetPackageType() override {
@@ -153,7 +174,7 @@
   }
 
   NameMangler* GetNameMangler() override {
-    abort();
+    UNIMPLEMENTED(FATAL);
     return nullptr;
   }
 
@@ -167,7 +188,7 @@
   }
 
   SymbolTable* GetExternalSymbols() override {
-    abort();
+    UNIMPLEMENTED(FATAL);
     return nullptr;
   }
 
@@ -188,9 +209,9 @@
   bool verbose_ = false;
 };
 
-/**
- * Entry point for dump command.
- */
+}  // namespace
+
+// Entry point for dump command.
 int Dump(const std::vector<StringPiece>& args) {
   bool verbose = false;
   Flags flags = Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose);
@@ -206,7 +227,6 @@
       return 1;
     }
   }
-
   return 0;
 }
 
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 40d71a3..55a4c43 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -25,7 +25,6 @@
 #include "android-base/file.h"
 #include "android-base/stringprintf.h"
 #include "androidfw/StringPiece.h"
-#include "google/protobuf/io/coded_stream.h"
 
 #include "AppInfo.h"
 #include "Debug.h"
@@ -39,13 +38,14 @@
 #include "compile/IdAssigner.h"
 #include "filter/ConfigFilter.h"
 #include "format/Archive.h"
+#include "format/Container.h"
 #include "format/binary/BinaryResourceParser.h"
 #include "format/binary/TableFlattener.h"
 #include "format/binary/XmlFlattener.h"
 #include "format/proto/ProtoDeserialize.h"
 #include "format/proto/ProtoSerialize.h"
-#include "io/BigBufferInputStream.h"
-#include "io/FileInputStream.h"
+#include "io/BigBufferStream.h"
+#include "io/FileStream.h"
 #include "io/FileSystem.h"
 #include "io/Util.h"
 #include "io/ZipArchive.h"
@@ -71,6 +71,14 @@
 
 namespace aapt {
 
+constexpr static const char kApkResourceTablePath[] = "resources.arsc";
+constexpr static const char kProtoResourceTablePath[] = "resources.pb";
+
+enum class OutputFormat {
+  kApk,
+  kProto,
+};
+
 struct LinkOptions {
   std::string output_path;
   std::string manifest_path;
@@ -79,6 +87,7 @@
   std::vector<std::string> assets_dirs;
   bool output_to_directory = false;
   bool auto_add_overlay = false;
+  OutputFormat output_format = OutputFormat::kApk;
 
   // Java/Proguard options.
   Maybe<std::string> generate_java_class_path;
@@ -253,26 +262,39 @@
   IAaptContext* context_;
 };
 
-static bool FlattenXml(IAaptContext* context, xml::XmlResource* xml_res, const StringPiece& path,
-                       bool keep_raw_values, bool utf16, IArchiveWriter* writer) {
-  BigBuffer buffer(1024);
-  XmlFlattenerOptions options = {};
-  options.keep_raw_values = keep_raw_values;
-  options.use_utf16 = utf16;
-  XmlFlattener flattener(&buffer, options);
-  if (!flattener.Consume(context, xml_res)) {
-    return false;
-  }
-
+static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res,
+                       const StringPiece& path, bool keep_raw_values, bool utf16,
+                       OutputFormat format, IArchiveWriter* writer) {
   if (context->IsVerbose()) {
     context->GetDiagnostics()->Note(DiagMessage(path) << "writing to archive (keep_raw_values="
                                                       << (keep_raw_values ? "true" : "false")
                                                       << ")");
   }
 
-  io::BigBufferInputStream input_stream(&buffer);
-  return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(),
-                                      ArchiveEntry::kCompress, writer);
+  switch (format) {
+    case OutputFormat::kApk: {
+      BigBuffer buffer(1024);
+      XmlFlattenerOptions options = {};
+      options.keep_raw_values = keep_raw_values;
+      options.use_utf16 = utf16;
+      XmlFlattener flattener(&buffer, options);
+      if (!flattener.Consume(context, &xml_res)) {
+        return false;
+      }
+
+      io::BigBufferInputStream input_stream(&buffer);
+      return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(),
+                                          ArchiveEntry::kCompress, writer);
+    } break;
+
+    case OutputFormat::kProto: {
+      pb::XmlNode pb_node;
+      SerializeXmlResourceToPb(xml_res, &pb_node);
+      return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress,
+                                    writer);
+    } break;
+  }
+  return false;
 }
 
 static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, const void* data,
@@ -310,6 +332,7 @@
   bool keep_raw_values = false;
   bool do_not_compress_anything = false;
   bool update_proguard_spec = false;
+  OutputFormat output_format = OutputFormat::kApk;
   std::unordered_set<std::string> extensions_to_not_compress;
 };
 
@@ -467,6 +490,10 @@
                                      << "linking " << src.path << " (" << doc->file.name << ")");
   }
 
+  // First, strip out any tools namespace attributes. AAPT stripped them out early, which means
+  // that existing projects have out-of-date references which pass compilation.
+  xml::StripAndroidStudioAttributes(doc->root.get());
+
   XmlReferenceLinker xml_linker;
   if (!xml_linker.Consume(context_, doc)) {
     return {};
@@ -543,9 +570,9 @@
           file_op.config = config_value->config;
           file_op.file_to_copy = file;
 
-          const StringPiece src_path = file->GetSource().path;
           if (type->type != ResourceType::kRaw &&
-              (util::EndsWith(src_path, ".xml.flat") || util::EndsWith(src_path, ".xml"))) {
+              (file_ref->type == ResourceFile::Type::kBinaryXml ||
+               file_ref->type == ResourceFile::Type::kProtoXml)) {
             std::unique_ptr<io::IData> data = file->OpenAsData();
             if (!data) {
               context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
@@ -553,11 +580,27 @@
               return false;
             }
 
-            file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(),
-                                                  context_->GetDiagnostics(), file->GetSource());
+            if (file_ref->type == ResourceFile::Type::kProtoXml) {
+              pb::XmlNode pb_xml_node;
+              if (!pb_xml_node.ParseFromArray(data->data(), static_cast<int>(data->size()))) {
+                context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+                                                  << "failed to parse proto xml");
+                return false;
+              }
 
-            if (!file_op.xml_to_flatten) {
-              return false;
+              std::string error;
+              file_op.xml_to_flatten = DeserializeXmlResourceFromPb(pb_xml_node, &error);
+              if (file_op.xml_to_flatten == nullptr) {
+                context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+                                                  << "failed to deserialize proto xml: " << error);
+                return false;
+              }
+            } else {
+              file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(),
+                                                    context_->GetDiagnostics(), file->GetSource());
+              if (file_op.xml_to_flatten == nullptr) {
+                return false;
+              }
             }
 
             file_op.xml_to_flatten->file.config = config_value->config;
@@ -607,8 +650,9 @@
                 return false;
               }
             }
-            error |= !FlattenXml(context_, doc.get(), dst_path, options_.keep_raw_values,
-                                 false /*utf16*/, archive_writer);
+
+            error |= !FlattenXml(context_, *doc, dst_path, options_.keep_raw_values,
+                                 false /*utf16*/, options_.output_format, archive_writer);
           }
         } else {
           error |= !io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path,
@@ -907,23 +951,29 @@
     }
   }
 
-  bool FlattenTable(ResourceTable* table, IArchiveWriter* writer) {
-    BigBuffer buffer(1024);
-    TableFlattener flattener(options_.table_flattener_options, &buffer);
-    if (!flattener.Consume(context_, table)) {
-      context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table");
-      return false;
+  bool FlattenTable(ResourceTable* table, OutputFormat format, IArchiveWriter* writer) {
+    switch (format) {
+      case OutputFormat::kApk: {
+        BigBuffer buffer(1024);
+        TableFlattener flattener(options_.table_flattener_options, &buffer);
+        if (!flattener.Consume(context_, table)) {
+          context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table");
+          return false;
+        }
+
+        io::BigBufferInputStream input_stream(&buffer);
+        return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
+                                            ArchiveEntry::kAlign, writer);
+      } break;
+
+      case OutputFormat::kProto: {
+        pb::ResourceTable pb_table;
+        SerializeTableToPb(*table, &pb_table);
+        return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
+                                      ArchiveEntry::kCompress, writer);
+      } break;
     }
-
-    io::BigBufferInputStream input_stream(&buffer);
-    return io::CopyInputStreamToArchive(context_, &input_stream, "resources.arsc",
-                                        ArchiveEntry::kAlign, writer);
-  }
-
-  bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
-    pb::ResourceTable pb_table;
-    SerializeTableToPb(*table, &pb_table);
-    return io::CopyProtoToArchive(context_, &pb_table, "resources.arsc.flat", 0, writer);
+    return false;
   }
 
   bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate,
@@ -1152,7 +1202,7 @@
   }
 
   std::unique_ptr<ResourceTable> LoadTablePbFromCollection(io::IFileCollection* collection) {
-    io::IFile* file = collection->FindFile("resources.arsc.flat");
+    io::IFile* file = collection->FindFile(kProtoResourceTablePath);
     if (!file) {
       return {};
     }
@@ -1201,11 +1251,7 @@
       // Clear the package name, so as to make the resources look like they are coming from the
       // local package.
       pkg->name = "";
-      if (override) {
-        result = table_merger_->MergeOverlay(Source(input), table.get(), collection.get());
-      } else {
-        result = table_merger_->Merge(Source(input), table.get(), collection.get());
-      }
+      result = table_merger_->Merge(Source(input), table.get(), override, collection.get());
 
     } else {
       // This is the proper way to merge libraries, where the package name is
@@ -1241,49 +1287,34 @@
       return false;
     }
 
-    bool result = false;
-    if (override) {
-      result = table_merger_->MergeOverlay(file->GetSource(), table.get());
-    } else {
-      result = table_merger_->Merge(file->GetSource(), table.get());
-    }
-    return result;
+    return table_merger_->Merge(file->GetSource(), table.get(), override);
   }
 
-  bool MergeCompiledFile(io::IFile* file, ResourceFile* file_desc, bool override) {
+  bool MergeCompiledFile(const ResourceFile& compiled_file, io::IFile* file, bool override) {
     if (context_->IsVerbose()) {
-      context_->GetDiagnostics()->Note(DiagMessage() << "merging '" << file_desc->name
-                                                     << "' from compiled file "
-                                                     << file->GetSource());
+      context_->GetDiagnostics()->Note(DiagMessage()
+                                       << "merging '" << compiled_file.name
+                                       << "' from compiled file " << compiled_file.source);
     }
 
-    bool result = false;
-    if (override) {
-      result = table_merger_->MergeFileOverlay(*file_desc, file);
-    } else {
-      result = table_merger_->MergeFile(*file_desc, file);
-    }
-
-    if (!result) {
+    if (!table_merger_->MergeFile(compiled_file, override, file)) {
       return false;
     }
 
     // Add the exports of this file to the table.
-    for (SourcedResourceName& exported_symbol : file_desc->exported_symbols) {
-      if (exported_symbol.name.package.empty()) {
-        exported_symbol.name.package = context_->GetCompilationPackage();
+    for (const SourcedResourceName& exported_symbol : compiled_file.exported_symbols) {
+      ResourceName res_name = exported_symbol.name;
+      if (res_name.package.empty()) {
+        res_name.package = context_->GetCompilationPackage();
       }
 
-      ResourceNameRef res_name = exported_symbol.name;
-
-      Maybe<ResourceName> mangled_name =
-          context_->GetNameMangler()->MangleName(exported_symbol.name);
+      Maybe<ResourceName> mangled_name = context_->GetNameMangler()->MangleName(res_name);
       if (mangled_name) {
         res_name = mangled_name.value();
       }
 
       std::unique_ptr<Id> id = util::make_unique<Id>();
-      id->SetSource(file_desc->source.WithLine(exported_symbol.line));
+      id->SetSource(compiled_file.source.WithLine(exported_symbol.line));
       bool result = final_table_.AddResourceAllowMangled(
           res_name, ConfigDescription::DefaultConfig(), std::string(), std::move(id),
           context_->GetDiagnostics());
@@ -1294,15 +1325,11 @@
     return true;
   }
 
-  /**
-   * Takes a path to load as a ZIP file and merges the files within into the
-   * master ResourceTable.
-   * If override is true, conflicting resources are allowed to override each
-   * other, in order of last seen.
-   *
-   * An io::IFileCollection is created from the ZIP file and added to the set of
-   * io::IFileCollections that are open.
-   */
+  // Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
+  // If override is true, conflicting resources are allowed to override each other, in order of last
+  // seen.
+  // An io::IFileCollection is created from the ZIP file and added to the set of
+  // io::IFileCollections that are open.
   bool MergeArchive(const std::string& input, bool override) {
     if (context_->IsVerbose()) {
       context_->GetDiagnostics()->Note(DiagMessage() << "merging archive " << input);
@@ -1328,18 +1355,11 @@
     return !error;
   }
 
-  /**
-   * Takes a path to load and merge into the master ResourceTable. If override
-   * is true,
-   * conflicting resources are allowed to override each other, in order of last
-   * seen.
-   *
-   * If the file path ends with .flata, .jar, .jack, or .zip the file is treated
-   * as ZIP archive
-   * and the files within are merged individually.
-   *
-   * Otherwise the files is processed on its own.
-   */
+  // Takes a path to load and merge into the master ResourceTable. If override is true,
+  // conflicting resources are allowed to override each other, in order of last seen.
+  // If the file path ends with .flata, .jar, .jack, or .zip the file is treated
+  // as ZIP archive and the files within are merged individually.
+  // Otherwise the file is processed on its own.
   bool MergePath(const std::string& path, bool override) {
     if (util::EndsWith(path, ".flata") || util::EndsWith(path, ".jar") ||
         util::EndsWith(path, ".jack") || util::EndsWith(path, ".zip")) {
@@ -1352,70 +1372,15 @@
     return MergeFile(file, override);
   }
 
-  /**
-   * Takes a file to load and merge into the master ResourceTable. If override
-   * is true,
-   * conflicting resources are allowed to override each other, in order of last
-   * seen.
-   *
-   * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and
-   * merged into the
-   * master ResourceTable. If the file ends with .flat, then it is treated like
-   * a compiled file
-   * and the header data is read and merged into the final ResourceTable.
-   *
-   * All other file types are ignored. This is because these files could be
-   * coming from a zip,
-   * where we could have other files like classes.dex.
-   */
+  // Takes an AAPT Container file (.apc/.flat) to load and merge into the master ResourceTable.
+  // If override is true, conflicting resources are allowed to override each other, in order of last
+  // seen.
+  // All other file types are ignored. This is because these files could be coming from a zip,
+  // where we could have other files like classes.dex.
   bool MergeFile(io::IFile* file, bool override) {
     const Source& src = file->GetSource();
-    if (util::EndsWith(src.path, ".arsc.flat")) {
-      return MergeResourceTable(file, override);
 
-    } else if (util::EndsWith(src.path, ".flat")) {
-      // Try opening the file and looking for an Export header.
-      std::unique_ptr<io::IData> data = file->OpenAsData();
-      if (!data) {
-        context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open");
-        return false;
-      }
-
-      CompiledFileInputStream input_stream(data->data(), data->size());
-      uint32_t num_files = 0;
-      if (!input_stream.ReadLittleEndian32(&num_files)) {
-        context_->GetDiagnostics()->Error(DiagMessage(src) << "failed read num files");
-        return false;
-      }
-
-      for (uint32_t i = 0; i < num_files; i++) {
-        pb::internal::CompiledFile compiled_file;
-        if (!input_stream.ReadCompiledFile(&compiled_file)) {
-          context_->GetDiagnostics()->Error(DiagMessage(src)
-                                            << "failed to read compiled file header");
-          return false;
-        }
-
-        uint64_t offset, len;
-        if (!input_stream.ReadDataMetaData(&offset, &len)) {
-          context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read data meta data");
-          return false;
-        }
-
-        ResourceFile resource_file;
-        std::string error;
-        if (!DeserializeCompiledFileFromPb(compiled_file, &resource_file, &error)) {
-          context_->GetDiagnostics()->Error(DiagMessage(src)
-                                            << "failed to read compiled header: " << error);
-          return false;
-        }
-
-        if (!MergeCompiledFile(file->CreateFileSegment(offset, len), &resource_file, override)) {
-          return false;
-        }
-      }
-      return true;
-    } else if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) {
+    if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) {
       // Since AAPT compiles these file types and appends .flat to them, seeing
       // their raw extensions is a sign that they weren't compiled.
       const StringPiece file_type = util::EndsWith(src.path, ".xml") ? "XML" : "PNG";
@@ -1423,11 +1388,71 @@
                                                          << " file passed as argument. Must be "
                                                             "compiled first into .flat file.");
       return false;
+    } else if (!util::EndsWith(src.path, ".apc") && !util::EndsWith(src.path, ".flat")) {
+      if (context_->IsVerbose()) {
+        context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring unrecognized file");
+        return true;
+      }
     }
 
-    // Ignore non .flat files. This could be classes.dex or something else that
-    // happens
-    // to be in an archive.
+    std::unique_ptr<io::InputStream> input_stream = file->OpenInputStream();
+    if (input_stream == nullptr) {
+      context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open file");
+      return false;
+    }
+
+    if (input_stream->HadError()) {
+      context_->GetDiagnostics()->Error(DiagMessage(src)
+                                        << "failed to open file: " << input_stream->GetError());
+      return false;
+    }
+
+    ContainerReaderEntry* entry;
+    ContainerReader reader(input_stream.get());
+    while ((entry = reader.Next()) != nullptr) {
+      if (entry->Type() == ContainerEntryType::kResTable) {
+        pb::ResourceTable pb_table;
+        if (!entry->GetResTable(&pb_table)) {
+          context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read resource table: "
+                                                             << entry->GetError());
+          return false;
+        }
+
+        ResourceTable table;
+        std::string error;
+        if (!DeserializeTableFromPb(pb_table, &table, &error)) {
+          context_->GetDiagnostics()->Error(DiagMessage(src)
+                                            << "failed to deserialize resource table: " << error);
+          return false;
+        }
+
+        if (!table_merger_->Merge(src, &table, override)) {
+          context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to merge resource table");
+          return false;
+        }
+      } else if (entry->Type() == ContainerEntryType::kResFile) {
+        pb::internal::CompiledFile pb_compiled_file;
+        off64_t offset;
+        size_t len;
+        if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &len)) {
+          context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to get resource file: "
+                                                             << entry->GetError());
+          return false;
+        }
+
+        ResourceFile resource_file;
+        std::string error;
+        if (!DeserializeCompiledFileFromPb(pb_compiled_file, &resource_file, &error)) {
+          context_->GetDiagnostics()->Error(DiagMessage(src)
+                                            << "failed to read compiled header: " << error);
+          return false;
+        }
+
+        if (!MergeCompiledFile(resource_file, file->CreateFileSegment(offset, len), override)) {
+          return false;
+        }
+      }
+    }
     return true;
   }
 
@@ -1471,15 +1496,13 @@
     return true;
   }
 
-  /**
-   * Writes the AndroidManifest, ResourceTable, and all XML files referenced by
-   * the ResourceTable to the IArchiveWriter.
-   */
+  // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
+  // to the IArchiveWriter.
   bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
                 ResourceTable* table) {
     const bool keep_raw_values = context_->GetPackageType() == PackageType::kStaticLib;
-    bool result = FlattenXml(context_, manifest, "AndroidManifest.xml", keep_raw_values,
-                             true /*utf16*/, writer);
+    bool result = FlattenXml(context_, *manifest, "AndroidManifest.xml", keep_raw_values,
+                             true /*utf16*/, options_.output_format, writer);
     if (!result) {
       return false;
     }
@@ -1494,6 +1517,7 @@
     file_flattener_options.no_xml_namespaces = options_.no_xml_namespaces;
     file_flattener_options.update_proguard_spec =
         static_cast<bool>(options_.generate_proguard_rules_path);
+    file_flattener_options.output_format = options_.output_format;
 
     ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set);
 
@@ -1502,15 +1526,9 @@
       return false;
     }
 
-    if (context_->GetPackageType() == PackageType::kStaticLib) {
-      if (!FlattenTableToPb(table, writer)) {
-        return false;
-      }
-    } else {
-      if (!FlattenTable(table, writer)) {
-        context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc");
-        return false;
-      }
+    if (!FlattenTable(table, options_.output_format, writer)) {
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resource table");
+      return false;
     }
     return true;
   }
@@ -1874,6 +1892,7 @@
   bool verbose = false;
   bool shared_lib = false;
   bool static_lib = false;
+  bool proto_format = false;
   Maybe<std::string> stable_id_file_path;
   std::vector<std::string> split_args;
   Flags flags =
@@ -1954,6 +1973,10 @@
           .OptionalSwitch("--shared-lib", "Generates a shared Android runtime library.",
                           &shared_lib)
           .OptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib)
+          .OptionalSwitch("--proto-format",
+                          "Generates compiled resources in Protobuf format.\n"
+                          "Suitable as input to the bundle tool for generating an App Bundle.",
+                          &proto_format)
           .OptionalSwitch("--no-static-lib-packages",
                           "Merge all library resources under the app's package.",
                           &options.no_static_lib_packages)
@@ -2040,21 +2063,25 @@
     context.SetVerbose(verbose);
   }
 
-  if (shared_lib && static_lib) {
-    context.GetDiagnostics()->Error(DiagMessage()
-                                    << "only one of --shared-lib and --static-lib can be defined");
+  if (int{shared_lib} + int{static_lib} + int{proto_format} > 1) {
+    context.GetDiagnostics()->Error(
+        DiagMessage()
+        << "only one of --shared-lib, --static-lib, or --proto_format can be defined");
     return 1;
   }
 
+  // The default build type.
+  context.SetPackageType(PackageType::kApp);
+  context.SetPackageId(kAppPackageId);
+
   if (shared_lib) {
     context.SetPackageType(PackageType::kSharedLib);
     context.SetPackageId(0x00);
   } else if (static_lib) {
     context.SetPackageType(PackageType::kStaticLib);
-    context.SetPackageId(kAppPackageId);
-  } else {
-    context.SetPackageType(PackageType::kApp);
-    context.SetPackageId(kAppPackageId);
+    options.output_format = OutputFormat::kProto;
+  } else if (proto_format) {
+    options.output_format = OutputFormat::kProto;
   }
 
   if (package_id) {
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 67ac67a..44e148e 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -33,7 +33,7 @@
 #include "filter/AbiFilter.h"
 #include "format/binary/TableFlattener.h"
 #include "format/binary/XmlFlattener.h"
-#include "io/BigBufferInputStream.h"
+#include "io/BigBufferStream.h"
 #include "io/Util.h"
 #include "optimize/MultiApkGenerator.h"
 #include "optimize/ResourceDeduper.h"
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index d17858d..708bed8 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -72,7 +72,6 @@
   }
 
   *out_path = parts[0];
-  std::vector<ConfigDescription> configs;
   for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) {
     ConfigDescription config;
     if (!ConfigDescription::Parse(config_str, &config)) {
@@ -141,6 +140,16 @@
   return decl;
 }
 
+static std::string MakePackageSafeName(const std::string &name) {
+  std::string result(name);
+  for (char &c : result) {
+    if (c == '-') {
+      c = '_';
+    }
+  }
+  return result;
+}
+
 std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
                                                         const SplitConstraints& constraints) {
   const ResourceId kVersionCode(0x0101021b);
@@ -172,7 +181,11 @@
   if (app_info.split_name) {
     split_name << app_info.split_name.value() << ".";
   }
-  split_name << "config." << util::Joiner(constraints.configs, "_");
+  std::vector<std::string> sanitized_config_names;
+  for (const auto &config : constraints.configs) {
+    sanitized_config_names.push_back(MakePackageSafeName(config.toString().string()));
+  }
+  split_name << "config." << util::Joiner(sanitized_config_names, "_");
 
   manifest_el->attributes.push_back(xml::Attribute{"", "split", split_name.str()});
 
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
new file mode 100644
index 0000000..9c33135
--- /dev/null
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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 "Util.h"
+
+#include "AppInfo.h"
+#include "split/TableSplitter.h"
+#include "test/Test.h"
+
+namespace aapt {
+
+TEST(UtilTest, SplitNamesAreSanitized) {
+    AppInfo app_info{"com.pkg"};
+    SplitConstraints split_constraints{{test::ParseConfigOrDie("en-rUS-land")}};
+
+    const auto doc = GenerateSplitManifest(app_info, split_constraints);
+    const auto &root = doc->root;
+    EXPECT_EQ(root->name, "manifest");
+    // split names cannot contain hyphens
+    EXPECT_EQ(root->FindAttribute("", "split")->value, "config.en_rUS_land");
+    // but we should use resource qualifiers verbatim in 'targetConfig'.
+    EXPECT_EQ(root->FindAttribute("", "targetConfig")->value, "en-rUS-land");
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index a79a577..b99240f 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -30,7 +30,7 @@
 #include "ResourceUtils.h"
 #include "io/File.h"
 #include "io/FileSystem.h"
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
 #include "util/Files.h"
 #include "util/Maybe.h"
 #include "util/Util.h"
diff --git a/tools/aapt2/format/Container.cpp b/tools/aapt2/format/Container.cpp
new file mode 100644
index 0000000..739555c
--- /dev/null
+++ b/tools/aapt2/format/Container.cpp
@@ -0,0 +1,353 @@
+/*
+ * 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 "format/Container.h"
+
+#include "android-base/scopeguard.h"
+#include "android-base/stringprintf.h"
+
+using ::android::base::StringPrintf;
+using ::google::protobuf::io::CodedInputStream;
+using ::google::protobuf::io::CodedOutputStream;
+using ::google::protobuf::io::ZeroCopyOutputStream;
+
+namespace aapt {
+
+constexpr const static uint32_t kContainerFormatMagic = 0x54504141u;
+constexpr const static uint32_t kContainerFormatVersion = 1u;
+
+ContainerWriter::ContainerWriter(ZeroCopyOutputStream* out, size_t entry_count)
+    : out_(out), total_entry_count_(entry_count), current_entry_count_(0u) {
+  CodedOutputStream coded_out(out_);
+
+  // Write the magic.
+  coded_out.WriteLittleEndian32(kContainerFormatMagic);
+
+  // Write the version.
+  coded_out.WriteLittleEndian32(kContainerFormatVersion);
+
+  // Write the total number of entries.
+  coded_out.WriteLittleEndian32(static_cast<uint32_t>(total_entry_count_));
+
+  if (coded_out.HadError()) {
+    error_ = "failed writing container format header";
+  }
+}
+
+inline static void WritePadding(int padding, CodedOutputStream* out) {
+  if (padding < 4) {
+    const uint32_t zero = 0u;
+    out->WriteRaw(&zero, padding);
+  }
+}
+
+bool ContainerWriter::AddResTableEntry(const pb::ResourceTable& table) {
+  if (current_entry_count_ >= total_entry_count_) {
+    error_ = "too many entries being serialized";
+    return false;
+  }
+  current_entry_count_++;
+
+  CodedOutputStream coded_out(out_);
+
+  // Write the type.
+  coded_out.WriteLittleEndian32(kResTable);
+
+  // Write the aligned size.
+  const ::google::protobuf::uint64 size = table.ByteSize();
+  const int padding = 4 - (size % 4);
+  coded_out.WriteLittleEndian64(size);
+
+  // Write the table.
+  table.SerializeWithCachedSizes(&coded_out);
+
+  // Write the padding.
+  WritePadding(padding, &coded_out);
+
+  if (coded_out.HadError()) {
+    error_ = "failed writing to output";
+    return false;
+  }
+  return true;
+}
+
+bool ContainerWriter::AddResFileEntry(const pb::internal::CompiledFile& file,
+                                      io::KnownSizeInputStream* in) {
+  if (current_entry_count_ >= total_entry_count_) {
+    error_ = "too many entries being serialized";
+    return false;
+  }
+  current_entry_count_++;
+
+  constexpr const static int kResFileEntryHeaderSize = 12;
+
+  CodedOutputStream coded_out(out_);
+
+  // Write the type.
+  coded_out.WriteLittleEndian32(kResFile);
+
+  // Write the aligned size.
+  const ::google::protobuf::uint32 header_size = file.ByteSize();
+  const int header_padding = 4 - (header_size % 4);
+  const ::google::protobuf::uint64 data_size = in->TotalSize();
+  const int data_padding = 4 - (data_size % 4);
+  coded_out.WriteLittleEndian64(kResFileEntryHeaderSize + header_size + header_padding + data_size +
+                                data_padding);
+
+  // Write the res file header size.
+  coded_out.WriteLittleEndian32(header_size);
+
+  // Write the data payload size.
+  coded_out.WriteLittleEndian64(data_size);
+
+  // Write the header.
+  file.SerializeToCodedStream(&coded_out);
+
+  WritePadding(header_padding, &coded_out);
+
+  // Write the data payload. We need to call Trim() since we are going to write to the underlying
+  // ZeroCopyOutputStream.
+  coded_out.Trim();
+
+  // Check at this point if there were any errors.
+  if (coded_out.HadError()) {
+    error_ = "failed writing to output";
+    return false;
+  }
+
+  if (!io::Copy(out_, in)) {
+    if (in->HadError()) {
+      std::ostringstream error;
+      error << "failed reading from input: " << in->GetError();
+      error_ = error.str();
+    } else {
+      error_ = "failed writing to output";
+    }
+    return false;
+  }
+  WritePadding(data_padding, &coded_out);
+
+  if (coded_out.HadError()) {
+    error_ = "failed writing to output";
+    return false;
+  }
+  return true;
+}
+
+bool ContainerWriter::HadError() const {
+  return !error_.empty();
+}
+
+std::string ContainerWriter::GetError() const {
+  return error_;
+}
+
+static bool AlignRead(CodedInputStream* in) {
+  const int padding = 4 - (in->CurrentPosition() % 4);
+  if (padding < 4) {
+    return in->Skip(padding);
+  }
+  return true;
+}
+
+ContainerReaderEntry::ContainerReaderEntry(ContainerReader* reader) : reader_(reader) {
+}
+
+ContainerEntryType ContainerReaderEntry::Type() const {
+  return type_;
+}
+
+bool ContainerReaderEntry::GetResTable(pb::ResourceTable* out_table) {
+  CHECK(type_ == ContainerEntryType::kResTable) << "reading a kResTable when the type is kResFile";
+  if (length_ > std::numeric_limits<int>::max()) {
+    reader_->error_ = StringPrintf("entry length %zu is too large", length_);
+    return false;
+  }
+
+  CodedInputStream& coded_in = reader_->coded_in_;
+
+  const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast<int>(length_));
+  auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); });
+
+  if (!out_table->ParseFromCodedStream(&coded_in)) {
+    reader_->error_ = "failed to parse ResourceTable";
+    return false;
+  }
+  return true;
+}
+
+bool ContainerReaderEntry::GetResFileOffsets(pb::internal::CompiledFile* out_file,
+                                             off64_t* out_offset, size_t* out_len) {
+  CHECK(type_ == ContainerEntryType::kResFile) << "reading a kResFile when the type is kResTable";
+
+  CodedInputStream& coded_in = reader_->coded_in_;
+
+  // Read the ResFile header.
+  ::google::protobuf::uint32 header_length;
+  if (!coded_in.ReadLittleEndian32(&header_length)) {
+    std::ostringstream error;
+    error << "failed to read header length from input: " << reader_->in_->GetError();
+    reader_->error_ = error.str();
+    return false;
+  }
+
+  ::google::protobuf::uint64 data_length;
+  if (!coded_in.ReadLittleEndian64(&data_length)) {
+    std::ostringstream error;
+    error << "failed to read data length from input: " << reader_->in_->GetError();
+    reader_->error_ = error.str();
+    return false;
+  }
+
+  if (header_length > std::numeric_limits<int>::max()) {
+    std::ostringstream error;
+    error << "header length " << header_length << " is too large";
+    reader_->error_ = error.str();
+    return false;
+  }
+
+  if (data_length > std::numeric_limits<size_t>::max()) {
+    std::ostringstream error;
+    error << "data length " << data_length << " is too large";
+    reader_->error_ = error.str();
+    return false;
+  }
+
+  {
+    const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast<int>(header_length));
+    auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); });
+
+    if (!out_file->ParseFromCodedStream(&coded_in)) {
+      reader_->error_ = "failed to parse CompiledFile header";
+      return false;
+    }
+  }
+
+  AlignRead(&coded_in);
+
+  *out_offset = coded_in.CurrentPosition();
+  *out_len = data_length;
+
+  coded_in.Skip(static_cast<int>(data_length));
+  AlignRead(&coded_in);
+  return true;
+}
+
+bool ContainerReaderEntry::HadError() const {
+  return reader_->HadError();
+}
+
+std::string ContainerReaderEntry::GetError() const {
+  return reader_->GetError();
+}
+
+ContainerReader::ContainerReader(io::InputStream* in)
+    : in_(in),
+      adaptor_(in),
+      coded_in_(&adaptor_),
+      total_entry_count_(0u),
+      current_entry_count_(0u),
+      entry_(this) {
+  ::google::protobuf::uint32 magic;
+  if (!coded_in_.ReadLittleEndian32(&magic)) {
+    std::ostringstream error;
+    error << "failed to read magic from input: " << in_->GetError();
+    error_ = error.str();
+    return;
+  }
+
+  if (magic != kContainerFormatMagic) {
+    error_ = "magic value doesn't match AAPT";
+    return;
+  }
+
+  ::google::protobuf::uint32 version;
+  if (!coded_in_.ReadLittleEndian32(&version)) {
+    std::ostringstream error;
+    error << "failed to read version from input: " << in_->GetError();
+    error_ = error.str();
+    return;
+  }
+
+  if (version != kContainerFormatVersion) {
+    error_ = StringPrintf("container version is 0x%08x but AAPT expects version 0x%08x", version,
+                          kContainerFormatVersion);
+    return;
+  }
+
+  ::google::protobuf::uint32 total_entry_count;
+  if (!coded_in_.ReadLittleEndian32(&total_entry_count)) {
+    std::ostringstream error;
+    error << "failed to read entry count from input: " << in_->GetError();
+    error_ = error.str();
+    return;
+  }
+
+  total_entry_count_ = total_entry_count;
+}
+
+ContainerReaderEntry* ContainerReader::Next() {
+  if (current_entry_count_ >= total_entry_count_) {
+    return nullptr;
+  }
+  current_entry_count_++;
+
+  // Ensure the next read is aligned.
+  AlignRead(&coded_in_);
+
+  ::google::protobuf::uint32 entry_type;
+  if (!coded_in_.ReadLittleEndian32(&entry_type)) {
+    std::ostringstream error;
+    error << "failed reading entry type from input: " << in_->GetError();
+    error_ = error.str();
+    return nullptr;
+  }
+
+  ::google::protobuf::uint64 entry_length;
+  if (!coded_in_.ReadLittleEndian64(&entry_length)) {
+    std::ostringstream error;
+    error << "failed reading entry length from input: " << in_->GetError();
+    error_ = error.str();
+    return nullptr;
+  }
+
+  if (entry_type == ContainerEntryType::kResFile || entry_type == ContainerEntryType::kResTable) {
+    entry_.type_ = static_cast<ContainerEntryType>(entry_type);
+  } else {
+    error_ = StringPrintf("entry type 0x%08x is invalid", entry_type);
+    return nullptr;
+  }
+
+  if (entry_length > std::numeric_limits<size_t>::max()) {
+    std::ostringstream error;
+    error << "entry length " << entry_length << " is too large";
+    error_ = error.str();
+    return nullptr;
+  }
+
+  entry_.length_ = entry_length;
+  return &entry_;
+}
+
+bool ContainerReader::HadError() const {
+  return !error_.empty();
+}
+
+std::string ContainerReader::GetError() const {
+  return error_;
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/Container.h b/tools/aapt2/format/Container.h
new file mode 100644
index 0000000..aa5c82c
--- /dev/null
+++ b/tools/aapt2/format/Container.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FORMAT_CONTAINER_H
+#define AAPT_FORMAT_CONTAINER_H
+
+#include <inttypes.h>
+
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/io/zero_copy_stream.h"
+
+#include "Resources.pb.h"
+#include "ResourcesInternal.pb.h"
+#include "io/Io.h"
+#include "io/Util.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+
+enum ContainerEntryType : uint8_t {
+  kResTable = 0x00u,
+  kResFile = 0x01u,
+};
+
+class ContainerWriter {
+ public:
+  explicit ContainerWriter(::google::protobuf::io::ZeroCopyOutputStream* out, size_t entry_count);
+
+  bool AddResTableEntry(const pb::ResourceTable& table);
+  bool AddResFileEntry(const pb::internal::CompiledFile& file, io::KnownSizeInputStream* in);
+  bool HadError() const;
+  std::string GetError() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ContainerWriter);
+
+  ::google::protobuf::io::ZeroCopyOutputStream* out_;
+  size_t total_entry_count_;
+  size_t current_entry_count_;
+  std::string error_;
+};
+
+class ContainerReader;
+
+class ContainerReaderEntry {
+ public:
+  ContainerEntryType Type() const;
+
+  bool GetResTable(pb::ResourceTable* out_table);
+  bool GetResFileOffsets(pb::internal::CompiledFile* out_file, off64_t* out_offset,
+                         size_t* out_len);
+
+  bool HadError() const;
+  std::string GetError() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ContainerReaderEntry);
+
+  friend class ContainerReader;
+
+  explicit ContainerReaderEntry(ContainerReader* reader);
+
+  ContainerReader* reader_;
+  ContainerEntryType type_ = ContainerEntryType::kResTable;
+  size_t length_ = 0u;
+};
+
+class ContainerReader {
+ public:
+  explicit ContainerReader(io::InputStream* in);
+
+  ContainerReaderEntry* Next();
+
+  bool HadError() const;
+  std::string GetError() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ContainerReader);
+
+  friend class ContainerReaderEntry;
+
+  io::InputStream* in_;
+  io::ZeroCopyInputAdaptor adaptor_;
+  ::google::protobuf::io::CodedInputStream coded_in_;
+  size_t total_entry_count_;
+  size_t current_entry_count_;
+  ContainerReaderEntry entry_;
+  std::string error_;
+};
+
+}  // namespace aapt
+
+#endif /* AAPT_FORMAT_CONTAINER_H */
diff --git a/tools/aapt2/format/Container_test.cpp b/tools/aapt2/format/Container_test.cpp
new file mode 100644
index 0000000..dc81a3a
--- /dev/null
+++ b/tools/aapt2/format/Container_test.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 "format/Container.h"
+
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+
+#include "io/StringStream.h"
+#include "test/Test.h"
+
+using ::google::protobuf::io::StringOutputStream;
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::testing::IsNull;
+using ::testing::NotNull;
+using ::testing::StrEq;
+
+namespace aapt {
+
+TEST(ContainerTest, SerializeCompiledFile) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+  const std::string expected_data = "123";
+
+  std::string output_str;
+  {
+    StringOutputStream out_stream(&output_str);
+    ContainerWriter writer(&out_stream, 2u);
+    ASSERT_FALSE(writer.HadError());
+
+    pb::internal::CompiledFile pb_compiled_file;
+    pb_compiled_file.set_resource_name("android:layout/main.xml");
+    pb_compiled_file.set_type(pb::FileReference::PROTO_XML);
+    pb_compiled_file.set_source_path("res/layout/main.xml");
+    io::StringInputStream data(expected_data);
+    ASSERT_TRUE(writer.AddResFileEntry(pb_compiled_file, &data));
+
+    pb::ResourceTable pb_table;
+    pb::Package* pb_pkg = pb_table.add_package();
+    pb_pkg->set_package_name("android");
+    pb_pkg->mutable_package_id()->set_id(0x01u);
+    ASSERT_TRUE(writer.AddResTableEntry(pb_table));
+
+    ASSERT_FALSE(writer.HadError());
+  }
+
+  io::StringInputStream input(output_str);
+  ContainerReader reader(&input);
+  ASSERT_FALSE(reader.HadError());
+
+  ContainerReaderEntry* entry = reader.Next();
+  ASSERT_THAT(entry, NotNull());
+  ASSERT_THAT(entry->Type(), Eq(ContainerEntryType::kResFile));
+
+  pb::internal::CompiledFile pb_new_file;
+  off64_t offset;
+  size_t len;
+  ASSERT_TRUE(entry->GetResFileOffsets(&pb_new_file, &offset, &len)) << entry->GetError();
+  EXPECT_THAT(offset & 0x03, Eq(0u));
+  EXPECT_THAT(output_str.substr(static_cast<size_t>(offset), len), StrEq(expected_data));
+
+  entry = reader.Next();
+  ASSERT_THAT(entry, NotNull());
+  ASSERT_THAT(entry->Type(), Eq(ContainerEntryType::kResTable));
+
+  pb::ResourceTable pb_new_table;
+  ASSERT_TRUE(entry->GetResTable(&pb_new_table));
+  ASSERT_THAT(pb_new_table.package_size(), Eq(1));
+  EXPECT_THAT(pb_new_table.package(0).package_name(), StrEq("android"));
+  EXPECT_THAT(pb_new_table.package(0).package_id().id(), Eq(0x01u));
+
+  EXPECT_THAT(reader.Next(), IsNull());
+  EXPECT_FALSE(reader.HadError());
+  EXPECT_THAT(reader.GetError(), IsEmpty());
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp
index 2456c3d..345cc95 100644
--- a/tools/aapt2/format/binary/XmlFlattener.cpp
+++ b/tools/aapt2/format/binary/XmlFlattener.cpp
@@ -56,9 +56,9 @@
   return false;
 }
 
-class XmlFlattenerVisitor : public xml::Visitor {
+class XmlFlattenerVisitor : public xml::ConstVisitor {
  public:
-  using xml::Visitor::Visit;
+  using xml::ConstVisitor::Visit;
 
   StringPool pool;
   std::map<uint8_t, StringPool> package_pools;
@@ -74,7 +74,7 @@
       : buffer_(buffer), options_(options) {
   }
 
-  void Visit(xml::Text* node) override {
+  void Visit(const xml::Text* node) override {
     if (util::TrimWhitespace(node->text).empty()) {
       // Skip whitespace only text nodes.
       return;
@@ -95,7 +95,7 @@
     writer.Finish();
   }
 
-  void Visit(xml::Element* node) override {
+  void Visit(const xml::Element* node) override {
     for (const xml::NamespaceDecl& decl : node->namespace_decls) {
       // Skip dedicated tools namespace.
       if (decl.uri != xml::kSchemaTools) {
@@ -125,7 +125,7 @@
       start_writer.Finish();
     }
 
-    xml::Visitor::Visit(node);
+    xml::ConstVisitor::Visit(node);
 
     {
       ChunkWriter end_writer(buffer_);
@@ -182,12 +182,13 @@
     writer.Finish();
   }
 
-  void WriteAttributes(xml::Element* node, ResXMLTree_attrExt* flat_elem, ChunkWriter* writer) {
+  void WriteAttributes(const xml::Element* node, ResXMLTree_attrExt* flat_elem,
+                       ChunkWriter* writer) {
     filtered_attrs_.clear();
     filtered_attrs_.reserve(node->attributes.size());
 
     // Filter the attributes.
-    for (xml::Attribute& attr : node->attributes) {
+    for (const xml::Attribute& attr : node->attributes) {
       if (attr.namespace_uri != xml::kSchemaTools) {
         filtered_attrs_.push_back(&attr);
       }
@@ -282,12 +283,12 @@
   XmlFlattenerOptions options_;
 
   // Scratch vector to filter attributes. We avoid allocations making this a member.
-  std::vector<xml::Attribute*> filtered_attrs_;
+  std::vector<const xml::Attribute*> filtered_attrs_;
 };
 
 }  // namespace
 
-bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) {
+bool XmlFlattener::Flatten(IAaptContext* context, const xml::Node* node) {
   BigBuffer node_buffer(1024);
   XmlFlattenerVisitor visitor(&node_buffer, options_);
   node->Accept(&visitor);
@@ -341,7 +342,7 @@
   return true;
 }
 
-bool XmlFlattener::Consume(IAaptContext* context, xml::XmlResource* resource) {
+bool XmlFlattener::Consume(IAaptContext* context, const xml::XmlResource* resource) {
   if (!resource->root) {
     return false;
   }
diff --git a/tools/aapt2/format/binary/XmlFlattener.h b/tools/aapt2/format/binary/XmlFlattener.h
index 8db2281..1f9e777 100644
--- a/tools/aapt2/format/binary/XmlFlattener.h
+++ b/tools/aapt2/format/binary/XmlFlattener.h
@@ -34,18 +34,18 @@
   bool use_utf16 = false;
 };
 
-class XmlFlattener : public IXmlResourceConsumer {
+class XmlFlattener {
  public:
   XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options)
       : buffer_(buffer), options_(options) {
   }
 
-  bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
+  bool Consume(IAaptContext* context, const xml::XmlResource* resource);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(XmlFlattener);
 
-  bool Flatten(IAaptContext* context, xml::Node* node);
+  bool Flatten(IAaptContext* context, const xml::Node* node);
 
   BigBuffer* buffer_;
   XmlFlattenerOptions options_;
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index c14f09a..86bd865 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -26,7 +26,6 @@
 #include "ValueVisitor.h"
 
 using ::android::ResStringPool;
-using ::google::protobuf::io::CodedInputStream;
 
 namespace aapt {
 
@@ -391,8 +390,15 @@
     }
 
     ResourceTableType* type = pkg->FindOrCreateType(*res_type);
+    if (pb_type.has_type_id()) {
+      type->id = static_cast<uint8_t>(pb_type.type_id().id());
+    }
+
     for (const pb::Entry& pb_entry : pb_type.entry()) {
       ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name());
+      if (pb_entry.has_entry_id()) {
+        entry->id = static_cast<uint16_t>(pb_entry.entry_id().id());
+      }
 
       // Deserialize the symbol status (public/private with source and comments).
       if (pb_entry.has_symbol_status()) {
@@ -406,21 +412,11 @@
 
         const SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility());
         entry->symbol_status.state = visibility;
-
         if (visibility == SymbolState::kPublic) {
-          // This is a public symbol, we must encode the ID now if there is one.
-          if (pb_entry.has_entry_id()) {
-            entry->id = static_cast<uint16_t>(pb_entry.entry_id().id());
-          }
-
-          if (type->symbol_status.state != SymbolState::kPublic) {
-            // If the type has not been made public, do so now.
-            type->symbol_status.state = SymbolState::kPublic;
-            if (pb_type.has_type_id()) {
-              type->id = static_cast<uint8_t>(pb_type.type_id().id());
-            }
-          }
+          // Propagate the public visibility up to the Type.
+          type->symbol_status.state = SymbolState::kPublic;
         } else if (visibility == SymbolState::kPrivate) {
+          // Only propagate if no previous state was assigned.
           if (type->symbol_status.state == SymbolState::kUndefined) {
             type->symbol_status.state = SymbolState::kPrivate;
           }
@@ -485,6 +481,19 @@
   return true;
 }
 
+static ResourceFile::Type DeserializeFileReferenceTypeFromPb(const pb::FileReference::Type& type) {
+  switch (type) {
+    case pb::FileReference::BINARY_XML:
+      return ResourceFile::Type::kBinaryXml;
+    case pb::FileReference::PROTO_XML:
+      return ResourceFile::Type::kProtoXml;
+    case pb::FileReference::PNG:
+      return ResourceFile::Type::kPng;
+    default:
+      return ResourceFile::Type::kUnknown;
+  }
+}
+
 bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file,
                                    ResourceFile* out_file, std::string* out_error) {
   ResourceNameRef name_ref;
@@ -497,6 +506,7 @@
 
   out_file->name = name_ref.ToResourceName();
   out_file->source.path = pb_file.source_path();
+  out_file->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
 
   std::string config_error;
   if (!DeserializeConfigFromPb(pb_file.config(), &out_file->config, &config_error)) {
@@ -759,8 +769,12 @@
     } break;
 
     case pb::Item::kFile: {
-      return util::make_unique<FileReference>(value_pool->MakeRef(
-          pb_item.file().path(), StringPool::Context(StringPool::Context::kHighPriority, config)));
+      const pb::FileReference& pb_file = pb_item.file();
+      std::unique_ptr<FileReference> file_ref =
+          util::make_unique<FileReference>(value_pool->MakeRef(
+              pb_file.path(), StringPool::Context(StringPool::Context::kHighPriority, config)));
+      file_ref->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
+      return std::move(file_ref);
     } break;
 
     default:
@@ -847,72 +861,4 @@
   return true;
 }
 
-CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size)
-    : in_(static_cast<const uint8_t*>(data), size) {
-}
-
-void CompiledFileInputStream::EnsureAlignedRead() {
-  const int overflow = in_.CurrentPosition() % 4;
-  if (overflow > 0) {
-    // Reads are always 4 byte aligned.
-    in_.Skip(4 - overflow);
-  }
-}
-
-bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* out_val) {
-  EnsureAlignedRead();
-  return in_.ReadLittleEndian32(out_val);
-}
-
-bool CompiledFileInputStream::ReadCompiledFile(pb::internal::CompiledFile* out_val) {
-  EnsureAlignedRead();
-
-  google::protobuf::uint64 pb_size = 0u;
-  if (!in_.ReadLittleEndian64(&pb_size)) {
-    return false;
-  }
-
-  CodedInputStream::Limit l = in_.PushLimit(static_cast<int>(pb_size));
-
-  // Check that we haven't tried to read past the end.
-  if (static_cast<uint64_t>(in_.BytesUntilLimit()) != pb_size) {
-    in_.PopLimit(l);
-    in_.PushLimit(0);
-    return false;
-  }
-
-  if (!out_val->ParsePartialFromCodedStream(&in_)) {
-    in_.PopLimit(l);
-    in_.PushLimit(0);
-    return false;
-  }
-
-  in_.PopLimit(l);
-  return true;
-}
-
-bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset, uint64_t* out_len) {
-  EnsureAlignedRead();
-
-  google::protobuf::uint64 pb_size = 0u;
-  if (!in_.ReadLittleEndian64(&pb_size)) {
-    return false;
-  }
-
-  // Check that we aren't trying to read past the end.
-  if (pb_size > static_cast<uint64_t>(in_.BytesUntilLimit())) {
-    in_.PushLimit(0);
-    return false;
-  }
-
-  uint64_t offset = static_cast<uint64_t>(in_.CurrentPosition());
-  if (!in_.Skip(pb_size)) {
-    return false;
-  }
-
-  *out_offset = offset;
-  *out_len = pb_size;
-  return true;
-}
-
 }  // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.h b/tools/aapt2/format/proto/ProtoDeserialize.h
index c8a7199..7dc54f2 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.h
+++ b/tools/aapt2/format/proto/ProtoDeserialize.h
@@ -19,7 +19,6 @@
 
 #include "android-base/macros.h"
 #include "androidfw/ResourceTypes.h"
-#include "google/protobuf/io/coded_stream.h"
 
 #include "ConfigDescription.h"
 #include "Configuration.pb.h"
@@ -57,22 +56,6 @@
 bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file,
                                    ResourceFile* out_file, std::string* out_error);
 
-class CompiledFileInputStream {
- public:
-  explicit CompiledFileInputStream(const void* data, size_t size);
-
-  bool ReadLittleEndian32(uint32_t* outVal);
-  bool ReadCompiledFile(pb::internal::CompiledFile* outVal);
-  bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen);
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream);
-
-  void EnsureAlignedRead();
-
-  ::google::protobuf::io::CodedInputStream in_;
-};
-
 }  // namespace aapt
 
 #endif /* AAPT_FORMAT_PROTO_PROTODESERIALIZE_H */
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index c0d3614..1d184fe 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -16,14 +16,9 @@
 
 #include "format/proto/ProtoSerialize.h"
 
-#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
-
 #include "ValueVisitor.h"
 #include "util/BigBuffer.h"
 
-using ::google::protobuf::io::CodedOutputStream;
-using ::google::protobuf::io::ZeroCopyOutputStream;
-
 namespace aapt {
 
 void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool) {
@@ -366,6 +361,19 @@
   return pb::Plural_Arity_OTHER;
 }
 
+static pb::FileReference::Type SerializeFileReferenceTypeToPb(const ResourceFile::Type& type) {
+  switch (type) {
+    case ResourceFile::Type::kBinaryXml:
+      return pb::FileReference::BINARY_XML;
+    case ResourceFile::Type::kProtoXml:
+      return pb::FileReference::PROTO_XML;
+    case ResourceFile::Type::kPng:
+      return pb::FileReference::PNG;
+    default:
+      return pb::FileReference::UNKNOWN;
+  }
+}
+
 namespace {
 
 class ValueSerializer : public ConstValueVisitor {
@@ -400,7 +408,9 @@
   }
 
   void Visit(const FileReference* file) override {
-    out_value_->mutable_item()->mutable_file()->set_path(*file->path);
+    pb::FileReference* pb_file = out_value_->mutable_item()->mutable_file();
+    pb_file->set_path(*file->path);
+    pb_file->set_type(SerializeFileReferenceTypeToPb(file->type));
   }
 
   void Visit(const Id* /*id*/) override {
@@ -515,6 +525,7 @@
 void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) {
   out_file->set_resource_name(file.name.ToString());
   out_file->set_source_path(file.source.path);
+  out_file->set_type(SerializeFileReferenceTypeToPb(file.type));
   SerializeConfig(file.config, out_file->mutable_config());
 
   for (const SourcedResourceName& exported : file.exported_symbols) {
@@ -579,44 +590,4 @@
   SerializeXmlToPb(*resource.root, out_node);
 }
 
-CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) : out_(out) {
-}
-
-void CompiledFileOutputStream::EnsureAlignedWrite() {
-  const int overflow = out_.ByteCount() % 4;
-  if (overflow > 0) {
-    uint32_t zero = 0u;
-    out_.WriteRaw(&zero, 4 - overflow);
-  }
-}
-
-void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) {
-  EnsureAlignedWrite();
-  out_.WriteLittleEndian32(val);
-}
-
-void CompiledFileOutputStream::WriteCompiledFile(const pb::internal::CompiledFile& compiled_file) {
-  EnsureAlignedWrite();
-  out_.WriteLittleEndian64(static_cast<uint64_t>(compiled_file.ByteSize()));
-  compiled_file.SerializeWithCachedSizes(&out_);
-}
-
-void CompiledFileOutputStream::WriteData(const BigBuffer& buffer) {
-  EnsureAlignedWrite();
-  out_.WriteLittleEndian64(static_cast<uint64_t>(buffer.size()));
-  for (const BigBuffer::Block& block : buffer) {
-    out_.WriteRaw(block.buffer.get(), block.size);
-  }
-}
-
-void CompiledFileOutputStream::WriteData(const void* data, size_t len) {
-  EnsureAlignedWrite();
-  out_.WriteLittleEndian64(static_cast<uint64_t>(len));
-  out_.WriteRaw(data, len);
-}
-
-bool CompiledFileOutputStream::HadError() {
-  return out_.HadError();
-}
-
 }  // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h
index 1694b16..95dd413 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.h
+++ b/tools/aapt2/format/proto/ProtoSerialize.h
@@ -18,7 +18,6 @@
 #define AAPT_FORMAT_PROTO_PROTOSERIALIZE_H
 
 #include "android-base/macros.h"
-#include "google/protobuf/io/coded_stream.h"
 
 #include "ConfigDescription.h"
 #include "Configuration.pb.h"
@@ -29,14 +28,6 @@
 #include "StringPool.h"
 #include "xml/XmlDom.h"
 
-namespace google {
-namespace protobuf {
-namespace io {
-class ZeroCopyOutputStream;
-}  // namespace io
-}  // namespace protobuf
-}  // namespace google
-
 namespace aapt {
 
 // Serializes a Value to its protobuf representation. An optional StringPool will hold the
@@ -66,24 +57,6 @@
 // Serializes a ResourceFile into its protobuf representation.
 void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file);
 
-class CompiledFileOutputStream {
- public:
-  explicit CompiledFileOutputStream(::google::protobuf::io::ZeroCopyOutputStream* out);
-
-  void WriteLittleEndian32(uint32_t value);
-  void WriteCompiledFile(const pb::internal::CompiledFile& compiledFile);
-  void WriteData(const BigBuffer& buffer);
-  void WriteData(const void* data, size_t len);
-  bool HadError();
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream);
-
-  void EnsureAlignedWrite();
-
-  ::google::protobuf::io::CodedOutputStream out_;
-};
-
 }  // namespace aapt
 
 #endif /* AAPT_FORMAT_PROTO_PROTOSERIALIZE_H */
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 2154d5a..8efac8a 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -16,14 +16,11 @@
 
 #include "format/proto/ProtoSerialize.h"
 
-#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
-
 #include "ResourceUtils.h"
 #include "format/proto/ProtoDeserialize.h"
 #include "test/Test.h"
 
 using ::android::StringPiece;
-using ::google::protobuf::io::StringOutputStream;
 using ::testing::Eq;
 using ::testing::IsEmpty;
 using ::testing::NotNull;
@@ -137,113 +134,6 @@
   EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u));
 }
 
-TEST(ProtoSerializeTest, SerializeFileHeader) {
-  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
-
-  ResourceFile f;
-  f.config = test::ParseConfigOrDie("hdpi-v9");
-  f.name = test::ParseNameOrDie("com.app.a:layout/main");
-  f.source.path = "res/layout-hdpi-v9/main.xml";
-  f.exported_symbols.push_back(SourcedResourceName{test::ParseNameOrDie("id/unchecked"), 23u});
-
-  const std::string expected_data1 = "123";
-  const std::string expected_data2 = "1234";
-
-  std::string output_str;
-  {
-    pb::internal::CompiledFile pb_f1, pb_f2;
-    SerializeCompiledFileToPb(f, &pb_f1);
-
-    f.name.entry = "__" + f.name.entry + "$0";
-    SerializeCompiledFileToPb(f, &pb_f2);
-
-    StringOutputStream out_stream(&output_str);
-    CompiledFileOutputStream out_file_stream(&out_stream);
-    out_file_stream.WriteLittleEndian32(2);
-    out_file_stream.WriteCompiledFile(pb_f1);
-    out_file_stream.WriteData(expected_data1.data(), expected_data1.size());
-    out_file_stream.WriteCompiledFile(pb_f2);
-    out_file_stream.WriteData(expected_data2.data(), expected_data2.size());
-    ASSERT_FALSE(out_file_stream.HadError());
-  }
-
-  CompiledFileInputStream in_file_stream(output_str.data(), output_str.size());
-  uint32_t num_files = 0;
-  ASSERT_TRUE(in_file_stream.ReadLittleEndian32(&num_files));
-  ASSERT_EQ(2u, num_files);
-
-  // Read the first compiled file.
-
-  pb::internal::CompiledFile new_pb_f1;
-  ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_f1));
-
-  ResourceFile new_f1;
-  std::string error;
-  ASSERT_TRUE(DeserializeCompiledFileFromPb(new_pb_f1, &new_f1, &error));
-  EXPECT_THAT(error, IsEmpty());
-
-  uint64_t offset, len;
-  ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len));
-
-  std::string actual_data(output_str.data() + offset, len);
-  EXPECT_EQ(expected_data1, actual_data);
-
-  // Expect the data to be aligned.
-  EXPECT_EQ(0u, offset & 0x03);
-
-  ASSERT_EQ(1u, new_f1.exported_symbols.size());
-  EXPECT_EQ(test::ParseNameOrDie("id/unchecked"), new_f1.exported_symbols[0].name);
-
-  // Read the second compiled file.
-
-  pb::internal::CompiledFile new_pb_f2;
-  ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_f2));
-
-  ResourceFile new_f2;
-  ASSERT_TRUE(DeserializeCompiledFileFromPb(new_pb_f2, &new_f2, &error));
-  EXPECT_THAT(error, IsEmpty());
-
-  ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len));
-
-  actual_data = std::string(output_str.data() + offset, len);
-  EXPECT_EQ(expected_data2, actual_data);
-
-  // Expect the data to be aligned.
-  EXPECT_EQ(0u, offset & 0x03);
-}
-
-TEST(ProtoSerializeTest, DeserializeCorruptHeaderSafely) {
-  ResourceFile f;
-  pb::internal::CompiledFile pb_file;
-  SerializeCompiledFileToPb(f, &pb_file);
-
-  const std::string expected_data = "1234";
-
-  std::string output_str;
-  {
-    StringOutputStream out_stream(&output_str);
-    CompiledFileOutputStream out_file_stream(&out_stream);
-    out_file_stream.WriteLittleEndian32(1);
-    out_file_stream.WriteCompiledFile(pb_file);
-    out_file_stream.WriteData(expected_data.data(), expected_data.size());
-    ASSERT_FALSE(out_file_stream.HadError());
-  }
-
-  output_str[4] = 0xff;
-
-  CompiledFileInputStream in_file_stream(output_str.data(), output_str.size());
-
-  uint32_t num_files = 0;
-  EXPECT_TRUE(in_file_stream.ReadLittleEndian32(&num_files));
-  EXPECT_EQ(1u, num_files);
-
-  pb::internal::CompiledFile new_pb_file;
-  EXPECT_FALSE(in_file_stream.ReadCompiledFile(&new_pb_file));
-
-  uint64_t offset, len;
-  EXPECT_FALSE(in_file_stream.ReadDataMetaData(&offset, &len));
-}
-
 TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
   xml::Element element;
   element.line_number = 22;
diff --git a/tools/aapt2/formats.md b/tools/aapt2/formats.md
new file mode 100644
index 0000000..bb31a00
--- /dev/null
+++ b/tools/aapt2/formats.md
@@ -0,0 +1,44 @@
+# AAPT2 On-Disk Formats
+- AAPT2 Container Format (extension `.apc`)
+- AAPT2 Static Library Format (extension `.sapk`)
+
+## AAPT2 Container Format (extension `.apc`)
+The APC format (AAPT2 Container Format) is generated by AAPT2 during the compile phase and
+consumed by the AAPT2 link phase. It is a simple container format for storing compiled PNGs,
+binary and protobuf XML, and intermediate protobuf resource tables. It also stores all associated
+meta-data from the compile phase.
+
+### Format
+The file starts with a simple header. All multi-byte fields are little-endian.
+
+| Size (in bytes) | Field         | Description                                          |
+|:----------------|:--------------|:-----------------------------------------------------|
+| `4`             | `magic`       | The magic bytes must equal `'AAPT'` or `0x54504141`. |
+| `4`             | `version`     | The version of the container format.                 |
+| `4`             | `entry_count` | The number of entries in this container.             |
+
+This is followed by `entry_count` of the following data structure. It must be aligned on a 32-bit
+boundary, so if a previous entry ends unaligned, padding must be inserted.
+
+| Size (in bytes) | Field          | Description                                                                                               |
+|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------|
+| `4`             | `entry_type`   | The type of the entry. This can be one of two types: `RES_TABLE (0x00000000)` or `RES_FILE (0x00000001)`. |
+| `8`             | `entry_length` | The length of the data that follows.                                                                      |
+| `entry_length`  | `data`         | The payload. The contents of this varies based on the `entry_type`.                                       |
+
+If the `entry_type` is equal to `RES_TABLE (0x00000000)`, the `data` field contains a serialized
+[aapt.pb.ResourceTable](Resources.proto).
+
+If the `entry_type` is equal to `RES_FILE (0x00000001)`, the `data` field contains the following:
+
+
+| Size (in bytes) | Field          | Description                                                                                               |
+|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------|
+| `4`             | `header_size`  | The size of the `header` field.                                                                 |
+| `8`             | `data_size`    | The size of the `data` field.                                                                   |
+| `header_size`   | `header`       | The serialized Protobuf message [aapt.pb.internal.CompiledFile](ResourcesInternal.proto).       |
+| `x`             | `padding`      | Up to 4 bytes of zeros, if padding is necessary to align the `data` field on a 32-bit boundary. |
+| `data_size`     | `data`         | The payload, which is determined by the `type` field in the `aapt.pb.internal.CompiledFile`. This can be a PNG file, binary XML, or [aapt.pb.XmlNode](Resources.proto). |
+
+## AAPT2 Static Library Format (extension `.sapk`)
+
diff --git a/tools/aapt2/io/BigBufferOutputStream.h b/tools/aapt2/io/BigBufferOutputStream.h
deleted file mode 100644
index 95113bc..0000000
--- a/tools/aapt2/io/BigBufferOutputStream.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
-#define AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
-
-#include "io/Io.h"
-#include "util/BigBuffer.h"
-
-namespace aapt {
-namespace io {
-
-class BigBufferOutputStream : public OutputStream {
- public:
-  inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {}
-  virtual ~BigBufferOutputStream() = default;
-
-  bool Next(void** data, size_t* size) override;
-
-  void BackUp(size_t count) override;
-
-  size_t ByteCount() const override;
-
-  bool HadError() const override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
-
-  BigBuffer* buffer_;
-};
-
-}  // namespace io
-}  // namespace aapt
-
-#endif  // AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
diff --git a/tools/aapt2/io/BigBufferStreams.cpp b/tools/aapt2/io/BigBufferStream.cpp
similarity index 74%
rename from tools/aapt2/io/BigBufferStreams.cpp
rename to tools/aapt2/io/BigBufferStream.cpp
index eb99033..9704caa 100644
--- a/tools/aapt2/io/BigBufferStreams.cpp
+++ b/tools/aapt2/io/BigBufferStream.cpp
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#include "io/BigBufferInputStream.h"
-#include "io/BigBufferOutputStream.h"
+#include "io/BigBufferStream.h"
 
 namespace aapt {
 namespace io {
@@ -54,7 +53,9 @@
   }
 }
 
-bool BigBufferInputStream::CanRewind() const { return true; }
+bool BigBufferInputStream::CanRewind() const {
+  return true;
+}
 
 bool BigBufferInputStream::Rewind() {
   iter_ = buffer_->begin();
@@ -63,9 +64,17 @@
   return true;
 }
 
-size_t BigBufferInputStream::ByteCount() const { return bytes_read_; }
+size_t BigBufferInputStream::ByteCount() const {
+  return bytes_read_;
+}
 
-bool BigBufferInputStream::HadError() const { return false; }
+bool BigBufferInputStream::HadError() const {
+  return false;
+}
+
+size_t BigBufferInputStream::TotalSize() const {
+  return buffer_->size();
+}
 
 //
 // BigBufferOutputStream
@@ -76,11 +85,17 @@
   return true;
 }
 
-void BigBufferOutputStream::BackUp(size_t count) { buffer_->BackUp(count); }
+void BigBufferOutputStream::BackUp(size_t count) {
+  buffer_->BackUp(count);
+}
 
-size_t BigBufferOutputStream::ByteCount() const { return buffer_->size(); }
+size_t BigBufferOutputStream::ByteCount() const {
+  return buffer_->size();
+}
 
-bool BigBufferOutputStream::HadError() const { return false; }
+bool BigBufferOutputStream::HadError() const {
+  return false;
+}
 
 }  // namespace io
 }  // namespace aapt
diff --git a/tools/aapt2/io/BigBufferInputStream.h b/tools/aapt2/io/BigBufferStream.h
similarity index 64%
rename from tools/aapt2/io/BigBufferInputStream.h
rename to tools/aapt2/io/BigBufferStream.h
index 92612c7..8b5c8b8 100644
--- a/tools/aapt2/io/BigBufferInputStream.h
+++ b/tools/aapt2/io/BigBufferStream.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef AAPT_IO_BIGBUFFERINPUTSTREAM_H
-#define AAPT_IO_BIGBUFFERINPUTSTREAM_H
+#ifndef AAPT_IO_BIGBUFFERSTREAM_H
+#define AAPT_IO_BIGBUFFERSTREAM_H
 
 #include "io/Io.h"
 #include "util/BigBuffer.h"
@@ -23,10 +23,11 @@
 namespace aapt {
 namespace io {
 
-class BigBufferInputStream : public InputStream {
+class BigBufferInputStream : public KnownSizeInputStream {
  public:
   inline explicit BigBufferInputStream(const BigBuffer* buffer)
-      : buffer_(buffer), iter_(buffer->begin()) {}
+      : buffer_(buffer), iter_(buffer->begin()) {
+  }
   virtual ~BigBufferInputStream() = default;
 
   bool Next(const void** data, size_t* size) override;
@@ -41,6 +42,8 @@
 
   bool HadError() const override;
 
+  size_t TotalSize() const override;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
 
@@ -50,7 +53,27 @@
   size_t bytes_read_ = 0;
 };
 
+class BigBufferOutputStream : public OutputStream {
+ public:
+  inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {
+  }
+  virtual ~BigBufferOutputStream() = default;
+
+  bool Next(void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  size_t ByteCount() const override;
+
+  bool HadError() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
+
+  BigBuffer* buffer_;
+};
+
 }  // namespace io
 }  // namespace aapt
 
-#endif  // AAPT_IO_BIGBUFFERINPUTSTREAM_H
+#endif  // AAPT_IO_BIGBUFFERSTREAM_H
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index 09dc7ea..db91a77 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -28,12 +28,16 @@
 namespace io {
 
 // Interface for a block of contiguous memory. An instance of this interface owns the data.
-class IData : public InputStream {
+class IData : public KnownSizeInputStream {
  public:
   virtual ~IData() = default;
 
   virtual const void* data() const = 0;
   virtual size_t size() const = 0;
+
+  virtual size_t TotalSize() const override {
+    return size();
+  }
 };
 
 class DataSegment : public IData {
diff --git a/tools/aapt2/io/File.cpp b/tools/aapt2/io/File.cpp
index ee73728..b4f1ff3 100644
--- a/tools/aapt2/io/File.cpp
+++ b/tools/aapt2/io/File.cpp
@@ -39,5 +39,9 @@
   return {};
 }
 
+std::unique_ptr<io::InputStream> FileSegment::OpenInputStream() {
+  return OpenAsData();
+}
+
 }  // namespace io
 }  // namespace aapt
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 7ef6d88..f06e28c 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -43,6 +43,8 @@
   // Returns nullptr on failure.
   virtual std::unique_ptr<IData> OpenAsData() = 0;
 
+  virtual std::unique_ptr<io::InputStream> OpenInputStream() = 0;
+
   // Returns the source of this file. This is for presentation to the user and
   // may not be a valid file system path (for example, it may contain a '@' sign to separate
   // the files within a ZIP archive from the path to the containing ZIP archive.
@@ -71,8 +73,11 @@
       : file_(file), offset_(offset), len_(len) {}
 
   std::unique_ptr<IData> OpenAsData() override;
+  std::unique_ptr<io::InputStream> OpenInputStream() override;
 
-  const Source& GetSource() const override { return file_->GetSource(); }
+  const Source& GetSource() const override {
+    return file_->GetSource();
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(FileSegment);
diff --git a/tools/aapt2/io/FileInputStream.cpp b/tools/aapt2/io/FileInputStream.cpp
deleted file mode 100644
index 07dbb5a..0000000
--- a/tools/aapt2/io/FileInputStream.cpp
+++ /dev/null
@@ -1,102 +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.
- */
-
-#include "io/FileInputStream.h"
-
-#include <errno.h>   // for errno
-#include <fcntl.h>   // for O_RDONLY
-#include <unistd.h>  // for read
-
-#include "android-base/errors.h"
-#include "android-base/file.h"  // for O_BINARY
-#include "android-base/macros.h"
-#include "android-base/utf8.h"
-
-using ::android::base::SystemErrorCodeToString;
-
-namespace aapt {
-namespace io {
-
-FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
-    : FileInputStream(::android::base::utf8::open(path.c_str(), O_RDONLY | O_BINARY),
-                      buffer_capacity) {
-}
-
-FileInputStream::FileInputStream(int fd, size_t buffer_capacity)
-    : fd_(fd),
-      buffer_capacity_(buffer_capacity),
-      buffer_offset_(0u),
-      buffer_size_(0u),
-      total_byte_count_(0u) {
-  if (fd_ == -1) {
-    error_ = SystemErrorCodeToString(errno);
-  } else {
-    buffer_.reset(new uint8_t[buffer_capacity_]);
-  }
-}
-
-bool FileInputStream::Next(const void** data, size_t* size) {
-  if (HadError()) {
-    return false;
-  }
-
-  // Deal with any remaining bytes after BackUp was called.
-  if (buffer_offset_ != buffer_size_) {
-    *data = buffer_.get() + buffer_offset_;
-    *size = buffer_size_ - buffer_offset_;
-    total_byte_count_ += buffer_size_ - buffer_offset_;
-    buffer_offset_ = buffer_size_;
-    return true;
-  }
-
-  ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_));
-  if (n < 0) {
-    error_ = SystemErrorCodeToString(errno);
-    fd_.reset();
-    return false;
-  }
-
-  buffer_size_ = static_cast<size_t>(n);
-  buffer_offset_ = buffer_size_;
-  total_byte_count_ += buffer_size_;
-
-  *data = buffer_.get();
-  *size = buffer_size_;
-  return buffer_size_ != 0u;
-}
-
-void FileInputStream::BackUp(size_t count) {
-  if (count > buffer_offset_) {
-    count = buffer_offset_;
-  }
-  buffer_offset_ -= count;
-  total_byte_count_ -= count;
-}
-
-size_t FileInputStream::ByteCount() const {
-  return total_byte_count_;
-}
-
-bool FileInputStream::HadError() const {
-  return !error_.empty();
-}
-
-std::string FileInputStream::GetError() const {
-  return error_;
-}
-
-}  // namespace io
-}  // namespace aapt
diff --git a/tools/aapt2/io/FileStream.cpp b/tools/aapt2/io/FileStream.cpp
new file mode 100644
index 0000000..2f7a4b3
--- /dev/null
+++ b/tools/aapt2/io/FileStream.cpp
@@ -0,0 +1,180 @@
+/*
+ * 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 "io/FileStream.h"
+
+#include <errno.h>   // for errno
+#include <fcntl.h>   // for O_RDONLY
+#include <unistd.h>  // for read
+
+#include "android-base/errors.h"
+#include "android-base/file.h"  // for O_BINARY
+#include "android-base/macros.h"
+#include "android-base/utf8.h"
+
+using ::android::base::SystemErrorCodeToString;
+
+namespace aapt {
+namespace io {
+
+FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
+    : FileInputStream(::android::base::utf8::open(path.c_str(), O_RDONLY | O_BINARY),
+                      buffer_capacity) {
+}
+
+FileInputStream::FileInputStream(int fd, size_t buffer_capacity)
+    : fd_(fd),
+      buffer_capacity_(buffer_capacity),
+      buffer_offset_(0u),
+      buffer_size_(0u),
+      total_byte_count_(0u) {
+  if (fd_ == -1) {
+    error_ = SystemErrorCodeToString(errno);
+  } else {
+    buffer_.reset(new uint8_t[buffer_capacity_]);
+  }
+}
+
+bool FileInputStream::Next(const void** data, size_t* size) {
+  if (HadError()) {
+    return false;
+  }
+
+  // Deal with any remaining bytes after BackUp was called.
+  if (buffer_offset_ != buffer_size_) {
+    *data = buffer_.get() + buffer_offset_;
+    *size = buffer_size_ - buffer_offset_;
+    total_byte_count_ += buffer_size_ - buffer_offset_;
+    buffer_offset_ = buffer_size_;
+    return true;
+  }
+
+  ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_));
+  if (n < 0) {
+    error_ = SystemErrorCodeToString(errno);
+    fd_.reset();
+    buffer_.reset();
+    return false;
+  }
+
+  buffer_size_ = static_cast<size_t>(n);
+  buffer_offset_ = buffer_size_;
+  total_byte_count_ += buffer_size_;
+
+  *data = buffer_.get();
+  *size = buffer_size_;
+  return buffer_size_ != 0u;
+}
+
+void FileInputStream::BackUp(size_t count) {
+  if (count > buffer_offset_) {
+    count = buffer_offset_;
+  }
+  buffer_offset_ -= count;
+  total_byte_count_ -= count;
+}
+
+size_t FileInputStream::ByteCount() const {
+  return total_byte_count_;
+}
+
+bool FileInputStream::HadError() const {
+  return fd_ == -1;
+}
+
+std::string FileInputStream::GetError() const {
+  return error_;
+}
+
+FileOutputStream::FileOutputStream(const std::string& path, int mode, size_t buffer_capacity)
+    : FileOutputStream(::android::base::utf8::open(path.c_str(), mode), buffer_capacity) {
+}
+
+FileOutputStream::FileOutputStream(int fd, size_t buffer_capacity)
+    : fd_(fd), buffer_capacity_(buffer_capacity), buffer_offset_(0u), total_byte_count_(0u) {
+  if (fd_ == -1) {
+    error_ = SystemErrorCodeToString(errno);
+  } else {
+    buffer_.reset(new uint8_t[buffer_capacity_]);
+  }
+}
+
+FileOutputStream::~FileOutputStream() {
+  // Flush the buffer.
+  Flush();
+}
+
+bool FileOutputStream::Next(void** data, size_t* size) {
+  if (fd_ == -1 || HadError()) {
+    return false;
+  }
+
+  if (buffer_offset_ == buffer_capacity_) {
+    if (!FlushImpl()) {
+      return false;
+    }
+  }
+
+  const size_t buffer_size = buffer_capacity_ - buffer_offset_;
+  *data = buffer_.get() + buffer_offset_;
+  *size = buffer_size;
+  total_byte_count_ += buffer_size;
+  buffer_offset_ = buffer_capacity_;
+  return true;
+}
+
+void FileOutputStream::BackUp(size_t count) {
+  if (count > buffer_offset_) {
+    count = buffer_offset_;
+  }
+  buffer_offset_ -= count;
+  total_byte_count_ -= count;
+}
+
+size_t FileOutputStream::ByteCount() const {
+  return total_byte_count_;
+}
+
+bool FileOutputStream::Flush() {
+  if (!HadError()) {
+    return FlushImpl();
+  }
+  return false;
+}
+
+bool FileOutputStream::FlushImpl() {
+  ssize_t n = TEMP_FAILURE_RETRY(write(fd_, buffer_.get(), buffer_offset_));
+  if (n < 0) {
+    error_ = SystemErrorCodeToString(errno);
+    fd_.reset();
+    buffer_.reset();
+    return false;
+  }
+
+  buffer_offset_ = 0u;
+  return true;
+}
+
+bool FileOutputStream::HadError() const {
+  return fd_ == -1;
+}
+
+std::string FileOutputStream::GetError() const {
+  return error_;
+}
+
+}  // namespace io
+}  // namespace aapt
diff --git a/tools/aapt2/io/FileInputStream.h b/tools/aapt2/io/FileStream.h
similarity index 60%
rename from tools/aapt2/io/FileInputStream.h
rename to tools/aapt2/io/FileStream.h
index 6beb9a1..3b07667 100644
--- a/tools/aapt2/io/FileInputStream.h
+++ b/tools/aapt2/io/FileStream.h
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-#ifndef AAPT_IO_FILEINPUTSTREAM_H
-#define AAPT_IO_FILEINPUTSTREAM_H
+#ifndef AAPT_IO_FILESTREAM_H
+#define AAPT_IO_FILESTREAM_H
 
 #include "io/Io.h"
 
 #include <memory>
 #include <string>
 
+#include "android-base/file.h"  // for O_BINARY
 #include "android-base/macros.h"
 #include "android-base/unique_fd.h"
 
@@ -57,7 +58,43 @@
   size_t total_byte_count_;
 };
 
+class FileOutputStream : public OutputStream {
+ public:
+  explicit FileOutputStream(const std::string& path, int mode = O_RDWR | O_CREAT | O_BINARY,
+                            size_t buffer_capacity = 4096);
+
+  // Takes ownership of `fd`.
+  explicit FileOutputStream(int fd, size_t buffer_capacity = 4096);
+
+  ~FileOutputStream();
+
+  bool Next(void** data, size_t* size) override;
+
+  // Immediately flushes out the contents of the buffer to disk.
+  bool Flush();
+
+  void BackUp(size_t count) override;
+
+  size_t ByteCount() const override;
+
+  bool HadError() const override;
+
+  std::string GetError() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileOutputStream);
+
+  bool FlushImpl();
+
+  android::base::unique_fd fd_;
+  std::string error_;
+  std::unique_ptr<uint8_t[]> buffer_;
+  size_t buffer_capacity_;
+  size_t buffer_offset_;
+  size_t total_byte_count_;
+};
+
 }  // namespace io
 }  // namespace aapt
 
-#endif  // AAPT_IO_FILEINPUTSTREAM_H
+#endif  // AAPT_IO_FILESTREAM_H
diff --git a/tools/aapt2/io/FileInputStream_test.cpp b/tools/aapt2/io/FileStream_test.cpp
similarity index 68%
rename from tools/aapt2/io/FileInputStream_test.cpp
rename to tools/aapt2/io/FileStream_test.cpp
index 7314ab7..68c3cb1 100644
--- a/tools/aapt2/io/FileInputStream_test.cpp
+++ b/tools/aapt2/io/FileStream_test.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "io/FileInputStream.h"
+#include "io/FileStream.h"
 
 #include "android-base/macros.h"
 #include "android-base/test_utils.h"
@@ -36,7 +36,7 @@
   lseek64(file.fd, 0, SEEK_SET);
 
   // Use a small buffer size so that we can call Next() a few times.
-  FileInputStream in(file.fd, 10u);
+  FileInputStream in(file.release(), 10u);
   ASSERT_FALSE(in.HadError());
   EXPECT_THAT(in.ByteCount(), Eq(0u));
 
@@ -83,5 +83,47 @@
   EXPECT_FALSE(in.HadError());
 }
 
+TEST(FileOutputStreamTest, NextAndBackup) {
+  const std::string input = "this is a cool string";
+
+  TemporaryFile file;
+  int fd = file.release();
+
+  // FileOutputStream takes ownership.
+  FileOutputStream out(fd, 10u);
+  ASSERT_FALSE(out.HadError());
+  EXPECT_THAT(out.ByteCount(), Eq(0u));
+
+  char* buffer;
+  size_t size;
+  ASSERT_TRUE(out.Next(reinterpret_cast<void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(out.ByteCount(), Eq(10u));
+  memcpy(buffer, input.c_str(), size);
+
+  ASSERT_TRUE(out.Next(reinterpret_cast<void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(out.ByteCount(), Eq(20u));
+  memcpy(buffer, input.c_str() + 10u, size);
+
+  ASSERT_TRUE(out.Next(reinterpret_cast<void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(out.ByteCount(), Eq(30u));
+  buffer[0] = input[20u];
+  out.BackUp(size - 1);
+  EXPECT_THAT(out.ByteCount(), Eq(21u));
+
+  ASSERT_TRUE(out.Flush());
+
+  lseek64(fd, 0, SEEK_SET);
+
+  std::string actual;
+  ASSERT_TRUE(android::base::ReadFdToString(fd, &actual));
+  EXPECT_THAT(actual, StrEq(input));
+}
+
 }  // namespace io
 }  // namespace aapt
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index 027cbd0..1387d22 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -20,11 +20,12 @@
 #include "utils/FileMap.h"
 
 #include "Source.h"
+#include "io/FileStream.h"
 #include "util/Files.h"
 #include "util/Maybe.h"
 #include "util/Util.h"
 
-using android::StringPiece;
+using ::android::StringPiece;
 
 namespace aapt {
 namespace io {
@@ -42,12 +43,20 @@
   return {};
 }
 
-const Source& RegularFile::GetSource() const { return source_; }
+std::unique_ptr<io::InputStream> RegularFile::OpenInputStream() {
+  return util::make_unique<FileInputStream>(source_.path);
+}
+
+const Source& RegularFile::GetSource() const {
+  return source_;
+}
 
 FileCollectionIterator::FileCollectionIterator(FileCollection* collection)
     : current_(collection->files_.begin()), end_(collection->files_.end()) {}
 
-bool FileCollectionIterator::HasNext() { return current_ != end_; }
+bool FileCollectionIterator::HasNext() {
+  return current_ != end_;
+}
 
 IFile* FileCollectionIterator::Next() {
   IFile* result = current_->second.get();
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index dfd3717..6be8807 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -24,17 +24,18 @@
 namespace aapt {
 namespace io {
 
-/**
- * A regular file from the file system. Uses mmap to open the data.
- */
+// A regular file from the file system. Uses mmap to open the data.
 class RegularFile : public IFile {
  public:
   explicit RegularFile(const Source& source);
 
   std::unique_ptr<IData> OpenAsData() override;
+  std::unique_ptr<io::InputStream> OpenInputStream() override;
   const Source& GetSource() const override;
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(RegularFile);
+
   Source source_;
 };
 
@@ -48,23 +49,26 @@
   io::IFile* Next() override;
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(FileCollectionIterator);
+
   std::map<std::string, std::unique_ptr<IFile>>::const_iterator current_, end_;
 };
 
-/**
- * An IFileCollection representing the file system.
- */
+// An IFileCollection representing the file system.
 class FileCollection : public IFileCollection {
  public:
-  /**
-   * Adds a file located at path. Returns the IFile representation of that file.
-   */
+  FileCollection() = default;
+
+  // Adds a file located at path. Returns the IFile representation of that file.
   IFile* InsertFile(const android::StringPiece& path);
   IFile* FindFile(const android::StringPiece& path) override;
   std::unique_ptr<IFileCollectionIterator> Iterator() override;
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(FileCollection);
+
   friend class FileCollectionIterator;
+
   std::map<std::string, std::unique_ptr<IFile>> files_;
 };
 
diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h
index a656740..e1df23a6 100644
--- a/tools/aapt2/io/Io.h
+++ b/tools/aapt2/io/Io.h
@@ -58,6 +58,12 @@
   virtual bool HadError() const = 0;
 };
 
+// A sub-InputStream interface that knows the total size of its stream.
+class KnownSizeInputStream : public InputStream {
+ public:
+  virtual size_t TotalSize() const = 0;
+};
+
 // OutputStream interface that mimics protobuf's ZeroCopyOutputStream,
 // with added error handling methods to better report issues.
 class OutputStream {
diff --git a/tools/aapt2/io/StringInputStream.cpp b/tools/aapt2/io/StringInputStream.cpp
deleted file mode 100644
index 51a18a7..0000000
--- a/tools/aapt2/io/StringInputStream.cpp
+++ /dev/null
@@ -1,50 +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.
- */
-
-#include "io/StringInputStream.h"
-
-using ::android::StringPiece;
-
-namespace aapt {
-namespace io {
-
-StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) {
-}
-
-bool StringInputStream::Next(const void** data, size_t* size) {
-  if (offset_ == str_.size()) {
-    return false;
-  }
-
-  *data = str_.data() + offset_;
-  *size = str_.size() - offset_;
-  offset_ = str_.size();
-  return true;
-}
-
-void StringInputStream::BackUp(size_t count) {
-  if (count > offset_) {
-    count = offset_;
-  }
-  offset_ -= count;
-}
-
-size_t StringInputStream::ByteCount() const {
-  return offset_;
-}
-
-}  // namespace io
-}  // namespace aapt
diff --git a/tools/aapt2/io/StringInputStream.h b/tools/aapt2/io/StringInputStream.h
deleted file mode 100644
index ff5b112..0000000
--- a/tools/aapt2/io/StringInputStream.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_IO_STRINGINPUTSTREAM_H
-#define AAPT_IO_STRINGINPUTSTREAM_H
-
-#include "io/Io.h"
-
-#include "android-base/macros.h"
-#include "androidfw/StringPiece.h"
-
-namespace aapt {
-namespace io {
-
-class StringInputStream : public InputStream {
- public:
-  explicit StringInputStream(const android::StringPiece& str);
-
-  bool Next(const void** data, size_t* size) override;
-
-  void BackUp(size_t count) override;
-
-  size_t ByteCount() const override;
-
-  inline bool HadError() const override {
-    return false;
-  }
-
-  inline std::string GetError() const override {
-    return {};
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(StringInputStream);
-
-  android::StringPiece str_;
-  size_t offset_;
-};
-
-}  // namespace io
-}  // namespace aapt
-
-#endif  // AAPT_IO_STRINGINPUTSTREAM_H
diff --git a/tools/aapt2/io/StringStream.cpp b/tools/aapt2/io/StringStream.cpp
new file mode 100644
index 0000000..4ca04a8
--- /dev/null
+++ b/tools/aapt2/io/StringStream.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "io/StringStream.h"
+
+using ::android::StringPiece;
+
+namespace aapt {
+namespace io {
+
+StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) {
+}
+
+bool StringInputStream::Next(const void** data, size_t* size) {
+  if (offset_ == str_.size()) {
+    return false;
+  }
+
+  *data = str_.data() + offset_;
+  *size = str_.size() - offset_;
+  offset_ = str_.size();
+  return true;
+}
+
+void StringInputStream::BackUp(size_t count) {
+  if (count > offset_) {
+    offset_ = 0u;
+  } else {
+    offset_ -= count;
+  }
+}
+
+size_t StringInputStream::ByteCount() const {
+  return offset_;
+}
+
+size_t StringInputStream::TotalSize() const {
+  return str_.size();
+}
+
+StringOutputStream::StringOutputStream(std::string* str, size_t buffer_capacity)
+    : str_(str),
+      buffer_capacity_(buffer_capacity),
+      buffer_offset_(0u),
+      buffer_(new char[buffer_capacity]) {
+}
+
+StringOutputStream::~StringOutputStream() {
+  Flush();
+}
+
+bool StringOutputStream::Next(void** data, size_t* size) {
+  if (buffer_offset_ == buffer_capacity_) {
+    FlushImpl();
+  }
+
+  *data = buffer_.get() + buffer_offset_;
+  *size = buffer_capacity_ - buffer_offset_;
+  buffer_offset_ = buffer_capacity_;
+  return true;
+}
+
+void StringOutputStream::BackUp(size_t count) {
+  if (count > buffer_offset_) {
+    buffer_offset_ = 0u;
+  } else {
+    buffer_offset_ -= count;
+  }
+}
+
+size_t StringOutputStream::ByteCount() const {
+  return str_->size() + buffer_offset_;
+}
+
+void StringOutputStream::Flush() {
+  if (buffer_offset_ != 0u) {
+    FlushImpl();
+  }
+}
+
+void StringOutputStream::FlushImpl() {
+  str_->append(buffer_.get(), buffer_offset_);
+  buffer_offset_ = 0u;
+}
+
+}  // namespace io
+}  // namespace aapt
diff --git a/tools/aapt2/io/StringStream.h b/tools/aapt2/io/StringStream.h
new file mode 100644
index 0000000..f29890a
--- /dev/null
+++ b/tools/aapt2/io/StringStream.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_IO_STRINGSTREAM_H
+#define AAPT_IO_STRINGSTREAM_H
+
+#include "io/Io.h"
+
+#include <memory>
+
+#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
+
+namespace aapt {
+namespace io {
+
+class StringInputStream : public KnownSizeInputStream {
+ public:
+  explicit StringInputStream(const android::StringPiece& str);
+
+  bool Next(const void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  size_t ByteCount() const override;
+
+  inline bool HadError() const override {
+    return false;
+  }
+
+  inline std::string GetError() const override {
+    return {};
+  }
+
+  size_t TotalSize() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(StringInputStream);
+
+  android::StringPiece str_;
+  size_t offset_;
+};
+
+class StringOutputStream : public OutputStream {
+ public:
+  explicit StringOutputStream(std::string* str, size_t buffer_capacity = 4096u);
+
+  ~StringOutputStream();
+
+  bool Next(void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  void Flush();
+
+  size_t ByteCount() const override;
+
+  inline bool HadError() const override {
+    return false;
+  }
+
+  inline std::string GetError() const override {
+    return {};
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(StringOutputStream);
+
+  void FlushImpl();
+
+  std::string* str_;
+  size_t buffer_capacity_;
+  size_t buffer_offset_;
+  std::unique_ptr<char[]> buffer_;
+};
+
+}  // namespace io
+}  // namespace aapt
+
+#endif  // AAPT_IO_STRINGSTREAM_H
diff --git a/tools/aapt2/io/StringInputStream_test.cpp b/tools/aapt2/io/StringStream_test.cpp
similarity index 86%
rename from tools/aapt2/io/StringInputStream_test.cpp
rename to tools/aapt2/io/StringStream_test.cpp
index cc57bc4..fb43fb7 100644
--- a/tools/aapt2/io/StringInputStream_test.cpp
+++ b/tools/aapt2/io/StringStream_test.cpp
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
+
+#include "io/Util.h"
 
 #include "test/Test.h"
 
@@ -68,5 +70,16 @@
   EXPECT_THAT(in.ByteCount(), Eq(input.size()));
 }
 
+TEST(StringOutputStreamTest, NextAndBackUp) {
+  std::string input = "hello this is a string";
+  std::string output;
+
+  StringInputStream in(input);
+  StringOutputStream out(&output, 10u);
+  ASSERT_TRUE(Copy(&out, &in));
+  out.Flush();
+  EXPECT_THAT(output, StrEq(input));
+}
+
 }  // namespace io
 }  // namespace aapt
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index 15114e8..d270340 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -18,6 +18,8 @@
 
 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
 
+using ::google::protobuf::io::ZeroCopyOutputStream;
+
 namespace aapt {
 namespace io {
 
@@ -91,5 +93,10 @@
   return !in->HadError();
 }
 
+bool Copy(ZeroCopyOutputStream* out, InputStream* in) {
+  OutputStreamAdaptor adaptor(out);
+  return Copy(&adaptor, in);
+}
+
 }  // namespace io
 }  // namespace aapt
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index 02ee876..1e48508 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -42,6 +42,81 @@
 // Copies the data from in to out. Returns false if there was an error.
 // If there was an error, check the individual streams' HadError/GetError methods.
 bool Copy(OutputStream* out, InputStream* in);
+bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in);
+
+class OutputStreamAdaptor : public io::OutputStream {
+ public:
+  explicit OutputStreamAdaptor(::google::protobuf::io::ZeroCopyOutputStream* out) : out_(out) {
+  }
+
+  bool Next(void** data, size_t* size) override {
+    int out_size;
+    bool result = out_->Next(data, &out_size);
+    *size = static_cast<size_t>(out_size);
+    if (!result) {
+      error_ocurred_ = true;
+    }
+    return result;
+  }
+
+  void BackUp(size_t count) override {
+    out_->BackUp(static_cast<int>(count));
+  }
+
+  size_t ByteCount() const override {
+    return static_cast<size_t>(out_->ByteCount());
+  }
+
+  bool HadError() const override {
+    return error_ocurred_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OutputStreamAdaptor);
+
+  ::google::protobuf::io::ZeroCopyOutputStream* out_;
+  bool error_ocurred_ = false;
+};
+
+class ZeroCopyInputAdaptor : public ::google::protobuf::io::ZeroCopyInputStream {
+ public:
+  explicit ZeroCopyInputAdaptor(io::InputStream* in) : in_(in) {
+  }
+
+  bool Next(const void** data, int* size) override {
+    size_t out_size;
+    bool result = in_->Next(data, &out_size);
+    *size = static_cast<int>(out_size);
+    return result;
+  }
+
+  void BackUp(int count) override {
+    in_->BackUp(static_cast<size_t>(count));
+  }
+
+  bool Skip(int count) override {
+    const void* data;
+    int size;
+    while (Next(&data, &size)) {
+      if (size > count) {
+        BackUp(size - count);
+        return true;
+      } else {
+        count -= size;
+      }
+    }
+    return false;
+  }
+
+  ::google::protobuf::int64 ByteCount() const override {
+    return static_cast<::google::protobuf::int64>(in_->ByteCount());
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ZeroCopyInputAdaptor);
+
+  io::InputStream* in_;
+};
 
 }  // namespace io
 }  // namespace aapt
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index 6494d2d..269b6c5 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -22,7 +22,7 @@
 #include "Source.h"
 #include "util/Util.h"
 
-using android::StringPiece;
+using ::android::StringPiece;
 
 namespace aapt {
 namespace io {
@@ -57,7 +57,13 @@
   }
 }
 
-const Source& ZipFile::GetSource() const { return source_; }
+std::unique_ptr<io::InputStream> ZipFile::OpenInputStream() {
+  return OpenAsData();
+}
+
+const Source& ZipFile::GetSource() const {
+  return source_;
+}
 
 bool ZipFile::WasCompressed() {
   return zip_entry_.method != kCompressStored;
@@ -67,7 +73,9 @@
     ZipFileCollection* collection)
     : current_(collection->files_.begin()), end_(collection->files_.end()) {}
 
-bool ZipFileCollectionIterator::HasNext() { return current_ != end_; }
+bool ZipFileCollectionIterator::HasNext() {
+  return current_ != end_;
+}
 
 IFile* ZipFileCollectionIterator::Next() {
   IFile* result = current_->get();
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index 56c74e3..8381259 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -28,23 +28,20 @@
 namespace aapt {
 namespace io {
 
-/**
- * An IFile representing a file within a ZIP archive. If the file is compressed,
- * it is uncompressed
- * and copied into memory when opened. Otherwise it is mmapped from the ZIP
- * archive.
- */
+// An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed
+// and copied into memory when opened. Otherwise it is mmapped from the ZIP archive.
 class ZipFile : public IFile {
  public:
-  ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source);
+  ZipFile(::ZipArchiveHandle handle, const ::ZipEntry& entry, const Source& source);
 
   std::unique_ptr<IData> OpenAsData() override;
+  std::unique_ptr<io::InputStream> OpenInputStream() override;
   const Source& GetSource() const override;
   bool WasCompressed() override;
 
  private:
-  ZipArchiveHandle zip_handle_;
-  ZipEntry zip_entry_;
+  ::ZipArchiveHandle zip_handle_;
+  ::ZipEntry zip_entry_;
   Source source_;
 };
 
@@ -61,9 +58,7 @@
   std::vector<std::unique_ptr<IFile>>::const_iterator current_, end_;
 };
 
-/**
- * An IFileCollection that represents a ZIP archive and the entries within it.
- */
+// An IFileCollection that represents a ZIP archive and the entries within it.
 class ZipFileCollection : public IFileCollection {
  public:
   static std::unique_ptr<ZipFileCollection> Create(const android::StringPiece& path,
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 93c904f..21c6b11 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -37,13 +37,12 @@
   CHECK(master_package_ != nullptr) << "package name or ID already taken";
 }
 
-bool TableMerger::Merge(const Source& src, ResourceTable* table, io::IFileCollection* collection) {
-  return MergeImpl(src, table, collection, false /*overlay*/, true /*allow_new*/);
-}
-
-bool TableMerger::MergeOverlay(const Source& src, ResourceTable* table,
-                               io::IFileCollection* collection) {
-  return MergeImpl(src, table, collection, true /*overlay*/, options_.auto_add_overlay);
+bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay,
+                        io::IFileCollection* collection) {
+  // We allow adding new resources if this is not an overlay, or if the options allow overlays
+  // to add new resources.
+  return MergeImpl(src, table, collection, overlay,
+                   options_.auto_add_overlay || !overlay /*allow_new*/);
 }
 
 // This will merge packages with the same package name (or no package name).
@@ -322,17 +321,20 @@
         util::make_unique<FileReference>(master_table_->string_pool.MakeRef(newPath));
     new_file_ref->SetComment(file_ref.GetComment());
     new_file_ref->SetSource(file_ref.GetSource());
+    new_file_ref->type = file_ref.type;
+    new_file_ref->file = file_ref.file;
     return new_file_ref;
   }
   return std::unique_ptr<FileReference>(file_ref.Clone(&master_table_->string_pool));
 }
 
-bool TableMerger::MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, bool overlay) {
+bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFile* file) {
   ResourceTable table;
   std::string path = ResourceUtils::BuildResourceFileName(file_desc);
   std::unique_ptr<FileReference> file_ref =
       util::make_unique<FileReference>(table.string_pool.MakeRef(path));
   file_ref->SetSource(file_desc.source);
+  file_ref->type = file_desc.type;
   file_ref->file = file;
 
   ResourceTablePackage* pkg = table.CreatePackage(file_desc.name.package, 0x0);
@@ -341,17 +343,8 @@
       ->FindOrCreateValue(file_desc.config, {})
       ->value = std::move(file_ref);
 
-  return DoMerge(file->GetSource(), &table, pkg, false /* mangle */,
-                 overlay /* overlay */, true /* allow_new */, {});
-}
-
-bool TableMerger::MergeFile(const ResourceFile& file_desc, io::IFile* file) {
-  return MergeFileImpl(file_desc, file, false /* overlay */);
-}
-
-bool TableMerger::MergeFileOverlay(const ResourceFile& file_desc,
-                                   io::IFile* file) {
-  return MergeFileImpl(file_desc, file, true /* overlay */);
+  return DoMerge(file->GetSource(), &table, pkg, false /* mangle */, overlay /* overlay */,
+                 true /* allow_new */, {});
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 81518ff..d024aa4 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -59,24 +59,18 @@
   }
 
   // Merges resources from the same or empty package. This is for local sources.
+  // If overlay is true, the resources are treated as overlays.
   // An io::IFileCollection is optional and used to find the referenced Files and process them.
-  bool Merge(const Source& src, ResourceTable* table, io::IFileCollection* collection = nullptr);
-
-  // Merges resources from an overlay ResourceTable.
-  // An io::IFileCollection is optional and used to find the referenced Files and process them.
-  bool MergeOverlay(const Source& src, ResourceTable* table,
-                    io::IFileCollection* collection = nullptr);
+  bool Merge(const Source& src, ResourceTable* table, bool overlay,
+             io::IFileCollection* collection = nullptr);
 
   // Merges resources from the given package, mangling the name. This is for static libraries.
   // An io::IFileCollection is needed in order to find the referenced Files and process them.
   bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table,
                       io::IFileCollection* collection);
 
-  // Merges a compiled file that belongs to this same or empty package. This is for local sources.
-  bool MergeFile(const ResourceFile& fileDesc, io::IFile* file);
-
-  // Merges a compiled file from an overlay, overriding an existing definition.
-  bool MergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file);
+  // Merges a compiled file that belongs to this same or empty package.
+  bool MergeFile(const ResourceFile& fileDesc, bool overlay, io::IFile* file);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(TableMerger);
@@ -91,9 +85,6 @@
   ResourceTablePackage* master_package_;
   std::set<std::string> merged_packages_;
 
-  bool MergeFileImpl(const ResourceFile& file_desc, io::IFile* file,
-                     bool overlay);
-
   bool MergeImpl(const Source& src, ResourceTable* src_table,
                  io::IFileCollection* collection, bool overlay, bool allow_new);
 
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 45b01a4..3499809 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -69,7 +69,7 @@
   TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
   io::FileCollection collection;
 
-  ASSERT_TRUE(merger.Merge({}, table_a.get()));
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
   ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
 
   EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0);
@@ -98,7 +98,7 @@
   file_desc.source = Source("res/layout-hdpi/main.xml");
   test::TestFile test_file("path/to/res/layout-hdpi/main.xml.flat");
 
-  ASSERT_TRUE(merger.MergeFile(file_desc, &test_file));
+  ASSERT_TRUE(merger.MergeFile(file_desc, false /*overlay*/, &test_file));
 
   FileReference* file = test::GetValueForConfig<FileReference>(
       &final_table, "com.app.a:layout/main", test::ParseConfigOrDie("hdpi-v4"));
@@ -117,8 +117,8 @@
   test::TestFile file_a("path/to/fileA.xml.flat");
   test::TestFile file_b("path/to/fileB.xml.flat");
 
-  ASSERT_TRUE(merger.MergeFile(file_desc, &file_a));
-  ASSERT_TRUE(merger.MergeFileOverlay(file_desc, &file_b));
+  ASSERT_TRUE(merger.MergeFile(file_desc, false /*overlay*/, &file_a));
+  ASSERT_TRUE(merger.MergeFile(file_desc, true /*overlay*/, &file_b));
 }
 
 TEST_F(TableMergerTest, MergeFileReferences) {
@@ -138,7 +138,7 @@
   io::FileCollection collection;
   collection.InsertFile("res/xml/file.xml");
 
-  ASSERT_TRUE(merger.Merge({}, table_a.get()));
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
   ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
 
   FileReference* f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/file");
@@ -167,8 +167,8 @@
   options.auto_add_overlay = false;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, base.get()));
-  ASSERT_TRUE(merger.MergeOverlay({}, overlay.get()));
+  ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+  ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/));
 
   BinaryPrimitive* foo = test::GetValue<BinaryPrimitive>(&final_table, "com.app.a:bool/foo");
   ASSERT_THAT(foo,
@@ -194,8 +194,8 @@
   options.auto_add_overlay = false;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, base.get()));
-  ASSERT_TRUE(merger.MergeOverlay({}, overlay.get()));
+  ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+  ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/));
 }
 
 TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) {
@@ -217,8 +217,8 @@
   options.auto_add_overlay = false;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, base.get()));
-  ASSERT_FALSE(merger.MergeOverlay({}, overlay.get()));
+  ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+  ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/));
 }
 
 TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) {
@@ -240,8 +240,8 @@
   options.auto_add_overlay = false;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, base.get()));
-  ASSERT_FALSE(merger.MergeOverlay({}, overlay.get()));
+  ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+  ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/));
 }
 
 TEST_F(TableMergerTest, MergeAddResourceFromOverlay) {
@@ -259,8 +259,8 @@
   options.auto_add_overlay = false;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, table_a.get()));
-  ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+  ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
 }
 
 TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) {
@@ -277,8 +277,8 @@
   options.auto_add_overlay = true;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, table_a.get()));
-  ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+  ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
 }
 
 TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
@@ -295,8 +295,8 @@
   options.auto_add_overlay = false;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, table_a.get()));
-  ASSERT_FALSE(merger.MergeOverlay({}, table_b.get()));
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+  ASSERT_FALSE(merger.Merge({}, table_b.get(), true /*overlay*/));
 }
 
 TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) {
@@ -337,8 +337,8 @@
   options.auto_add_overlay = true;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, table_a.get()));
-  ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+  ASSERT_TRUE(merger.Merge({}, table_b.get(), true /*overlay*/));
 
   Styleable* styleable = test::GetValue<Styleable>(&final_table, "com.app.a:styleable/Foo");
   ASSERT_THAT(styleable, NotNull());
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index e658664..5a62e97 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -19,7 +19,7 @@
 #include "android-base/logging.h"
 #include "androidfw/StringPiece.h"
 
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
 #include "test/Common.h"
 #include "util/Util.h"
 
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 61d0563..4e318a9 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -90,6 +90,10 @@
     return {};
   }
 
+  std::unique_ptr<io::InputStream> OpenInputStream() override {
+    return OpenAsData();
+  }
+
   const Source& GetSource() const override {
     return source_;
   }
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index b3e0a92..3522506 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -215,8 +215,8 @@
       return {};
     }
   }
-  return util::make_unique<XmlResource>(ResourceFile{{}, {}, source}, StringPool{},
-                                        std::move(stack.root));
+  return util::make_unique<XmlResource>(ResourceFile{{}, {}, ResourceFile::Type::kUnknown, source},
+                                        StringPool{}, std::move(stack.root));
 }
 
 static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPool* out_pool) {
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 4ba0443..34e6d3f 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -19,7 +19,7 @@
 #include <string>
 
 #include "format/binary/XmlFlattener.h"
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
 #include "test/Test.h"
 
 using ::aapt::io::StringInputStream;
diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp
index 681d9d4..5304bde 100644
--- a/tools/aapt2/xml/XmlPullParser_test.cpp
+++ b/tools/aapt2/xml/XmlPullParser_test.cpp
@@ -18,43 +18,49 @@
 
 #include "androidfw/StringPiece.h"
 
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
 #include "test/Test.h"
 
 using ::aapt::io::StringInputStream;
 using ::android::StringPiece;
+using ::testing::Eq;
+using ::testing::StrEq;
+
+using Event = ::aapt::xml::XmlPullParser::Event;
 
 namespace aapt {
+namespace xml {
 
 TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) {
   std::string str =
       R"(<?xml version="1.0" encoding="utf-8"?>
          <a><b><c xmlns:a="http://schema.org"><d/></c><e/></b></a>)";
   StringInputStream input(str);
-  xml::XmlPullParser parser(&input);
+  XmlPullParser parser(&input);
 
   const size_t depth_outer = parser.depth();
-  ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_outer));
+  ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_outer));
 
-  EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
-  EXPECT_EQ(StringPiece("a"), StringPiece(parser.element_name()));
+  EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement));
+  EXPECT_THAT(parser.element_name(), StrEq("a"));
 
   const size_t depth_a = parser.depth();
-  ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_a));
-  EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
-  EXPECT_EQ(StringPiece("b"), StringPiece(parser.element_name()));
+  ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_a));
+  EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement));
+  EXPECT_THAT(parser.element_name(), StrEq("b"));
 
   const size_t depth_b = parser.depth();
-  ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b));
-  EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
-  EXPECT_EQ(StringPiece("c"), StringPiece(parser.element_name()));
+  ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_b));
+  EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement));
+  EXPECT_THAT(parser.element_name(), StrEq("c"));
 
-  ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b));
-  EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
-  EXPECT_EQ(StringPiece("e"), StringPiece(parser.element_name()));
+  ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_b));
+  EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement));
+  EXPECT_THAT(parser.element_name(), StrEq("e"));
 
-  ASSERT_FALSE(xml::XmlPullParser::NextChildNode(&parser, depth_outer));
-  EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.event());
+  ASSERT_FALSE(XmlPullParser::NextChildNode(&parser, depth_outer));
+  EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kEndDocument));
 }
 
+}  // namespace xml
 }  // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
index c1186e8..0a622b2 100644
--- a/tools/aapt2/xml/XmlUtil.cpp
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -16,20 +16,20 @@
 
 #include "xml/XmlUtil.h"
 
+#include <algorithm>
 #include <string>
 
 #include "util/Maybe.h"
 #include "util/Util.h"
+#include "xml/XmlDom.h"
 
-using android::StringPiece;
+using ::android::StringPiece;
 
 namespace aapt {
 namespace xml {
 
-std::string BuildPackageNamespace(const StringPiece& package,
-                                  bool private_reference) {
-  std::string result =
-      private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
+std::string BuildPackageNamespace(const StringPiece& package, bool private_reference) {
+  std::string result = private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
   result.append(package.data(), package.size());
   return result;
 }
@@ -39,8 +39,7 @@
   if (util::StartsWith(namespace_uri, kSchemaPublicPrefix)) {
     StringPiece schema_prefix = kSchemaPublicPrefix;
     StringPiece package = namespace_uri;
-    package = package.substr(schema_prefix.size(),
-                             package.size() - schema_prefix.size());
+    package = package.substr(schema_prefix.size(), package.size() - schema_prefix.size());
     if (package.empty()) {
       return {};
     }
@@ -49,8 +48,7 @@
   } else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) {
     StringPiece schema_prefix = kSchemaPrivatePrefix;
     StringPiece package = namespace_uri;
-    package = package.substr(schema_prefix.size(),
-                             package.size() - schema_prefix.size());
+    package = package.substr(schema_prefix.size(), package.size() - schema_prefix.size());
     if (package.empty()) {
       return {};
     }
@@ -76,5 +74,33 @@
   }
 }
 
+namespace {
+
+class ToolsNamespaceRemover : public Visitor {
+ public:
+  using Visitor::Visit;
+
+  void Visit(Element* el) override {
+    auto new_end =
+        std::remove_if(el->namespace_decls.begin(), el->namespace_decls.end(),
+                       [](const NamespaceDecl& decl) -> bool { return decl.uri == kSchemaTools; });
+    el->namespace_decls.erase(new_end, el->namespace_decls.end());
+
+    auto new_attr_end = std::remove_if(
+        el->attributes.begin(), el->attributes.end(),
+        [](const Attribute& attr) -> bool { return attr.namespace_uri == kSchemaTools; });
+    el->attributes.erase(new_attr_end, el->attributes.end());
+
+    Visitor::Visit(el);
+  }
+};
+
+}  // namespace
+
+void StripAndroidStudioAttributes(Element* el) {
+  ToolsNamespaceRemover remover;
+  el->Accept(&remover);
+}
+
 }  // namespace xml
 }  // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index 4eb359a..592a604 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -78,6 +78,12 @@
 // package declaration was private.
 void ResolvePackage(const IPackageDeclStack* decl_stack, Reference* in_ref);
 
+class Element;
+
+// Strips out any attributes in the http://schemas.android.com/tools namespace, which is owned by
+// Android Studio and should not make it to the final APK.
+void StripAndroidStudioAttributes(Element* el);
+
 }  // namespace xml
 }  // namespace aapt
 
diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk
index bb5f4d6..3f65151 100644
--- a/tools/locked_region_code_injection/Android.mk
+++ b/tools/locked_region_code_injection/Android.mk
@@ -6,10 +6,10 @@
 LOCAL_MODULE := lockedregioncodeinjection
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    asm-6.0_BETA \
-    asm-commons-6.0_BETA \
-    asm-tree-6.0_BETA \
-    asm-analysis-6.0_BETA \
+    asm-6.0 \
+    asm-commons-6.0 \
+    asm-tree-6.0 \
+    asm-analysis-6.0 \
     guava-21.0 \
 
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/wifi/java/android/net/wifi/rtt/IRttCallback.aidl b/wifi/java/android/net/wifi/rtt/IRttCallback.aidl
index fb1636f..578dd1e 100644
--- a/wifi/java/android/net/wifi/rtt/IRttCallback.aidl
+++ b/wifi/java/android/net/wifi/rtt/IRttCallback.aidl
@@ -26,7 +26,12 @@
 oneway interface IRttCallback
 {
     /**
-     * Service to manager callback providing RTT status and results.
+     * Service to manager callback indicating failure.
      */
-    void onRangingResults(int status, in List<RangingResult> results);
+    void onRangingFailure(int status);
+
+    /**
+     * Service to manager callback indicating success and providing results.
+     */
+    void onRangingResults(in List<RangingResult> results);
 }
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.java b/wifi/java/android/net/wifi/rtt/RangingRequest.java
index 997b680..a396281 100644
--- a/wifi/java/android/net/wifi/rtt/RangingRequest.java
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.java
@@ -17,13 +17,22 @@
 package android.net.wifi.rtt;
 
 import android.net.wifi.ScanResult;
+import android.net.wifi.aware.AttachCallback;
+import android.net.wifi.aware.DiscoverySessionCallback;
+import android.net.wifi.aware.IdentityChangedListener;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.WifiAwareManager;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import libcore.util.HexEncoding;
+
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.StringJoiner;
 
 /**
@@ -33,8 +42,8 @@
  * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}.
  * <p>
  * The ranging request is a batch request - specifying a set of devices (specified using
- * {@link RangingRequest.Builder#addAp(ScanResult)} and
- * {@link RangingRequest.Builder#addAps(List)}).
+ * {@link RangingRequest.Builder#addAccessPoint(ScanResult)} and
+ * {@link RangingRequest.Builder#addAccessPoints(List)}).
  *
  * @hide RTT_API
  */
@@ -44,8 +53,8 @@
     /**
      * Returns the maximum number of peers to range which can be specified in a single {@code
      * RangingRequest}. The limit applies no matter how the peers are added to the request, e.g.
-     * through {@link RangingRequest.Builder#addAp(ScanResult)} or
-     * {@link RangingRequest.Builder#addAps(List)}.
+     * through {@link RangingRequest.Builder#addAccessPoint(ScanResult)} or
+     * {@link RangingRequest.Builder#addAccessPoints(List)}.
      *
      * @return Maximum number of peers.
      */
@@ -94,11 +103,34 @@
     }
 
     /** @hide */
-    public void enforceValidity() {
+    public void enforceValidity(boolean awareSupported) {
         if (mRttPeers.size() > MAX_PEERS) {
             throw new IllegalArgumentException(
                     "Ranging to too many peers requested. Use getMaxPeers() API to get limit.");
         }
+
+        for (RttPeer peer: mRttPeers) {
+            if (peer instanceof RttPeerAp) {
+                RttPeerAp apPeer = (RttPeerAp) peer;
+                if (apPeer.scanResult == null || apPeer.scanResult.BSSID == null) {
+                    throw new IllegalArgumentException("Invalid AP peer specification");
+                }
+            } else if (peer instanceof RttPeerAware) {
+                if (!awareSupported) {
+                    throw new IllegalArgumentException(
+                            "Request contains Aware peers - but Aware isn't supported on this "
+                                    + "device");
+                }
+
+                RttPeerAware awarePeer = (RttPeerAware) peer;
+                if (awarePeer.peerMacAddress == null && awarePeer.peerHandle == null) {
+                    throw new IllegalArgumentException("Invalid Aware peer specification");
+                }
+            } else {
+                throw new IllegalArgumentException(
+                        "Request contains unknown peer specification types");
+            }
+        }
     }
 
     /**
@@ -116,7 +148,7 @@
          * @return The builder to facilitate chaining
          *         {@code builder.setXXX(..).setXXX(..)}.
          */
-        public Builder addAp(ScanResult apInfo) {
+        public Builder addAccessPoint(ScanResult apInfo) {
             if (apInfo == null) {
                 throw new IllegalArgumentException("Null ScanResult!");
             }
@@ -133,17 +165,55 @@
          * @return The builder to facilitate chaining
          *         {@code builder.setXXX(..).setXXX(..)}.
          */
-        public Builder addAps(List<ScanResult> apInfos) {
+        public Builder addAccessPoints(List<ScanResult> apInfos) {
             if (apInfos == null) {
                 throw new IllegalArgumentException("Null list of ScanResults!");
             }
             for (ScanResult scanResult : apInfos) {
-                addAp(scanResult);
+                addAccessPoint(scanResult);
             }
             return this;
         }
 
         /**
+         * Add the device specified by the {@code peerMacAddress} to the list of devices with
+         * which to measure range.
+         *
+         * The MAC address may be obtained out-of-band from a peer Wi-Fi Aware device. A Wi-Fi
+         * Aware device may obtain its MAC address using the {@link IdentityChangedListener}
+         * provided to
+         * {@link WifiAwareManager#attach(AttachCallback, IdentityChangedListener, Handler)}.
+         *
+         * * Note: in order to use this API the device must support Wi-Fi Aware
+         * {@link android.net.wifi.aware}.
+         *
+         * @param peerMacAddress The MAC address of the Wi-Fi Aware peer.
+         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder addWifiAwarePeer(byte[] peerMacAddress) {
+            mRttPeers.add(new RttPeerAware(peerMacAddress));
+            return this;
+        }
+
+        /**
+         * Add a device specified by a {@link PeerHandle} to the list of devices with which to
+         * measure range.
+         *
+         * The {@link PeerHandle} may be obtained as part of the Wi-Fi Aware discovery process. E.g.
+         * using {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}.
+         *
+         * Note: in order to use this API the device must support Wi-Fi Aware
+         * {@link android.net.wifi.aware}.
+         *
+         * @param peerHandle The peer handler of the peer Wi-Fi Aware device.
+         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder addWifiAwarePeer(PeerHandle peerHandle) {
+            mRttPeers.add(new RttPeerAware(peerHandle));
+            return this;
+        }
+
+        /**
          * Build {@link RangingRequest} given the current configurations made on the
          * builder.
          */
@@ -234,4 +304,89 @@
             return scanResult.hashCode();
         }
     }
-}
\ No newline at end of file
+
+    /** @hide */
+    public static class RttPeerAware implements RttPeer, Parcelable {
+        public PeerHandle peerHandle;
+        public byte[] peerMacAddress;
+
+        public RttPeerAware(PeerHandle peerHandle) {
+            if (peerHandle == null) {
+                throw new IllegalArgumentException("Null peerHandle");
+            }
+            this.peerHandle = peerHandle;
+            peerMacAddress = null;
+        }
+
+        public RttPeerAware(byte[] peerMacAddress) {
+            if (peerMacAddress == null) {
+                throw new IllegalArgumentException("Null peerMacAddress");
+            }
+
+            this.peerMacAddress = peerMacAddress;
+            peerHandle = null;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            if (peerHandle == null) {
+                dest.writeBoolean(false);
+                dest.writeByteArray(peerMacAddress);
+            } else {
+                dest.writeBoolean(true);
+                dest.writeInt(peerHandle.peerId);
+            }
+        }
+
+        public static final Creator<RttPeerAware> CREATOR = new Creator<RttPeerAware>() {
+            @Override
+            public RttPeerAware[] newArray(int size) {
+                return new RttPeerAware[size];
+            }
+
+            @Override
+            public RttPeerAware createFromParcel(Parcel in) {
+                boolean peerHandleAvail = in.readBoolean();
+                if (peerHandleAvail) {
+                    return new RttPeerAware(new PeerHandle(in.readInt()));
+                } else {
+                    return new RttPeerAware(in.createByteArray());
+                }
+            }
+        };
+
+        @Override
+        public String toString() {
+            return new StringBuilder("RttPeerAware: peerHandle=").append(
+                    peerHandle == null ? "<null>" : Integer.toString(peerHandle.peerId)).append(
+                    ", peerMacAddress=").append(peerMacAddress == null ? "<null>"
+                    : new String(HexEncoding.encode(peerMacAddress))).toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+
+            if (!(o instanceof RttPeerAware)) {
+                return false;
+            }
+
+            RttPeerAware lhs = (RttPeerAware) o;
+
+            return Objects.equals(peerHandle, lhs.peerHandle) && Arrays.equals(peerMacAddress,
+                    lhs.peerMacAddress);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(peerHandle.peerId, peerMacAddress);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.java b/wifi/java/android/net/wifi/rtt/RangingResult.java
index 918803e..93e52ae 100644
--- a/wifi/java/android/net/wifi/rtt/RangingResult.java
+++ b/wifi/java/android/net/wifi/rtt/RangingResult.java
@@ -16,13 +16,16 @@
 
 package android.net.wifi.rtt;
 
+import android.annotation.IntDef;
+import android.net.wifi.aware.PeerHandle;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.Log;
 
 import libcore.util.HexEncoding;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
@@ -40,28 +43,61 @@
 public final class RangingResult implements Parcelable {
     private static final String TAG = "RangingResult";
 
+    /** @hide */
+    @IntDef({STATUS_SUCCESS, STATUS_FAIL})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RangeResultStatus {
+    }
+
+    /**
+     * Individual range request status, {@link #getStatus()}. Indicates ranging operation was
+     * successful and distance value is valid.
+     */
+    public static final int STATUS_SUCCESS = 0;
+
+    /**
+     * Individual range request status, {@link #getStatus()}. Indicates ranging operation failed
+     * and the distance value is invalid.
+     */
+    public static final int STATUS_FAIL = 1;
+
     private final int mStatus;
     private final byte[] mMac;
-    private final int mDistanceCm;
-    private final int mDistanceStdDevCm;
+    private final PeerHandle mPeerHandle;
+    private final int mDistanceMm;
+    private final int mDistanceStdDevMm;
     private final int mRssi;
     private final long mTimestamp;
 
     /** @hide */
-    public RangingResult(int status, byte[] mac, int distanceCm, int distanceStdDevCm, int rssi,
-            long timestamp) {
+    public RangingResult(@RangeResultStatus int status, byte[] mac, int distanceMm,
+            int distanceStdDevMm, int rssi, long timestamp) {
         mStatus = status;
         mMac = mac;
-        mDistanceCm = distanceCm;
-        mDistanceStdDevCm = distanceStdDevCm;
+        mPeerHandle = null;
+        mDistanceMm = distanceMm;
+        mDistanceStdDevMm = distanceStdDevMm;
+        mRssi = rssi;
+        mTimestamp = timestamp;
+    }
+
+    /** @hide */
+    public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm,
+            int distanceStdDevMm, int rssi, long timestamp) {
+        mStatus = status;
+        mMac = null;
+        mPeerHandle = peerHandle;
+        mDistanceMm = distanceMm;
+        mDistanceStdDevMm = distanceStdDevMm;
         mRssi = rssi;
         mTimestamp = timestamp;
     }
 
     /**
-     * @return The status of ranging measurement: {@link RangingResultCallback#STATUS_SUCCESS} in
-     * case of success, and {@link RangingResultCallback#STATUS_FAIL} in case of failure.
+     * @return The status of ranging measurement: {@link #STATUS_SUCCESS} in case of success, and
+     * {@link #STATUS_FAIL} in case of failure.
      */
+    @RangeResultStatus
     public int getStatus() {
         return mStatus;
     }
@@ -70,57 +106,80 @@
      * @return The MAC address of the device whose range measurement was requested. Will correspond
      * to the MAC address of the device in the {@link RangingRequest}.
      * <p>
-     * Always valid (i.e. when {@link #getStatus()} is either SUCCESS or FAIL.
+     * Will return a {@code null} for results corresponding to requests issued using a {@code
+     * PeerHandle}, i.e. using the {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)} API.
      */
     public byte[] getMacAddress() {
         return mMac;
     }
 
     /**
-     * @return The distance (in cm) to the device specified by {@link #getMacAddress()}.
+     * @return The PeerHandle of the device whose reange measurement was requested. Will correspond
+     * to the PeerHandle of the devices requested using
+     * {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)}.
      * <p>
-     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     * Will return a {@code null} for results corresponding to requests issued using a MAC address.
      */
-    public int getDistanceCm() {
-        if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
-            Log.e(TAG, "getDistanceCm(): invalid value retrieved");
-        }
-        return mDistanceCm;
+    public PeerHandle getPeerHandle() {
+        return mPeerHandle;
     }
 
     /**
-     * @return The standard deviation of the measured distance (in cm) to the device specified by
-     * {@link #getMacAddress()}. The standard deviation is calculated over the measurements
-     * executed in a single RTT burst.
+     * @return The distance (in mm) to the device specified by {@link #getMacAddress()} or
+     * {@link #getPeerHandle()}.
      * <p>
-     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
      */
-    public int getDistanceStdDevCm() {
-        if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
-            Log.e(TAG, "getDistanceStdDevCm(): invalid value retrieved");
+    public int getDistanceMm() {
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "getDistanceMm(): invoked on an invalid result: getStatus()=" + mStatus);
         }
-        return mDistanceStdDevCm;
+        return mDistanceMm;
+    }
+
+    /**
+     * @return The standard deviation of the measured distance (in mm) to the device specified by
+     * {@link #getMacAddress()} or {@link #getPeerHandle()}. The standard deviation is calculated
+     * over the measurements executed in a single RTT burst.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
+     */
+    public int getDistanceStdDevMm() {
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "getDistanceStdDevMm(): invoked on an invalid result: getStatus()=" + mStatus);
+        }
+        return mDistanceStdDevMm;
     }
 
     /**
      * @return The average RSSI (in units of -0.5dB) observed during the RTT measurement.
      * <p>
-     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
      */
     public int getRssi() {
-        if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
-            // TODO: should this be an exception?
-            Log.e(TAG, "getRssi(): invalid value retrieved");
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "getRssi(): invoked on an invalid result: getStatus()=" + mStatus);
         }
         return mRssi;
     }
 
     /**
-     * @return The timestamp (in us) at which the ranging operation was performed
+     * @return The timestamp, in us since boot, at which the ranging operation was performed.
      * <p>
-     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
      */
-    public long getRangingTimestamp() {
+    public long getRangingTimestampUs() {
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "getRangingTimestamp(): invoked on an invalid result: getStatus()=" + mStatus);
+        }
         return mTimestamp;
     }
 
@@ -135,8 +194,14 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mStatus);
         dest.writeByteArray(mMac);
-        dest.writeInt(mDistanceCm);
-        dest.writeInt(mDistanceStdDevCm);
+        if (mPeerHandle == null) {
+            dest.writeBoolean(false);
+        } else {
+            dest.writeBoolean(true);
+            dest.writeInt(mPeerHandle.peerId);
+        }
+        dest.writeInt(mDistanceMm);
+        dest.writeInt(mDistanceStdDevMm);
         dest.writeInt(mRssi);
         dest.writeLong(mTimestamp);
     }
@@ -152,11 +217,22 @@
         public RangingResult createFromParcel(Parcel in) {
             int status = in.readInt();
             byte[] mac = in.createByteArray();
-            int distanceCm = in.readInt();
-            int distanceStdDevCm = in.readInt();
+            boolean peerHandlePresent = in.readBoolean();
+            PeerHandle peerHandle = null;
+            if (peerHandlePresent) {
+                peerHandle = new PeerHandle(in.readInt());
+            }
+            int distanceMm = in.readInt();
+            int distanceStdDevMm = in.readInt();
             int rssi = in.readInt();
             long timestamp = in.readLong();
-            return new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, timestamp);
+            if (peerHandlePresent) {
+                return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi,
+                        timestamp);
+            } else {
+                return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi,
+                        timestamp);
+            }
         }
     };
 
@@ -164,9 +240,10 @@
     @Override
     public String toString() {
         return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
-                mMac == null ? "<null>" : HexEncoding.encodeToString(mMac)).append(
-                ", distanceCm=").append(mDistanceCm).append(", distanceStdDevCm=").append(
-                mDistanceStdDevCm).append(", rssi=").append(mRssi).append(", timestamp=").append(
+                mMac == null ? "<null>" : new String(HexEncoding.encodeToString(mMac))).append(
+                ", peerHandle=").append(mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(
+                ", distanceMm=").append(mDistanceMm).append(", distanceStdDevMm=").append(
+                mDistanceStdDevMm).append(", rssi=").append(mRssi).append(", timestamp=").append(
                 mTimestamp).append("]").toString();
     }
 
@@ -182,13 +259,15 @@
 
         RangingResult lhs = (RangingResult) o;
 
-        return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac)
-                && mDistanceCm == lhs.mDistanceCm && mDistanceStdDevCm == lhs.mDistanceStdDevCm
-                && mRssi == lhs.mRssi && mTimestamp == lhs.mTimestamp;
+        return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac) && Objects.equals(
+                mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm
+                && mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi
+                && mTimestamp == lhs.mTimestamp;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mStatus, mMac, mDistanceCm, mDistanceStdDevCm, mRssi, mTimestamp);
+        return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi,
+                mTimestamp);
     }
-}
\ No newline at end of file
+}
diff --git a/wifi/java/android/net/wifi/rtt/RangingResultCallback.java b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
index d7270ad..7405e82 100644
--- a/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
+++ b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
@@ -16,35 +16,43 @@
 
 package android.net.wifi.rtt;
 
+import android.annotation.IntDef;
 import android.os.Handler;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
  * Base class for ranging result callbacks. Should be extended by applications and set when calling
- * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. A single
- * result from a range request will be called in this object.
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. If the
+ * ranging operation fails in whole (not attempted) then {@link #onRangingFailure(int)} will be
+ * called with a failure code. If the ranging operation is performed for each of the requested
+ * peers then the {@link #onRangingResults(List)} will be called with the set of results (@link
+ * {@link RangingResult}, each of which has its own success/failure code
+ * {@link RangingResult#getStatus()}.
  *
  * @hide RTT_API
  */
 public abstract class RangingResultCallback {
-    /**
-     * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
-     * operation was successful and distance value is valid.
-     */
-    public static final int STATUS_SUCCESS = 0;
+    /** @hide */
+    @IntDef({STATUS_CODE_FAIL})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RangingOperationStatus {
+    }
 
     /**
-     * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
-     * operation failed and the distance value is invalid.
+     * A failure code for the whole ranging request operation. Indicates a failure.
      */
-    public static final int STATUS_FAIL = 1;
+    public static final int STATUS_CODE_FAIL = 1;
 
     /**
      * Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
      * devices specified in the request was attempted.
+     *
+     * @param code A status code indicating the type of failure.
      */
-    public abstract void onRangingFailure();
+    public abstract void onRangingFailure(@RangingOperationStatus int code);
 
     /**
      * Called when a ranging operation was executed. The list of results corresponds to devices
diff --git a/wifi/java/android/net/wifi/rtt/WifiRttManager.java b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
index a085de1..435bb37 100644
--- a/wifi/java/android/net/wifi/rtt/WifiRttManager.java
+++ b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
@@ -83,17 +83,18 @@
         }
 
         @Override
-        public void onRangingResults(int status, List<RangingResult> results) throws RemoteException {
-            if (VDBG) {
-                Log.v(TAG, "RttCallbackProxy: onRanginResults: status=" + status + ", results="
-                        + results);
-            }
+        public void onRangingFailure(int status) throws RemoteException {
+            if (VDBG) Log.v(TAG, "RttCallbackProxy: onRangingFailure: status=" + status);
             mHandler.post(() -> {
-               if (status == RangingResultCallback.STATUS_SUCCESS) {
-                   mCallback.onRangingResults(results);
-               } else {
-                   mCallback.onRangingFailure();
-               }
+               mCallback.onRangingFailure(status);
+            });
+        }
+
+        @Override
+        public void onRangingResults(List<RangingResult> results) throws RemoteException {
+            if (VDBG) Log.v(TAG, "RttCallbackProxy: onRanginResults: results=" + results);
+            mHandler.post(() -> {
+               mCallback.onRangingResults(results);
             });
         }
     }
diff --git a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
index 23c75ce..33bd982 100644
--- a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
@@ -26,6 +26,7 @@
 
 import android.content.Context;
 import android.net.wifi.ScanResult;
+import android.net.wifi.aware.PeerHandle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -78,7 +79,7 @@
     public void testRangeSuccess() throws Exception {
         RangingRequest request = new RangingRequest.Builder().build();
         List<RangingResult> results = new ArrayList<>();
-        results.add(new RangingResult(RangingResultCallback.STATUS_SUCCESS, null, 15, 5, 10, 666));
+        results.add(new RangingResult(RangingResult.STATUS_SUCCESS, (byte[]) null, 15, 5, 10, 666));
         RangingResultCallback callbackMock = mock(RangingResultCallback.class);
         ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
 
@@ -88,7 +89,7 @@
                 callbackCaptor.capture());
 
         // service calls back with success
-        callbackCaptor.getValue().onRangingResults(RangingResultCallback.STATUS_SUCCESS, results);
+        callbackCaptor.getValue().onRangingResults(results);
         mMockLooper.dispatchAll();
         verify(callbackMock).onRangingResults(results);
 
@@ -100,6 +101,8 @@
      */
     @Test
     public void testRangeFail() throws Exception {
+        int failureCode = RangingResultCallback.STATUS_CODE_FAIL;
+
         RangingRequest request = new RangingRequest.Builder().build();
         RangingResultCallback callbackMock = mock(RangingResultCallback.class);
         ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
@@ -110,9 +113,9 @@
                 callbackCaptor.capture());
 
         // service calls back with failure code
-        callbackCaptor.getValue().onRangingResults(RangingResultCallback.STATUS_FAIL, null);
+        callbackCaptor.getValue().onRangingFailure(failureCode);
         mMockLooper.dispatchAll();
-        verify(callbackMock).onRangingFailure();
+        verify(callbackMock).onRangingFailure(failureCode);
 
         verifyNoMoreInteractions(mockRttService, callbackMock);
     }
@@ -132,10 +135,14 @@
         List<ScanResult> scanResults2and3 = new ArrayList<>(2);
         scanResults2and3.add(scanResult2);
         scanResults2and3.add(scanResult3);
+        final byte[] mac1 = HexEncoding.decode("000102030405".toCharArray(), false);
+        PeerHandle peerHandle1 = new PeerHandle(12);
 
         RangingRequest.Builder builder = new RangingRequest.Builder();
-        builder.addAp(scanResult1);
-        builder.addAps(scanResults2and3);
+        builder.addAccessPoint(scanResult1);
+        builder.addAccessPoints(scanResults2and3);
+        builder.addWifiAwarePeer(mac1);
+        builder.addWifiAwarePeer(peerHandle1);
         RangingRequest request = builder.build();
 
         Parcel parcelW = Parcel.obtain();
@@ -157,20 +164,23 @@
     @Test
     public void testRangingRequestAtLimit() {
         ScanResult scanResult = new ScanResult();
+        scanResult.BSSID = "AA:BB:CC:DD:EE:FF";
         List<ScanResult> scanResultList = new ArrayList<>();
-        for (int i = 0; i < RangingRequest.getMaxPeers() - 2; ++i) {
+        for (int i = 0; i < RangingRequest.getMaxPeers() - 3; ++i) {
             scanResultList.add(scanResult);
         }
+        final byte[] mac1 = HexEncoding.decode("000102030405".toCharArray(), false);
 
         // create request
         RangingRequest.Builder builder = new RangingRequest.Builder();
-        builder.addAp(scanResult);
-        builder.addAps(scanResultList);
-        builder.addAp(scanResult);
+        builder.addAccessPoint(scanResult);
+        builder.addAccessPoints(scanResultList);
+        builder.addAccessPoint(scanResult);
+        builder.addWifiAwarePeer(mac1);
         RangingRequest request = builder.build();
 
         // verify request
-        request.enforceValidity();
+        request.enforceValidity(true);
     }
 
     /**
@@ -180,19 +190,35 @@
     public void testRangingRequestPastLimit() {
         ScanResult scanResult = new ScanResult();
         List<ScanResult> scanResultList = new ArrayList<>();
-        for (int i = 0; i < RangingRequest.getMaxPeers() - 1; ++i) {
+        for (int i = 0; i < RangingRequest.getMaxPeers() - 2; ++i) {
             scanResultList.add(scanResult);
         }
+        final byte[] mac1 = HexEncoding.decode("000102030405".toCharArray(), false);
 
         // create request
         RangingRequest.Builder builder = new RangingRequest.Builder();
-        builder.addAp(scanResult);
-        builder.addAps(scanResultList);
-        builder.addAp(scanResult);
+        builder.addAccessPoint(scanResult);
+        builder.addAccessPoints(scanResultList);
+        builder.addAccessPoint(scanResult);
+        builder.addWifiAwarePeer(mac1);
         RangingRequest request = builder.build();
 
         // verify request
-        request.enforceValidity();
+        request.enforceValidity(true);
+    }
+
+    /**
+     * Validate that Aware requests are invalid on devices which do not support Aware
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRangingRequestWithAwareWithNoAwareSupport() {
+        // create request
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addWifiAwarePeer(new PeerHandle(10));
+        RangingRequest request = builder.build();
+
+        // verify request
+        request.enforceValidity(false);
     }
 
     /**
@@ -201,13 +227,15 @@
     @Test
     public void testRangingResultsParcel() {
         // Note: not validating parcel code of ScanResult (assumed to work)
-        int status = RangingResultCallback.STATUS_SUCCESS;
+        int status = RangingResult.STATUS_SUCCESS;
         final byte[] mac = HexEncoding.decode("000102030405".toCharArray(), false);
+        PeerHandle peerHandle = new PeerHandle(10);
         int distanceCm = 105;
         int distanceStdDevCm = 10;
         int rssi = 5;
         long timestamp = System.currentTimeMillis();
 
+        // RangingResults constructed with a MAC address
         RangingResult result = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
                 timestamp);
 
@@ -222,5 +250,21 @@
         RangingResult rereadResult = RangingResult.CREATOR.createFromParcel(parcelR);
 
         assertEquals(result, rereadResult);
+
+        // RangingResults constructed with a PeerHandle
+        result = new RangingResult(status, peerHandle, distanceCm, distanceStdDevCm, rssi,
+                timestamp);
+
+        parcelW = Parcel.obtain();
+        result.writeToParcel(parcelW, 0);
+        bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        rereadResult = RangingResult.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(result, rereadResult);
     }
 }