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, ¤t_entry,
- ¤t_config, ¤t_flags)) {
+
+ FindEntryResult current_entry;
+ if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, ¤t_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(©ing_adaptor, 1u);
pb::ResourceTable pb_table;
SerializeTableToPb(table, &pb_table);
- if (!pb_table.SerializeToZeroCopyStream(©ing_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(©ing_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(©ing_adaptor);
-
- // Number of CompiledFiles.
- output_stream.WriteLittleEndian32(1);
+ ContainerWriter container_writer(©ing_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(©ing_adaptor);
+ ContainerWriter container_writer(©ing_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