Merge "Made creation of multiple stacks more intentional."
diff --git a/api/current.txt b/api/current.txt
index 519f987..8463260 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -37283,10 +37283,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
@@ -37307,6 +37312,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 {
@@ -37315,6 +37321,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);
   }
@@ -52062,6 +52069,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 bbef471..9a59503 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -40385,10 +40385,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
@@ -40409,6 +40414,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 {
@@ -40417,6 +40423,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);
   }
@@ -56165,6 +56172,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 57772de..965dc72 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -37575,10 +37575,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
@@ -37599,6 +37604,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 {
@@ -37607,6 +37613,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);
   }
@@ -52670,6 +52677,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/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b435074..6f89644 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9254,6 +9254,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/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 3b719ac..d6c0dbf 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
@@ -136,9 +144,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 +196,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 +237,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 +279,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 +484,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..d91cebb 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,6 +65,7 @@
     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) {
@@ -59,6 +76,7 @@
         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.
      *
@@ -127,6 +150,7 @@
         private IntentSender mAuthentication;
         private AutofillId[] mAuthenticationIds;
         private AutofillId[] mIgnoredIds;
+        private int mFlags;
         private boolean mDestroyed;
 
         /**
@@ -269,6 +293,19 @@
         }
 
         /**
+         * 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;
+        }
+
+        /**
          * Builds a new {@link FillResponse} instance.
          *
          * <p>You must provide at least one dataset or some savable ids or an authentication with a
@@ -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/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/widget/Editor.java b/core/java/android/widget/Editor.java
index d4be7e5..af09592 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3899,7 +3899,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..51277e4 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -21,6 +21,7 @@
 import android.appwidget.AppWidgetManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -115,6 +116,12 @@
     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.
      */
@@ -309,6 +316,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 +368,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 +395,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 +409,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;
         }
     }
 
@@ -534,7 +529,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();
 
@@ -994,6 +989,20 @@
             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();
         RemoteViewsMetaData metaData = mCache.getMetaData();
         boolean viewTypeInRange;
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_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_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/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/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 6c32590..0b768f8 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -181,6 +181,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/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/libs/androidfw/tests/data/unverified/R.h b/libs/androidfw/tests/data/unverified/R.h
new file mode 100644
index 0000000..b734b49
--- /dev/null
+++ b/libs/androidfw/tests/data/unverified/R.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TESTS_DATA_UNVERIFIED_R_H_
+#define TESTS_DATA_UNVERIFIED_R_H_
+
+#include <cstdint>
+
+#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/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/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 880f236..075c741e7 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 109fe0b..59fc34d 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,194 @@
     }
 
     /**
+     * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
+     * when necessary.
+     */
+    public void logContextCommittedLocked() {
+        if (mResponses == null) {
+            if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): skipped (no responses)");
+            return;
+        }
+
+        final FillResponse lastResponse = mResponses.valueAt(mResponses.size() -1);
+        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.
@@ -1601,7 +1791,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();
@@ -1677,6 +1870,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/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/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/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index f964dfe..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",
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 6083160..082fd86 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -292,6 +292,7 @@
 FileReference* FileReference::Clone(StringPool* new_pool) const {
   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/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/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index a5e6aefd1..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/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