Merge "More APIs for encryption-aware apps."
diff --git a/core/java/android/security/net/config/ApplicationConfig.java b/core/java/android/security/net/config/ApplicationConfig.java
index c675352..9bf344a 100644
--- a/core/java/android/security/net/config/ApplicationConfig.java
+++ b/core/java/android/security/net/config/ApplicationConfig.java
@@ -48,7 +48,7 @@
*/
public boolean hasPerDomainConfigs() {
ensureInitialized();
- return mConfigs == null || !mConfigs.isEmpty();
+ return mConfigs != null && !mConfigs.isEmpty();
}
/**
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 915fbef..1c787b4 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -17,6 +17,9 @@
package android.security.net.config;
import android.util.ArraySet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -26,6 +29,12 @@
* @hide
*/
public final class NetworkSecurityConfig {
+ /** @hide */
+ public static final boolean DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED = true;
+ /** @hide */
+ public static final boolean DEFAULT_HSTS_ENFORCED = false;
+ public static final NetworkSecurityConfig DEFAULT = getDefaultBuilder().build();
+
private final boolean mCleartextTrafficPermitted;
private final boolean mHstsEnforced;
private final PinSet mPins;
@@ -35,7 +44,7 @@
private X509TrustManager mTrustManager;
private final Object mTrustManagerLock = new Object();
- public NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced,
+ private NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced,
PinSet pins, List<CertificatesEntryRef> certificatesEntryRefs) {
mCleartextTrafficPermitted = cleartextTrafficPermitted;
mHstsEnforced = hstsEnforced;
@@ -83,4 +92,151 @@
mAnchors = null;
}
}
+
+ /**
+ * Return a {@link Builder} for the default {@code NetworkSecurityConfig}.
+ *
+ * <p>
+ * The default configuration has the following properties:
+ * <ol>
+ * <li>Cleartext traffic is permitted.</li>
+ * <li>HSTS is not enforced.</li>
+ * <li>No certificate pinning is used.</li>
+ * <li>The system and user added trusted certificate stores are trusted for connections.</li>
+ * </ol>
+ *
+ * @hide
+ */
+ public static final Builder getDefaultBuilder() {
+ return new Builder()
+ .setCleartextTrafficPermitted(DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED)
+ .setHstsEnforced(DEFAULT_HSTS_ENFORCED)
+ // System certificate store, does not bypass static pins.
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ // User certificate store, does not bypass static pins.
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
+ }
+
+ /**
+ * Builder for creating {@code NetworkSecurityConfig} objects.
+ * @hide
+ */
+ public static final class Builder {
+ private List<CertificatesEntryRef> mCertificatesEntryRefs;
+ private PinSet mPinSet;
+ private boolean mCleartextTrafficPermitted = DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED;
+ private boolean mHstsEnforced = DEFAULT_HSTS_ENFORCED;
+ private boolean mCleartextTrafficPermittedSet = false;
+ private boolean mHstsEnforcedSet = false;
+ private Builder mParentBuilder;
+
+ /**
+ * Sets the parent {@code Builder} for this {@code Builder}.
+ * The parent will be used to determine values not configured in this {@code Builder}
+ * in {@link Builder#build()}, recursively if needed.
+ */
+ public Builder setParent(Builder parent) {
+ // Sanity check to avoid adding loops.
+ Builder current = parent;
+ while (current != null) {
+ if (current == this) {
+ throw new IllegalArgumentException("Loops are not allowed in Builder parents");
+ }
+ current = current.getParent();
+ }
+ mParentBuilder = parent;
+ return this;
+ }
+
+ public Builder getParent() {
+ return mParentBuilder;
+ }
+
+ public Builder setPinSet(PinSet pinSet) {
+ mPinSet = pinSet;
+ return this;
+ }
+
+ private PinSet getEffectivePinSet() {
+ if (mPinSet != null) {
+ return mPinSet;
+ }
+ if (mParentBuilder != null) {
+ return mParentBuilder.getEffectivePinSet();
+ }
+ return PinSet.EMPTY_PINSET;
+ }
+
+ public Builder setCleartextTrafficPermitted(boolean cleartextTrafficPermitted) {
+ mCleartextTrafficPermitted = cleartextTrafficPermitted;
+ mCleartextTrafficPermittedSet = true;
+ return this;
+ }
+
+ private boolean getEffectiveCleartextTrafficPermitted() {
+ if (mCleartextTrafficPermittedSet) {
+ return mCleartextTrafficPermitted;
+ }
+ if (mParentBuilder != null) {
+ return mParentBuilder.getEffectiveCleartextTrafficPermitted();
+ }
+ return DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED;
+ }
+
+ public Builder setHstsEnforced(boolean hstsEnforced) {
+ mHstsEnforced = hstsEnforced;
+ mHstsEnforcedSet = true;
+ return this;
+ }
+
+ private boolean getEffectiveHstsEnforced() {
+ if (mHstsEnforcedSet) {
+ return mHstsEnforced;
+ }
+ if (mParentBuilder != null) {
+ return mParentBuilder.getEffectiveHstsEnforced();
+ }
+ return DEFAULT_HSTS_ENFORCED;
+ }
+
+ public Builder addCertificatesEntryRef(CertificatesEntryRef ref) {
+ if (mCertificatesEntryRefs == null) {
+ mCertificatesEntryRefs = new ArrayList<CertificatesEntryRef>();
+ }
+ mCertificatesEntryRefs.add(ref);
+ return this;
+ }
+
+ public Builder addCertificatesEntryRefs(Collection<? extends CertificatesEntryRef> refs) {
+ if (mCertificatesEntryRefs == null) {
+ mCertificatesEntryRefs = new ArrayList<CertificatesEntryRef>();
+ }
+ mCertificatesEntryRefs.addAll(refs);
+ return this;
+ }
+
+ private List<CertificatesEntryRef> getEffectiveCertificatesEntryRefs() {
+ if (mCertificatesEntryRefs != null) {
+ return mCertificatesEntryRefs;
+ }
+ if (mParentBuilder != null) {
+ return mParentBuilder.getEffectiveCertificatesEntryRefs();
+ }
+ return Collections.<CertificatesEntryRef>emptyList();
+ }
+
+ public boolean hasCertificateEntryRefs() {
+ return mCertificatesEntryRefs != null;
+ }
+
+ public NetworkSecurityConfig build() {
+ boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted();
+ boolean hstsEnforced = getEffectiveHstsEnforced();
+ PinSet pinSet = getEffectivePinSet();
+ List<CertificatesEntryRef> entryRefs = getEffectiveCertificatesEntryRefs();
+ return new NetworkSecurityConfig(cleartextPermitted, hstsEnforced, pinSet, entryRefs);
+ }
+ }
}
diff --git a/core/java/android/security/net/config/Pin.java b/core/java/android/security/net/config/Pin.java
index 8567431..94520e2 100644
--- a/core/java/android/security/net/config/Pin.java
+++ b/core/java/android/security/net/config/Pin.java
@@ -30,6 +30,26 @@
this.digest = digest;
mHashCode = Arrays.hashCode(digest) ^ digestAlgorithm.hashCode();
}
+
+ /**
+ * @hide
+ */
+ public static boolean isSupportedDigestAlgorithm(String algorithm) {
+ // Currently only SHA-256 is supported. SHA-512 if/once Chromium networking stack
+ // supports it.
+ return "SHA-256".equalsIgnoreCase(algorithm);
+ }
+
+ /**
+ * @hide
+ */
+ public static int getDigestLength(String algorithm) {
+ if ("SHA-256".equalsIgnoreCase(algorithm)) {
+ return 32;
+ }
+ throw new IllegalArgumentException("Unsupported digest algorithm: " + algorithm);
+ }
+
@Override
public int hashCode() {
return mHashCode;
diff --git a/core/java/android/security/net/config/PinSet.java b/core/java/android/security/net/config/PinSet.java
index a9ee039..d3c975e 100644
--- a/core/java/android/security/net/config/PinSet.java
+++ b/core/java/android/security/net/config/PinSet.java
@@ -17,10 +17,13 @@
package android.security.net.config;
import android.util.ArraySet;
+import java.util.Collections;
import java.util.Set;
/** @hide */
public final class PinSet {
+ public static final PinSet EMPTY_PINSET =
+ new PinSet(Collections.<Pin>emptySet(), Long.MAX_VALUE);
public final long expirationTime;
public final Set<Pin> pins;
diff --git a/core/java/android/security/net/config/SystemCertificateSource.java b/core/java/android/security/net/config/SystemCertificateSource.java
index 640ebd9..7649a97 100644
--- a/core/java/android/security/net/config/SystemCertificateSource.java
+++ b/core/java/android/security/net/config/SystemCertificateSource.java
@@ -36,18 +36,23 @@
* @hide
*/
public class SystemCertificateSource implements CertificateSource {
- private static Set<X509Certificate> sSystemCerts = null;
- private static final Object sLock = new Object();
+ private static final SystemCertificateSource INSTANCE = new SystemCertificateSource();
+ private Set<X509Certificate> mSystemCerts = null;
+ private final Object mLock = new Object();
- public SystemCertificateSource() {
+ private SystemCertificateSource() {
+ }
+
+ public static SystemCertificateSource getInstance() {
+ return INSTANCE;
}
@Override
public Set<X509Certificate> getCertificates() {
// TODO: loading all of these is wasteful, we should instead use a keystore style API.
- synchronized (sLock) {
- if (sSystemCerts != null) {
- return sSystemCerts;
+ synchronized (mLock) {
+ if (mSystemCerts != null) {
+ return mSystemCerts;
}
CertificateFactory certFactory;
try {
@@ -83,14 +88,14 @@
IoUtils.closeQuietly(is);
}
}
- sSystemCerts = systemCerts;
- return sSystemCerts;
+ mSystemCerts = systemCerts;
+ return mSystemCerts;
}
}
public void onCertificateStorageChange() {
- synchronized (sLock) {
- sSystemCerts = null;
+ synchronized (mLock) {
+ mSystemCerts = null;
}
}
}
diff --git a/core/java/android/security/net/config/UserCertificateSource.java b/core/java/android/security/net/config/UserCertificateSource.java
index 77e2c88..e9d5aa1 100644
--- a/core/java/android/security/net/config/UserCertificateSource.java
+++ b/core/java/android/security/net/config/UserCertificateSource.java
@@ -36,18 +36,23 @@
* @hide
*/
public class UserCertificateSource implements CertificateSource {
- private static Set<X509Certificate> sUserCerts = null;
- private static final Object sLock = new Object();
+ private static final UserCertificateSource INSTANCE = new UserCertificateSource();
+ private Set<X509Certificate> mUserCerts = null;
+ private final Object mLock = new Object();
- public UserCertificateSource() {
+ private UserCertificateSource() {
+ }
+
+ public static UserCertificateSource getInstance() {
+ return INSTANCE;
}
@Override
public Set<X509Certificate> getCertificates() {
// TODO: loading all of these is wasteful, we should instead use a keystore style API.
- synchronized (sLock) {
- if (sUserCerts != null) {
- return sUserCerts;
+ synchronized (mLock) {
+ if (mUserCerts != null) {
+ return mUserCerts;
}
CertificateFactory certFactory;
try {
@@ -57,32 +62,31 @@
}
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
final File userCaDir = new File(configDir, "cacerts-added");
- if (!userCaDir.isDirectory()) {
- throw new AssertionError(userCaDir + " is not a directory");
- }
-
Set<X509Certificate> userCerts = new ArraySet<X509Certificate>();
- for (String caFile : userCaDir.list()) {
- InputStream is = null;
- try {
- is = new BufferedInputStream(
- new FileInputStream(new File(userCaDir, caFile)));
- userCerts.add((X509Certificate) certFactory.generateCertificate(is));
- } catch (CertificateException | IOException e) {
- // Don't rethrow to be consistent with conscrypt's cert loading code.
- continue;
- } finally {
- IoUtils.closeQuietly(is);
+ // If the user hasn't added any certificates the directory may not exist.
+ if (userCaDir.isDirectory()) {
+ for (String caFile : userCaDir.list()) {
+ InputStream is = null;
+ try {
+ is = new BufferedInputStream(
+ new FileInputStream(new File(userCaDir, caFile)));
+ userCerts.add((X509Certificate) certFactory.generateCertificate(is));
+ } catch (CertificateException | IOException e) {
+ // Don't rethrow to be consistent with conscrypt's cert loading code.
+ continue;
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
}
}
- sUserCerts = userCerts;
- return sUserCerts;
+ mUserCerts = userCerts;
+ return mUserCerts;
}
}
public void onCertificateStorageChange() {
- synchronized (sLock) {
- sUserCerts = null;
+ synchronized (mLock) {
+ mUserCerts = null;
}
}
}
diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java
new file mode 100644
index 0000000..6abfb66
--- /dev/null
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -0,0 +1,328 @@
+package android.security.net.config;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.util.ArraySet;
+import android.util.Base64;
+import android.util.Pair;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * {@link ConfigSource} based on an XML configuration file.
+ *
+ * @hide
+ */
+public class XmlConfigSource implements ConfigSource {
+ private final Object mLock = new Object();
+ private final int mResourceId;
+
+ private boolean mInitialized;
+ private NetworkSecurityConfig mDefaultConfig;
+ private Set<Pair<Domain, NetworkSecurityConfig>> mDomainMap;
+ private Context mContext;
+
+ public XmlConfigSource(Context context, int resourceId) {
+ mResourceId = resourceId;
+ mContext = context;
+ }
+
+ public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+ ensureInitialized();
+ return mDomainMap;
+ }
+
+ public NetworkSecurityConfig getDefaultConfig() {
+ ensureInitialized();
+ return mDefaultConfig;
+ }
+
+ private void ensureInitialized() {
+ synchronized (mLock) {
+ if (mInitialized) {
+ return;
+ }
+ try (XmlResourceParser parser = mContext.getResources().getXml(mResourceId)) {
+ parseNetworkSecurityConfig(parser);
+ mContext = null;
+ mInitialized = true;
+ } catch (Resources.NotFoundException | XmlPullParserException | IOException
+ | ParserException e) {
+ throw new RuntimeException("Failed to parse XML configuration from "
+ + mContext.getResources().getResourceEntryName(mResourceId), e);
+ }
+ }
+ }
+
+ private Pin parsePin(XmlResourceParser parser)
+ throws IOException, XmlPullParserException, ParserException {
+ String digestAlgorithm = parser.getAttributeValue(null, "digest");
+ if (!Pin.isSupportedDigestAlgorithm(digestAlgorithm)) {
+ throw new ParserException(parser, "Unsupported pin digest algorithm: "
+ + digestAlgorithm);
+ }
+ if (parser.next() != XmlPullParser.TEXT) {
+ throw new ParserException(parser, "Missing pin digest");
+ }
+ String digest = parser.getText();
+ byte[] decodedDigest = null;
+ try {
+ decodedDigest = Base64.decode(digest, 0);
+ } catch (IllegalArgumentException e) {
+ throw new ParserException(parser, "Invalid pin digest", e);
+ }
+ int expectedLength = Pin.getDigestLength(digestAlgorithm);
+ if (decodedDigest.length != expectedLength) {
+ throw new ParserException(parser, "digest length " + decodedDigest.length
+ + " does not match expected length for " + digestAlgorithm + " of "
+ + expectedLength);
+ }
+ if (parser.next() != XmlPullParser.END_TAG) {
+ throw new ParserException(parser, "pin contains additional elements");
+ }
+ return new Pin(digestAlgorithm, decodedDigest);
+ }
+
+ private PinSet parsePinSet(XmlResourceParser parser)
+ throws IOException, XmlPullParserException, ParserException {
+ String expirationDate = parser.getAttributeValue(null, "expiration");
+ long expirationTimestampMilis = Long.MAX_VALUE;
+ if (expirationDate != null) {
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ sdf.setLenient(false);
+ Date date = sdf.parse(expirationDate);
+ if (date == null) {
+ throw new ParserException(parser, "Invalid expiration date in pin-set");
+ }
+ expirationTimestampMilis = date.getTime();
+ } catch (ParseException e) {
+ throw new ParserException(parser, "Invalid expiration date in pin-set", e);
+ }
+ }
+
+ int outerDepth = parser.getDepth();
+ Set<Pin> pins = new ArraySet<>();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tagName = parser.getName();
+ if (tagName.equals("pin")) {
+ pins.add(parsePin(parser));
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ return new PinSet(pins, expirationTimestampMilis);
+ }
+
+ private Domain parseDomain(XmlResourceParser parser, Set<String> seenDomains)
+ throws IOException, XmlPullParserException, ParserException {
+ boolean includeSubdomains =
+ parser.getAttributeBooleanValue(null, "includeSubdomains", false);
+ if (parser.next() != XmlPullParser.TEXT) {
+ throw new ParserException(parser, "Domain name missing");
+ }
+ String domain = parser.getText().toLowerCase(Locale.US);
+ if (parser.next() != XmlPullParser.END_TAG) {
+ throw new ParserException(parser, "domain contains additional elements");
+ }
+ // Domains are matched using a most specific match, so don't allow duplicates.
+ // includeSubdomains isn't relevant here, both android.com + subdomains and android.com
+ // match for android.com equally. Do not allow any duplicates period.
+ if (!seenDomains.add(domain)) {
+ throw new ParserException(parser, domain + " has already been specified");
+ }
+ return new Domain(domain, includeSubdomains);
+ }
+
+ private CertificatesEntryRef parseCertificatesEntry(XmlResourceParser parser)
+ throws IOException, XmlPullParserException, ParserException {
+ boolean overridePins = parser.getAttributeBooleanValue(null, "overridePins", false);
+ int sourceId = parser.getAttributeResourceValue(null, "src", -1);
+ String sourceString = parser.getAttributeValue(null, "src");
+ CertificateSource source = null;
+ if (sourceString == null) {
+ throw new ParserException(parser, "certificates element missing src attribute");
+ }
+ if (sourceId != -1) {
+ // TODO: Cache ResourceCertificateSources by sourceId
+ source = new ResourceCertificateSource(sourceId, mContext);
+ } else if ("system".equals(sourceString)) {
+ source = SystemCertificateSource.getInstance();
+ } else if ("user".equals(sourceString)) {
+ source = UserCertificateSource.getInstance();
+ } else {
+ throw new ParserException(parser, "Unknown certificates src. "
+ + "Should be one of system|user|@resourceVal");
+ }
+ XmlUtils.skipCurrentTag(parser);
+ return new CertificatesEntryRef(source, overridePins);
+ }
+
+ private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser)
+ throws IOException, XmlPullParserException, ParserException {
+ int outerDepth = parser.getDepth();
+ List<CertificatesEntryRef> anchors = new ArrayList<>();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tagName = parser.getName();
+ if (tagName.equals("certificates")) {
+ anchors.add(parseCertificatesEntry(parser));
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ return anchors;
+ }
+
+ private List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> parseConfigEntry(
+ XmlResourceParser parser, Set<String> seenDomains,
+ NetworkSecurityConfig.Builder parentBuilder, boolean baseConfig)
+ throws IOException, XmlPullParserException, ParserException {
+ List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>();
+ NetworkSecurityConfig.Builder builder = new NetworkSecurityConfig.Builder();
+ builder.setParent(parentBuilder);
+ Set<Domain> domains = new ArraySet<>();
+ boolean seenPinSet = false;
+ boolean seenTrustAnchors = false;
+ String configName = parser.getName();
+ int outerDepth = parser.getDepth();
+ // Add this builder now so that this builder occurs before any of its children. This
+ // makes the final build pass easier.
+ builders.add(new Pair<>(builder, domains));
+ // Parse config attributes. Only set values that are present, config inheritence will
+ // handle the rest.
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ String name = parser.getAttributeName(i);
+ if ("hstsEnforced".equals(name)) {
+ builder.setHstsEnforced(
+ parser.getAttributeBooleanValue(i,
+ NetworkSecurityConfig.DEFAULT_HSTS_ENFORCED));
+ } else if ("cleartextTrafficPermitted".equals(name)) {
+ builder.setCleartextTrafficPermitted(
+ parser.getAttributeBooleanValue(i,
+ NetworkSecurityConfig.DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED));
+ }
+ }
+ // Parse the config elements.
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tagName = parser.getName();
+ if ("domain".equals(tagName)) {
+ if (baseConfig) {
+ throw new ParserException(parser, "domain element not allowed in base-config");
+ }
+ Domain domain = parseDomain(parser, seenDomains);
+ domains.add(domain);
+ } else if ("trust-anchors".equals(tagName)) {
+ if (seenTrustAnchors) {
+ throw new ParserException(parser,
+ "Multiple trust-anchor elements not allowed");
+ }
+ builder.addCertificatesEntryRefs(parseTrustAnchors(parser));
+ seenTrustAnchors = true;
+ } else if ("pin-set".equals(tagName)) {
+ if (baseConfig) {
+ throw new ParserException(parser,
+ "pin-set element not allowed in base-config");
+ }
+ if (seenPinSet) {
+ throw new ParserException(parser, "Multiple pin-set elements not allowed");
+ }
+ builder.setPinSet(parsePinSet(parser));
+ seenPinSet = true;
+ } else if ("domain-config".equals(tagName)) {
+ if (baseConfig) {
+ throw new ParserException(parser,
+ "Nested domain-config not allowed in base-config");
+ }
+ builders.addAll(parseConfigEntry(parser, seenDomains, builder, false));
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ if (!baseConfig && domains.isEmpty()) {
+ throw new ParserException(parser, "No domain elements in domain-config");
+ }
+ return builders;
+ }
+
+ private void parseNetworkSecurityConfig(XmlResourceParser parser)
+ throws IOException, XmlPullParserException, ParserException {
+ Set<String> seenDomains = new ArraySet<>();
+ List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>();
+ NetworkSecurityConfig.Builder baseConfigBuilder = null;
+ boolean seenDebugOverrides = false;
+ boolean seenBaseConfig = false;
+
+ XmlUtils.beginDocument(parser, "network-security-config");
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ // TODO: support debug-override.
+ if ("base-config".equals(parser.getName())) {
+ if (seenBaseConfig) {
+ throw new ParserException(parser, "Only one base-config allowed");
+ }
+ seenBaseConfig = true;
+ baseConfigBuilder = parseConfigEntry(parser, seenDomains, null, true).get(0).first;
+ } else if ("domain-config".equals(parser.getName())) {
+ builders.addAll(parseConfigEntry(parser, seenDomains, baseConfigBuilder, false));
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ // Use the platform default as the parent of the base config for any values not provided
+ // there. If there is no base config use the platform default.
+ NetworkSecurityConfig.Builder platformDefaultBuilder =
+ NetworkSecurityConfig.getDefaultBuilder();
+ if (baseConfigBuilder != null) {
+ baseConfigBuilder.setParent(platformDefaultBuilder);
+ } else {
+ baseConfigBuilder = platformDefaultBuilder;
+ }
+ // Build the per-domain config mapping.
+ Set<Pair<Domain, NetworkSecurityConfig>> configs = new ArraySet<>();
+
+ for (Pair<NetworkSecurityConfig.Builder, Set<Domain>> entry : builders) {
+ NetworkSecurityConfig.Builder builder = entry.first;
+ Set<Domain> domains = entry.second;
+ // Set the parent of configs that do not have a parent to the base-config. This can
+ // happen if the base-config comes after a domain-config in the file.
+ // Note that this is safe with regards to children because of the order that
+ // parseConfigEntry returns builders, the parent is always before the children. The
+ // children builders will not have build called until _after_ their parents have their
+ // parent set so everything is consistent.
+ if (builder.getParent() == null) {
+ builder.setParent(baseConfigBuilder);
+ }
+ NetworkSecurityConfig config = builder.build();
+ for (Domain domain : domains) {
+ configs.add(new Pair<>(domain, config));
+ }
+ }
+ mDefaultConfig = baseConfigBuilder.build();
+ mDomainMap = configs;
+ }
+
+ public static class ParserException extends Exception {
+
+ public ParserException(XmlPullParser parser, String message, Throwable cause) {
+ super(message + " at: " + parser.getPositionDescription(), cause);
+ }
+
+ public ParserException(XmlPullParser parser, String message) {
+ this(parser, message, null);
+ }
+ }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 28b22bf7..7b5f5ab 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -219,6 +219,11 @@
*/
void cancelTaskWindowTransition(int taskId);
+ /**
+ * Cancels the thumbnail transitions for the given task.
+ */
+ void cancelTaskThumbnailTransition(int taskId);
+
// These can only be called with the SET_ORIENTATION permission.
/**
* Update the current screen rotation based on the current state of
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 10d862e..d05c66a 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -144,7 +144,7 @@
mAlpha = alpha;
if (mCurrDrawable != null) {
if (mEnterAnimationEnd == 0) {
- mCurrDrawable.mutate().setAlpha(alpha);
+ mCurrDrawable.setAlpha(alpha);
} else {
animate(false);
}
@@ -162,7 +162,7 @@
if (mDrawableContainerState.mDither != dither) {
mDrawableContainerState.mDither = dither;
if (mCurrDrawable != null) {
- mCurrDrawable.mutate().setDither(mDrawableContainerState.mDither);
+ mCurrDrawable.setDither(mDrawableContainerState.mDither);
}
}
}
@@ -175,7 +175,7 @@
mDrawableContainerState.mColorFilter = colorFilter;
if (mCurrDrawable != null) {
- mCurrDrawable.mutate().setColorFilter(colorFilter);
+ mCurrDrawable.setColorFilter(colorFilter);
}
}
}
@@ -188,7 +188,7 @@
mDrawableContainerState.mTintList = tint;
if (mCurrDrawable != null) {
- mCurrDrawable.mutate().setTintList(tint);
+ mCurrDrawable.setTintList(tint);
}
}
}
@@ -201,7 +201,7 @@
mDrawableContainerState.mTintMode = tintMode;
if (mCurrDrawable != null) {
- mCurrDrawable.mutate().setTintMode(tintMode);
+ mCurrDrawable.setTintMode(tintMode);
}
}
}
@@ -244,7 +244,7 @@
if (mDrawableContainerState.mAutoMirrored != mirrored) {
mDrawableContainerState.mAutoMirrored = mirrored;
if (mCurrDrawable != null) {
- mCurrDrawable.mutate().setAutoMirrored(mDrawableContainerState.mAutoMirrored);
+ mCurrDrawable.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
}
}
}
@@ -266,7 +266,7 @@
if (mCurrDrawable != null) {
mCurrDrawable.jumpToCurrentState();
if (mHasAlpha) {
- mCurrDrawable.mutate().setAlpha(mAlpha);
+ mCurrDrawable.setAlpha(mAlpha);
}
}
if (mExitAnimationEnd != 0) {
@@ -499,8 +499,6 @@
* @param d The drawable to initialize.
*/
private void initializeDrawableForDisplay(Drawable d) {
- d.mutate();
-
if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) {
d.setAlpha(mAlpha);
}
@@ -540,13 +538,12 @@
if (mCurrDrawable != null) {
if (mEnterAnimationEnd != 0) {
if (mEnterAnimationEnd <= now) {
- mCurrDrawable.mutate().setAlpha(mAlpha);
+ mCurrDrawable.setAlpha(mAlpha);
mEnterAnimationEnd = 0;
} else {
int animAlpha = (int)((mEnterAnimationEnd-now)*255)
/ mDrawableContainerState.mEnterFadeDuration;
- if (DEBUG) android.util.Log.i(TAG, toString() + " cur alpha " + animAlpha);
- mCurrDrawable.mutate().setAlpha(((255-animAlpha)*mAlpha)/255);
+ mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
animating = true;
}
}
@@ -563,8 +560,7 @@
} else {
int animAlpha = (int)((mExitAnimationEnd-now)*255)
/ mDrawableContainerState.mExitFadeDuration;
- if (DEBUG) android.util.Log.i(TAG, toString() + " last alpha " + animAlpha);
- mLastDrawable.mutate().setAlpha((animAlpha*mAlpha)/255);
+ mLastDrawable.setAlpha((animAlpha*mAlpha)/255);
animating = true;
}
}
@@ -832,9 +828,7 @@
private Drawable prepareDrawable(Drawable child) {
child.setLayoutDirection(mLayoutDirection);
child.setCallback(mOwner);
- if (mMutated) {
- child = child.mutate();
- }
+ child = child.mutate();
return child;
}
diff --git a/libs/androidfw/tests/AppAsLib_test.cpp b/libs/androidfw/tests/AppAsLib_test.cpp
index bdb0c3d..8489acf 100644
--- a/libs/androidfw/tests/AppAsLib_test.cpp
+++ b/libs/androidfw/tests/AppAsLib_test.cpp
@@ -16,7 +16,6 @@
#include <androidfw/ResourceTypes.h>
-#include "data/basic/R.h"
#include "data/appaslib/R.h"
#include <gtest/gtest.h>
@@ -25,29 +24,45 @@
namespace {
-#include "data/basic/basic_arsc.h"
+#include "data/appaslib/appaslib_arsc.h"
+#include "data/appaslib/appaslib_lib_arsc.h"
+// This tests the app resources loaded as app.
TEST(AppAsLibTest, loadedAsApp) {
ResTable table;
- ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+ ASSERT_EQ(NO_ERROR, table.add(appaslib_arsc, appaslib_arsc_len));
Res_value val;
- ssize_t block = table.getResource(base::R::integer::number2, &val);
+ ssize_t block = table.getResource(appaslib::R::app::integer::number1, &val);
ASSERT_GE(block, 0);
ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
- ASSERT_EQ(base::R::array::integerArray1, val.data);
+ ASSERT_EQ(appaslib::R::app::array::integerArray1, val.data);
}
+// This tests the app resources loaded as shared-lib.
TEST(AppAsLibTest, loadedAsSharedLib) {
ResTable table;
// Load as shared library.
- ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len, NULL, 0, -1, false, true));
+ ASSERT_EQ(NO_ERROR, table.add(appaslib_arsc, appaslib_arsc_len, NULL, 0, -1, false, true));
Res_value val;
- ssize_t block = table.getResource(appaslib::R::integer::number2, &val);
+ ssize_t block = table.getResource(appaslib::R::lib::integer::number1, &val);
ASSERT_GE(block, 0);
ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
- ASSERT_EQ(appaslib::R::array::integerArray1, val.data);
+ ASSERT_EQ(appaslib::R::lib::array::integerArray1, val.data);
+}
+
+// This tests the shared-lib loaded with appAsLib as true.
+TEST(AppAsLibTest, loadedSharedLib) {
+ ResTable table;
+ // Load shared library with appAsLib as true.
+ ASSERT_EQ(NO_ERROR, table.add(appaslib_lib_arsc, appaslib_lib_arsc_len, NULL, 0, -1, false, true));
+
+ Res_value val;
+ ssize_t block = table.getResource(appaslib::R::lib::integer::number1, &val);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
+ ASSERT_EQ(appaslib::R::lib::array::integerArray1, val.data);
}
}
diff --git a/libs/androidfw/tests/data/appaslib/AndroidManifest.xml b/libs/androidfw/tests/data/appaslib/AndroidManifest.xml
new file mode 100644
index 0000000..e00045b
--- /dev/null
+++ b/libs/androidfw/tests/data/appaslib/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.basic">
+ <application>
+ </application>
+</manifest>
diff --git a/libs/androidfw/tests/data/appaslib/R.h b/libs/androidfw/tests/data/appaslib/R.h
index f89d4bf..3af921a 100644
--- a/libs/androidfw/tests/data/appaslib/R.h
+++ b/libs/androidfw/tests/data/appaslib/R.h
@@ -19,19 +19,33 @@
namespace appaslib {
namespace R {
-
+namespace lib {
namespace integer {
enum {
- number2 = 0x02040001, // default
+ number1 = 0x02020000, // default
};
}
namespace array {
enum {
- integerArray1 = 0x02060000, // default
+ integerArray1 = 0x02030000, // default
+ };
+}
+} // namespace lib
+
+namespace app {
+namespace integer {
+ enum {
+ number1 = 0x7f020000, // default
};
}
+namespace array {
+ enum {
+ integerArray1 = 0x7f030000, // default
+ };
+}
+} // namespace app
} // namespace R
} // namespace appaslib
diff --git a/libs/androidfw/tests/data/appaslib/appaslib_arsc.h b/libs/androidfw/tests/data/appaslib/appaslib_arsc.h
new file mode 100644
index 0000000..be176ab
--- /dev/null
+++ b/libs/androidfw/tests/data/appaslib/appaslib_arsc.h
@@ -0,0 +1,68 @@
+unsigned char appaslib_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0x04, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xdc, 0x02, 0x00, 0x00,
+ 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00,
+ 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x0d, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00,
+ 0x72, 0x00, 0x41, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x31, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x48, 0x00, 0x5c, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x7f,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x48, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x4c, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x03, 0x00, 0x00, 0x00
+};
+unsigned int appaslib_arsc_len = 772;
diff --git a/libs/androidfw/tests/data/appaslib/appaslib_lib_arsc.h b/libs/androidfw/tests/data/appaslib/appaslib_lib_arsc.h
new file mode 100644
index 0000000..099285a
--- /dev/null
+++ b/libs/androidfw/tests/data/appaslib/appaslib_lib_arsc.h
@@ -0,0 +1,68 @@
+unsigned char appaslib_lib_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0x04, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xdc, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00,
+ 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x0d, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00,
+ 0x72, 0x00, 0x41, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x31, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x48, 0x00, 0x5c, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x07, 0x00, 0x00, 0x03, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x48, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x4c, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x03, 0x00, 0x00, 0x00
+};
+unsigned int appaslib_lib_arsc_len = 772;
diff --git a/libs/androidfw/tests/data/appaslib/build b/libs/androidfw/tests/data/appaslib/build
new file mode 100755
index 0000000..e4bd88b
--- /dev/null
+++ b/libs/androidfw/tests/data/appaslib/build
@@ -0,0 +1,28 @@
+#!/bin/bash
+#
+# Copyright (C) 2015 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.
+#
+
+PATH_TO_FRAMEWORK_RES=$(gettop)/prebuilts/sdk/current/android.jar
+
+aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc appaslib.arsc && \
+xxd -i appaslib.arsc > appaslib_arsc.h && \
+aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES -F bundle.apk -f --shared-lib && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc appaslib_lib.arsc && \
+xxd -i appaslib_lib.arsc > appaslib_lib_arsc.h \
+
diff --git a/libs/androidfw/tests/data/appaslib/res/values/values.xml b/libs/androidfw/tests/data/appaslib/res/values/values.xml
new file mode 100644
index 0000000..39b99a6
--- /dev/null
+++ b/libs/androidfw/tests/data/appaslib/res/values/values.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<resources>
+ <integer name="number1">@array/integerArray1</integer>
+ <integer-array name="integerArray1">
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ </integer-array>
+</resources>
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 57ee319..3d78028 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -603,6 +603,7 @@
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (DEBUG) Log.d(TAG, "received broadcast " + action);
@@ -656,6 +657,7 @@
private final BroadcastReceiver mBroadcastAllReceiver = new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED.equals(action)) {
@@ -788,6 +790,7 @@
return new SimData(state, slotId, subId);
}
+ @Override
public String toString() {
return "SimData{state=" + simState + ",slotId=" + slotId + ",subId=" + subId + "}";
}
@@ -1693,5 +1696,19 @@
for (int subId : mServiceStates.keySet()) {
pw.println(" " + subId + "=" + mServiceStates.get(subId));
}
+ if (mFpm != null && mFpm.isHardwareDetected()) {
+ final int userId = ActivityManager.getCurrentUser();
+ final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
+ pw.println(" Fingerprint state (user=" + userId + ")");
+ pw.println(" allowed=" + isUnlockingWithFingerprintAllowed());
+ pw.println(" auth'd=" + mUserFingerprintAuthenticated.get(userId));
+ pw.println(" authSinceBoot="
+ + getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
+ pw.println(" disabled(DPM)=" + isFingerprintDisabled(userId));
+ pw.println(" possible=" + isUnlockWithFingerprintPossible(userId));
+ pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
+ pw.println(" timedout=" + hasFingerprintUnlockTimedOut(userId));
+ pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index ebfacac..a429447 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -21,6 +21,7 @@
*/
public class Constants {
+ // TODO: Move into RecentsMetrics
public static class Metrics {
// DO NOT MODIFY THE ORDER OF THESE METRICS
public static final int DismissSourceKeyboard = 0;
@@ -28,6 +29,7 @@
public static final int DismissSourceHeaderButton = 2;
}
+ // TODO: Move into RecentsDebugFlags
public static class DebugFlags {
public static class App {
@@ -37,7 +39,7 @@
public static final boolean EnableTaskFiltering = false;
// Enables dismiss-all
public static final boolean EnableDismissAll = false;
- // Enables fast-toggling
+ // Enables fast-toggling by just tapping on the recents button
public static final boolean EnableFastToggleRecents = false;
// Enables the thumbnail alpha on the front-most task
public static final boolean EnableThumbnailAlphaOnFrontmost = false;
@@ -57,14 +59,4 @@
public static final int SystemServicesProxyMockTaskCount = 100;
}
}
-
- public static class Values {
- public static class App {
- public static int AppWidgetHostId = 1024;
- }
-
- public static class TaskStackView {
- public static final int FilterStartDelay = 25;
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index f800785..6874247 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -32,19 +32,20 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewStub;
import android.view.ViewTreeObserver;
-
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.activity.IterateRecentsEvent;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
@@ -57,7 +58,6 @@
import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
-import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -137,8 +137,7 @@
try {
startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(), UserHandle.CURRENT);
} catch (Exception e) {
- Console.logError(RecentsActivity.this,
- getString(R.string.recents_launch_error_message, "Home"));
+ Log.e(TAG, getString(R.string.recents_launch_error_message, "Home"), e);
}
}
}
@@ -345,7 +344,7 @@
// Initialize the widget host (the host id is static and does not change)
if (!Constants.DebugFlags.App.DisableSearchBar) {
- mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
+ mAppWidgetHost = new RecentsAppWidgetHost(this, RecentsAppWidgetHost.HOST_ID);
}
mPackageMonitor = new RecentsPackageMonitor();
mPackageMonitor.register(this);
@@ -511,12 +510,7 @@
if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
// As we iterate to the next/previous task, cancel any current/lagging window
// transition animations
- RecentsConfiguration config = Recents.getConfiguration();
- RecentsActivityLaunchState launchState = config.getLaunchState();
- if (launchState.launchedToTaskId != -1) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.cancelWindowTransition(launchState.launchedToTaskId);
- }
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
// Focus the next task in the stack
final boolean backward = event.isShiftPressed();
@@ -575,10 +569,6 @@
/**** RecentsView.RecentsViewCallbacks Implementation ****/
@Override
- public void onTaskViewClicked() {
- }
-
- @Override
public void onTaskLaunchFailed() {
// Return to Home
dismissRecentsToHome(true);
@@ -657,6 +647,17 @@
mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
}
+ public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
+ RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+ int launchToTaskId = launchState.launchedToTaskId;
+ if (launchToTaskId != -1 &&
+ (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.cancelWindowTransition(launchState.launchedToTaskId);
+ ssp.cancelThumbnailTransition(getTaskId());
+ }
+ }
+
public final void onBusEvent(AppWidgetProviderChangedEvent event) {
refreshSearchWidgetView();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 9ee6f5b..01ffd2a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -19,7 +19,6 @@
/**
* The launch state of the RecentsActivity.
*
- * TODO: We will be refactoring this out RecentsConfiguration.
* Current Constraints:
* - needed in onStart() before onNewIntent()
* - needs to be reset when Recents is hidden
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
index fc96c11..573db98 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
@@ -26,6 +26,8 @@
/** Our special app widget host for the Search widget */
public class RecentsAppWidgetHost extends AppWidgetHost {
+ public static final int HOST_ID = 1024;
+
boolean mIsListening;
public RecentsAppWidgetHost(Context context, int hostId) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 675ef2f..db65e00 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -16,8 +16,6 @@
package com.android.systemui.recents;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ITaskStackListener;
@@ -32,24 +30,24 @@
import android.os.Handler;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.util.Log;
import android.util.MutableBoolean;
import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.View;
-
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.activity.IterateRecentsEvent;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.ForegroundThread;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsTaskLoadPlan;
@@ -57,14 +55,16 @@
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskGrouping;
import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskStackView;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
+import com.android.systemui.recents.views.TaskStackView;
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.ArrayList;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+
/**
* An implementation of the Recents component for the current user. For secondary users, this can
* be called remotely from the system user.
@@ -75,7 +75,12 @@
private final static String TAG = "RecentsImpl";
private final static boolean DEBUG = false;
- private final static int sMinToggleDelay = 350;
+ // The minimum amount of time between each recents button press that we will handle
+ private final static int MIN_TOGGLE_DELAY_MS = 350;
+ // The duration within which the user releasing the alt tab (from when they pressed alt tab)
+ // that the fast alt-tab animation will run. If the user's alt-tab takes longer than this
+ // duration, then we will toggle recents after this duration.
+ private final static int FAST_ALT_TAB_DELAY_MS = 225;
public final static String RECENTS_PACKAGE = "com.android.systemui";
public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
@@ -156,6 +161,14 @@
// Variables to keep track of if we need to start recents after binding
boolean mTriggeredFromAltTab;
long mLastToggleTime;
+ DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
+ @Override
+ public void run() {
+ // When this fires, then the user has not released alt-tab for at least
+ // FAST_ALT_TAB_DELAY_MS milliseconds
+ showRecents(mTriggeredFromAltTab);
+ }
+ });
Bitmap mThumbnailTransitionBitmapCache;
Task mThumbnailTransitionBitmapCacheKey;
@@ -164,7 +177,7 @@
public RecentsImpl(Context context) {
mContext = context;
mHandler = new Handler();
- mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId);
+ mAppWidgetHost = new RecentsAppWidgetHost(mContext, RecentsAppWidgetHost.HOST_ID);
Resources res = mContext.getResources();
LayoutInflater inflater = LayoutInflater.from(mContext);
@@ -239,6 +252,29 @@
@Override
public void showRecents(boolean triggeredFromAltTab) {
mTriggeredFromAltTab = triggeredFromAltTab;
+ if (mFastAltTabTrigger.hasTriggered()) {
+ // We are calling this from the doze trigger, so just fall through to show Recents
+ mFastAltTabTrigger.resetTrigger();
+ } else if (mFastAltTabTrigger.isDozing()) {
+ // We are dozing but haven't yet triggered, ignore this if this is not another alt-tab,
+ // otherwise, this is an additional tab (alt-tab*), which means that we should trigger
+ // immediately (fall through and disable the pending trigger)
+ // TODO: This is tricky, we need to handle the tab key, but Recents has not yet started
+ // so we may actually additional signal to handle multiple quick tab cases. The
+ // severity of this is inversely proportional to the FAST_ALT_TAB_DELAY_MS
+ // duration though
+ if (!triggeredFromAltTab) {
+ return;
+ }
+ mFastAltTabTrigger.stopDozing();
+ } else {
+ // Otherwise, the doze trigger is not running, and if this is an alt tab, we should
+ // start the trigger and then wait for the hide (or for it to elapse)
+ if (triggeredFromAltTab) {
+ mFastAltTabTrigger.startDozing();
+ return;
+ }
+ }
try {
// Check if the top task is in the home stack, and start the recents activity
@@ -249,13 +285,24 @@
startRecentsActivity(topTask, isTopTaskHome.value);
}
} catch (ActivityNotFoundException e) {
- Console.logRawError("Failed to launch RecentAppsIntent", e);
+ Log.e(TAG, "Failed to launch RecentsActivity", e);
}
}
@Override
public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mBootCompleted) {
+ if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
+ // The user has released alt-tab before the trigger has run, so just show the next
+ // task immediately
+ showNextTask();
+
+ // Cancel the fast alt-tab trigger
+ mFastAltTabTrigger.stopDozing();
+ mFastAltTabTrigger.resetTrigger();
+ return;
+ }
+
// Defer to the activity to handle hiding recents, if it handles it, then it must still
// be visible
EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
@@ -265,6 +312,11 @@
@Override
public void toggleRecents() {
+ // Skip this toggle if we are already waiting to trigger recents via alt-tab
+ if (mFastAltTabTrigger.isDozing()) {
+ return;
+ }
+
mTriggeredFromAltTab = false;
try {
@@ -283,7 +335,7 @@
// better than showing a janky screenshot).
// NOTE: Ideally, the screenshot mechanism would take the window transform into
// account
- if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
+ if ((SystemClock.elapsedRealtime() - mLastToggleTime) < MIN_TOGGLE_DELAY_MS) {
return;
}
@@ -296,7 +348,7 @@
// better than showing a janky screenshot).
// NOTE: Ideally, the screenshot mechanism would take the window transform into
// account
- if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
+ if ((SystemClock.elapsedRealtime() - mLastToggleTime) < MIN_TOGGLE_DELAY_MS) {
return;
}
@@ -305,7 +357,7 @@
mLastToggleTime = SystemClock.elapsedRealtime();
}
} catch (ActivityNotFoundException e) {
- Console.logRawError("Failed to launch RecentAppsIntent", e);
+ Log.e(TAG, "Failed to launch RecentsActivity", e);
}
}
@@ -335,10 +387,58 @@
// Do nothing
}
- public void showRelativeAffiliatedTask(boolean showNextTask) {
- // Return early if there is no focused stack
+ /**
+ * Transitions to the next recent task in the stack.
+ */
+ public void showNextTask() {
SystemServicesProxy ssp = Recents.getSystemServices();
- int focusedStackId = ssp.getFocusedStack();
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+ RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+ loader.preloadTasks(plan, true /* isTopTaskHome */);
+ TaskStack focusedStack = plan.getTaskStack();
+
+ // Return early if there are no tasks in the focused stack
+ if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
+
+ ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
+ // Return early if there is no running task
+ if (runningTask == null) return;
+ // Return early if the running task is in the home stack (optimization)
+ if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
+
+ // Find the task in the recents list
+ ArrayList<Task> tasks = focusedStack.getTasks();
+ Task toTask = null;
+ ActivityOptions launchOpts = null;
+ int taskCount = tasks.size();
+ for (int i = taskCount - 1; i >= 1; i--) {
+ Task task = tasks.get(i);
+ if (task.key.id == runningTask.id) {
+ toTask = tasks.get(i - 1);
+ launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.recents_launch_prev_affiliated_task_target,
+ R.anim.recents_launch_prev_affiliated_task_source);
+ break;
+ }
+ }
+
+ // Return early if there is no next task
+ if (toTask == null) {
+ ssp.startInPlaceAnimationOnFrontMostApplication(
+ ActivityOptions.makeCustomInPlaceAnimation(mContext,
+ R.anim.recents_launch_prev_affiliated_task_bounce));
+ return;
+ }
+
+ // Launch the task
+ ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts);
+ }
+
+ /**
+ * Transitions to the next affiliated task.
+ */
+ public void showRelativeAffiliatedTask(boolean showNextTask) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, true /* isTopTaskHome */);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
new file mode 100644
index 0000000..7604de1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.model.Task;
+
+/**
+ * This is sent when we want to cancel the enter-recents window animation for the launch task.
+ */
+public class CancelEnterRecentsWindowAnimationEvent extends EventBus.Event {
+
+ // This is set for the task that is launching, which allows us to ensure that we are not
+ // cancelling the same task animation (it will just be overwritten instead)
+ public final Task launchTask;
+
+ public CancelEnterRecentsWindowAnimationEvent(Task launchTask) {
+ this.launchTask = launchTask;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Console.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Console.java
deleted file mode 100644
index 28ac9d3..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Console.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.misc;
-
-
-import android.content.ComponentCallbacks2;
-import android.content.Context;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.widget.Toast;
-
-import java.util.HashMap;
-import java.util.Map;
-
-
-public class Console {
- // Timer
- public static final Map<Object, Long> mTimeLogs = new HashMap<Object, Long>();
-
- // Colors
- public static final String AnsiReset = "\u001B[0m";
- public static final String AnsiBlack = "\u001B[30m";
- public static final String AnsiRed = "\u001B[31m"; // SystemUIHandshake
- public static final String AnsiGreen = "\u001B[32m"; // MeasureAndLayout
- public static final String AnsiYellow = "\u001B[33m"; // SynchronizeViewsWithModel
- public static final String AnsiBlue = "\u001B[34m"; // TouchEvents, Search
- public static final String AnsiPurple = "\u001B[35m"; // Draw
- public static final String AnsiCyan = "\u001B[36m"; // ClickEvents
- public static final String AnsiWhite = "\u001B[37m";
-
- // Console enabled state
- public static boolean Enabled = false;
-
- /** Logs a key */
- public static void log(String key) {
- log(true, key, "", AnsiReset);
- }
-
- /** Logs a conditioned key */
- public static void log(boolean condition, String key) {
- if (condition) {
- log(condition, key, "", AnsiReset);
- }
- }
-
- /** Logs a key in a specific color */
- public static void log(boolean condition, String key, Object data) {
- if (condition) {
- log(condition, key, data, AnsiReset);
- }
- }
-
- /** Logs a key with data in a specific color */
- public static void log(boolean condition, String key, Object data, String color) {
- if (condition) {
- System.out.println(color + key + AnsiReset + " " + data.toString());
- }
- }
-
- /** Logs an error */
- public static void logError(Context context, String msg) {
- Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
- Log.e("Recents", msg);
- }
-
- /** Logs a raw error */
- public static void logRawError(String msg, Exception e) {
- Log.e("Recents", msg, e);
- }
-
- /** Logs a divider bar */
- public static void logDivider(boolean condition) {
- if (condition) {
- System.out.println("==== [" + System.currentTimeMillis() +
- "] ============================================================");
- }
- }
-
- /** Starts a time trace */
- public static void logStartTracingTime(boolean condition, String key) {
- if (condition) {
- long curTime = System.currentTimeMillis();
- mTimeLogs.put(key, curTime);
- Console.log(condition, "[Recents|" + key + "]",
- "started @ " + curTime);
- }
- }
-
- /** Continues a time trace */
- public static void logTraceTime(boolean condition, String key, String desc) {
- if (condition) {
- long timeDiff = System.currentTimeMillis() - mTimeLogs.get(key);
- Console.log(condition, "[Recents|" + key + "|" + desc + "]",
- "+" + timeDiff + "ms");
- }
- }
-
- /** Logs a stack trace */
- public static void logStackTrace() {
- logStackTrace("", 99);
- }
-
- /** Logs a stack trace to a certain depth */
- public static void logStackTrace(int depth) {
- logStackTrace("", depth);
- }
-
- /** Logs a stack trace to a certain depth with a key */
- public static void logStackTrace(String key, int depth) {
- int offset = 0;
- StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
- String tinyStackTrace = "";
- // Skip over the known stack trace classes
- for (int i = 0; i < callStack.length; i++) {
- StackTraceElement el = callStack[i];
- String className = el.getClassName();
- if (className.indexOf("dalvik.system.VMStack") == -1 &&
- className.indexOf("java.lang.Thread") == -1 &&
- className.indexOf("recents.Console") == -1) {
- break;
- } else {
- offset++;
- }
- }
- // Build the pretty stack trace
- int start = Math.min(offset + depth, callStack.length);
- int end = offset;
- String indent = "";
- for (int i = start - 1; i >= end; i--) {
- StackTraceElement el = callStack[i];
- tinyStackTrace += indent + " -> " + el.getClassName() +
- "[" + el.getLineNumber() + "]." + el.getMethodName();
- if (i > end) {
- tinyStackTrace += "\n";
- indent += " ";
- }
- }
- log(true, key, tinyStackTrace, AnsiRed);
- }
-
-
- /** Returns the stringified MotionEvent action */
- public static String motionEventActionToString(int action) {
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- return "Down";
- case MotionEvent.ACTION_UP:
- return "Up";
- case MotionEvent.ACTION_MOVE:
- return "Move";
- case MotionEvent.ACTION_CANCEL:
- return "Cancel";
- case MotionEvent.ACTION_POINTER_DOWN:
- return "Pointer Down";
- case MotionEvent.ACTION_POINTER_UP:
- return "Pointer Up";
- default:
- return "" + action;
- }
- }
-
- public static String trimMemoryLevelToString(int level) {
- switch (level) {
- case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
- return "UI Hidden";
- case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
- return "Running Moderate";
- case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
- return "Background";
- case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
- return "Running Low";
- case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
- return "Moderate";
- case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
- return "Critical";
- case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
- return "Complete";
- default:
- return "" + level;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
index 336d2db..dc8f547 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
@@ -29,44 +29,53 @@
boolean mIsDozing;
boolean mHasTriggered;
int mDozeDurationMilliseconds;
- Runnable mSleepRunnable;
+ Runnable mOnSleepRunnable;
// Sleep-runnable
Runnable mDozeRunnable = new Runnable() {
@Override
public void run() {
- mSleepRunnable.run();
mIsDozing = false;
mHasTriggered = true;
+ mOnSleepRunnable.run();
}
};
- public DozeTrigger(int dozeDurationMilliseconds, Runnable sleepRunnable) {
+ public DozeTrigger(int dozeDurationMilliseconds, Runnable onSleepRunnable) {
mHandler = new Handler();
mDozeDurationMilliseconds = dozeDurationMilliseconds;
- mSleepRunnable = sleepRunnable;
+ mOnSleepRunnable = onSleepRunnable;
}
- /** Starts dozing. This also resets the trigger flag. */
+ /**
+ * Starts dozing and queues the onSleepRunnable to be called. This also resets the trigger flag.
+ */
public void startDozing() {
forcePoke();
mHasTriggered = false;
}
- /** Stops dozing. */
+ /**
+ * Stops dozing and prevents the onSleepRunnable from being called.
+ */
public void stopDozing() {
mHandler.removeCallbacks(mDozeRunnable);
mIsDozing = false;
}
- /** Poke this dozer to wake it up for a little bit, if it is dozing. */
+ /**
+ * Poke this dozer to wake it up if it is dozing, delaying the onSleepRunnable from being
+ * called for a for the doze duration.
+ */
public void poke() {
if (mIsDozing) {
forcePoke();
}
}
- /** Poke this dozer to wake it up for a little bit. */
+ /**
+ * Poke this dozer to wake it up even if it is not currently dozing.
+ */
void forcePoke() {
mHandler.removeCallbacks(mDozeRunnable);
mHandler.postDelayed(mDozeRunnable, mDozeDurationMilliseconds);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
index c87a901..401b448 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
@@ -19,6 +19,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
+import android.util.Log;
import java.util.ArrayList;
@@ -28,6 +29,8 @@
*/
public class ReferenceCountedTrigger {
+ private static final String TAG = "ReferenceCountedTrigger";
+
Context mContext;
int mCount;
ArrayList<Runnable> mFirstIncRunnables = new ArrayList<Runnable>();
@@ -94,8 +97,7 @@
if (mErrorRunnable != null) {
mErrorRunnable.run();
} else {
- new Throwable("Invalid ref count").printStackTrace();
- Console.logError(mContext, "Invalid ref count");
+ Log.e(TAG, "Invalid ref count");
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 4ee0357..0432ac9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -363,6 +363,19 @@
}
}
+ /**
+ * Cancels the current thumbnail transtion to/from Recents for the given task id.
+ */
+ public void cancelThumbnailTransition(int taskId) {
+ if (mWm == null) return;
+
+ try {
+ WindowManagerGlobal.getWindowManagerService().cancelTaskThumbnailTransition(taskId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
/** Returns the top task thumbnail for the given task id */
public Bitmap getTaskThumbnail(int taskId) {
if (mAm == null) return null;
@@ -775,8 +788,7 @@
taskId, INVALID_STACK_ID, options == null ? null : options.toBundle());
return true;
} catch (Exception e) {
- Console.logError(context,
- context.getString(R.string.recents_launch_error_message, taskName));
+ Log.e(TAG, context.getString(R.string.recents_launch_error_message, taskName), e);
}
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 495c8fd..6734012 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -17,14 +17,12 @@
package com.android.systemui.recents.model;
import android.animation.ObjectAnimator;
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
-import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 9a5e141..5f94fa7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -51,6 +51,7 @@
import com.android.systemui.recents.RecentsAppWidgetHostView;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
@@ -64,8 +65,8 @@
import java.util.ArrayList;
import java.util.List;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
/**
@@ -82,7 +83,6 @@
/** The RecentsView callbacks */
public interface RecentsViewCallbacks {
- public void onTaskViewClicked();
public void onTaskLaunchFailed();
public void onAllTaskViewsDismissed();
}
@@ -107,6 +107,7 @@
Rect mSystemInsets = new Rect();
+
@GuardedBy("this")
List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs;
@@ -246,6 +247,9 @@
}
ctx.postAnimationTrigger.decrement();
+ // If we are going home, cancel the previous task's window transition
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
+
// Notify of the exit animation
EventBus.getDefault().send(new DismissRecentsToHomeAnimationStarted());
}
@@ -555,30 +559,12 @@
return new AppTransitionAnimationSpec(taskId, b, rect);
}
- /**
- * Cancels any running window transitions for the launched task (the task animating into
- * Recents).
- */
- private void cancelLaunchedTaskWindowTransition(final Task task) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- RecentsConfiguration config = Recents.getConfiguration();
- RecentsActivityLaunchState launchState = config.getLaunchState();
- if (launchState.launchedToTaskId != -1 &&
- launchState.launchedToTaskId != task.key.id) {
- ssp.cancelWindowTransition(launchState.launchedToTaskId);
- }
- }
-
/**** TaskStackView.TaskStackCallbacks Implementation ****/
@Override
public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
final TaskStack stack, final Task task, final boolean lockToTask,
final boolean boundsValid, final Rect bounds, int destinationStack) {
- // Notify any callbacks of the launching of a new task
- if (mCb != null) {
- mCb.onTaskViewClicked();
- }
// Upfront the processing of the thumbnail
TaskViewTransform transform = new TaskViewTransform();
@@ -610,7 +596,7 @@
public void onAnimationStarted() {
// If we are launching into another task, cancel the previous task's
// window transition
- cancelLaunchedTaskWindowTransition(task);
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
if (lockToTask) {
// Request screen pinning after the animation runs
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 7f5c768..51091c3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -31,7 +31,6 @@
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.LinkedList;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index fc3a681..3ef597f7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -34,7 +34,6 @@
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
@@ -228,6 +227,42 @@
return mImmutableTaskViews;
}
+ /**
+ * Returns the front most task view.
+ *
+ * @param stackTasksOnly if set, will return the front most task view in the stack (by default
+ * the front most task view will be freeform since they are placed above
+ * stack tasks)
+ */
+ private TaskView getFrontMostTaskView(boolean stackTasksOnly) {
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
+ Task task = tv.getTask();
+ if (stackTasksOnly && task.isFreeformTask()) {
+ continue;
+ }
+ return tv;
+ }
+ return null;
+ }
+
+ /**
+ * Finds the child view given a specific {@param task}.
+ */
+ public TaskView getChildViewForTask(Task t) {
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
+ if (tv.getTask() == t) {
+ return tv;
+ }
+ }
+ return null;
+ }
+
/** Resets this TaskStackView for reuse. */
void reset() {
// Reset the focused task
@@ -288,19 +323,6 @@
}
}
- /** Finds the child view given a specific task. */
- public TaskView getChildViewForTask(Task t) {
- List<TaskView> taskViews = getTaskViews();
- int taskViewCount = taskViews.size();
- for (int i = 0; i < taskViewCount; i++) {
- TaskView tv = taskViews.get(i);
- if (tv.getTask() == t) {
- return tv;
- }
- }
- return null;
- }
-
/** Returns the stack algorithm for this task stack. */
public TaskStackLayoutAlgorithm getStackAlgorithm() {
return mLayoutAlgorithm;
@@ -635,13 +657,49 @@
/**
* Sets the focused task relative to the currently focused task.
*
+ * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
+ * if the currently focused task is not a stack task, will set the focus
+ * to the first visible stack task
* @param animated determines whether to actually draw the highlight along with the change in
* focus.
*/
- public void setRelativeFocusedTask(boolean forward, boolean animated) {
- // Find the next index to focus
- int newIndex = mFocusedTaskIndex + (forward ? -1 : 1);
- setFocusedTask(newIndex, true, animated);
+ public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated) {
+ int newIndex = -1;
+ if (mFocusedTaskIndex != -1) {
+ if (stackTasksOnly) {
+ List<Task> tasks = mStack.getTasks();
+ newIndex = mFocusedTaskIndex;
+ Task task = tasks.get(mFocusedTaskIndex);
+ if (task.isFreeformTask()) {
+ // Try and focus the front most stack task
+ TaskView tv = getFrontMostTaskView(stackTasksOnly);
+ if (tv != null) {
+ newIndex = mStack.indexOfTask(tv.getTask());
+ }
+ } else {
+ // Try the next task if it is a stack task
+ int tmpNewIndex = mFocusedTaskIndex + (forward ? -1 : 1);
+ if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
+ Task t = tasks.get(tmpNewIndex);
+ if (!t.isFreeformTask()) {
+ newIndex = tmpNewIndex;
+ }
+ }
+ }
+ } else {
+ // No restrictions, lets just move to the new task
+ newIndex = mFocusedTaskIndex + (forward ? -1 : 1);
+ }
+ } else {
+ // We don't have a focused task, so focus the first visible task view
+ TaskView tv = getFrontMostTaskView(stackTasksOnly);
+ if (tv != null) {
+ newIndex = mStack.indexOfTask(tv.getTask());
+ }
+ }
+ if (newIndex != -1) {
+ setFocusedTask(newIndex, true, animated);
+ }
}
/**
@@ -703,11 +761,11 @@
}
switch (action) {
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
- setRelativeFocusedTask(true, false /* animated */);
+ setRelativeFocusedTask(true, false /* stackTasksOnly */, false /* animated */);
return true;
}
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
- setRelativeFocusedTask(false, false /* animated */);
+ setRelativeFocusedTask(false, false /* stackTasksOnly */, false /* animated */);
return true;
}
}
@@ -1324,11 +1382,11 @@
}
public final void onBusEvent(FocusNextTaskViewEvent event) {
- setRelativeFocusedTask(true, true);
+ setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */);
}
public final void onBusEvent(FocusPreviousTaskViewEvent event) {
- setRelativeFocusedTask(false, true);
+ setRelativeFocusedTask(false, false /* stackTasksOnly */, true /* animated */);
}
public final void onBusEvent(DismissFocusedTaskViewEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
index a32b242..45f573d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
@@ -17,7 +17,6 @@
package com.android.systemui.recents.views;
import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
import com.android.systemui.recents.model.Task;
import java.util.ArrayList;
@@ -113,7 +112,7 @@
tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
tv.updateViewPropertiesToTaskTransform(fromTransform, 0);
- toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay;
+ toTransform.startDelay = offset * 25;
childViewTransformsOut.put(tv, toTransform);
// Use the movement of the new views to calculate the duration of the animation
@@ -166,7 +165,7 @@
(int) tv.getTranslationY()));
}
- toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay;
+ toTransform.startDelay = offset * 25;
childViewTransformsOut.put(tv, toTransform);
offset++;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 59c9708..81c89a1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -31,6 +31,7 @@
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
@@ -149,6 +150,11 @@
if (mSv.getTaskViews().size() == 0) {
return false;
}
+ // Short circuit while we are alt-tabbing
+ RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+ if (launchState.launchedWithAltTab) {
+ return false;
+ }
final TaskStackLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
int action = ev.getAction();
@@ -305,9 +311,11 @@
// Find the front most task and scroll the next task to the front
float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL);
if (vScroll > 0) {
- mSv.setRelativeFocusedTask(true, false /* animated */);
+ mSv.setRelativeFocusedTask(true, true /* stackTasksOnly */,
+ false /* animated */);
} else {
- mSv.setRelativeFocusedTask(false, false /* animated */);
+ mSv.setRelativeFocusedTask(false, true /* stackTasksOnly */,
+ false /* animated */);
}
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index dd1474d..4f4b91a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -29,6 +29,7 @@
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -80,7 +81,6 @@
boolean mTaskDataLoaded;
boolean mIsFocused;
boolean mIsFocusAnimated;
- boolean mFocusAnimationsEnabled;
boolean mClipViewInStack;
AnimateableViewBounds mViewBounds;
@@ -642,6 +642,21 @@
setDim(getDimFromTaskProgress());
}
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ if (Constants.DebugFlags.App.EnableFastToggleRecents && mIsFocused) {
+ Paint tmpPaint = new Paint();
+ Rect tmpRect = new Rect();
+ tmpRect.set(0, 0, getWidth(), getHeight());
+ tmpPaint.setColor(0xFFFF0000);
+ tmpPaint.setStrokeWidth(35);
+ tmpPaint.setStyle(Paint.Style.STROKE);
+ canvas.drawRect(tmpRect, tmpPaint);
+ }
+ }
+
/**** View focus state ****/
/**
@@ -672,6 +687,9 @@
clearAccessibilityFocus();
}
}
+ if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+ invalidate();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 3d6c600..59d4011 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -303,6 +303,10 @@
if (mTmpRect.equals(mLastResizeRect)) {
return;
}
+
+ // Make sure shadows are updated
+ mBackground.invalidate();
+
mLastResizeRect.set(mTmpRect);
mWindowManagerProxy.resizeDockedStack(mTmpRect);
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index c35b91f..031f6d2 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -319,7 +319,7 @@
mCallingPackage = callingPackage;
mResizeable = resizeable || mService.mForceResizableActivites;
mPrivileged = privileged;
- ActivityInfo info = mActivities.get(0).info;
+ ActivityInfo info = (mActivities.size() > 0) ? mActivities.get(0).info : null;
mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a6a34af..3eb2d11 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -635,7 +635,7 @@
}
if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
info.flags |= UserInfo.FLAG_INITIALIZED;
- scheduleWriteUserLP(info);
+ scheduleWriteUser(info);
}
}
}
@@ -775,6 +775,7 @@
Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
!= newRestrictions);
mBaseUserRestrictions.put(userId, newRestrictions);
+ scheduleWriteUser(mUsers.get(userId));
}
final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
@@ -1082,7 +1083,7 @@
UserInfo user = getUserInfoNoChecks(UserHandle.USER_SYSTEM);
if ("Primary".equals(user.name)) {
user.name = mContext.getResources().getString(com.android.internal.R.string.owner_name);
- scheduleWriteUserLP(user);
+ scheduleWriteUser(user);
}
userVersion = 1;
}
@@ -1092,7 +1093,7 @@
UserInfo user = getUserInfoNoChecks(UserHandle.USER_SYSTEM);
if ((user.flags & UserInfo.FLAG_INITIALIZED) == 0) {
user.flags |= UserInfo.FLAG_INITIALIZED;
- scheduleWriteUserLP(user);
+ scheduleWriteUser(user);
}
userVersion = 2;
}
@@ -1116,7 +1117,7 @@
if (!splitSystemUser && user.isRestricted()
&& (user.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID)) {
user.restrictedProfileParentId = UserHandle.USER_SYSTEM;
- scheduleWriteUserLP(user);
+ scheduleWriteUser(user);
}
}
}
@@ -1161,7 +1162,9 @@
writeUserLP(system);
}
- private void scheduleWriteUserLP(UserInfo userInfo) {
+ private void scheduleWriteUser(UserInfo userInfo) {
+ // No need to wrap it within a lock -- worst case, we'll just post the same message
+ // twice.
if (!mHandler.hasMessages(WRITE_USER_MSG, userInfo)) {
Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userInfo);
mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
@@ -1543,7 +1546,7 @@
if (isManagedProfile) {
if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
parent.profileGroupId = parent.id;
- scheduleWriteUserLP(parent);
+ scheduleWriteUser(parent);
}
userInfo.profileGroupId = parent.profileGroupId;
} else if (isRestricted) {
@@ -1552,7 +1555,7 @@
}
if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
parent.restrictedProfileParentId = parent.id;
- scheduleWriteUserLP(parent);
+ scheduleWriteUser(parent);
}
userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
}
@@ -1571,7 +1574,7 @@
}
mPm.createNewUserLILPw(userId);
userInfo.partial = false;
- scheduleWriteUserLP(userInfo);
+ scheduleWriteUser(userInfo);
updateUserIds();
Bundle restrictions = new Bundle();
synchronized (mRestrictionsLock) {
@@ -2148,7 +2151,7 @@
}
if (now > EPOCH_PLUS_30_YEARS) {
user.lastLoggedInTime = now;
- scheduleWriteUserLP(user);
+ scheduleWriteUser(user);
}
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5864b25..6aaf3c7 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -328,6 +328,15 @@
}
}
+ /**
+ * Cancels any running thumbnail transitions associated with the task.
+ */
+ void cancelTaskThumbnailTransition() {
+ for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
+ mAppTokens.get(activityNdx).mAppAnimator.clearThumbnail();
+ }
+ }
+
boolean showForAllUsers() {
final int tokensCount = mAppTokens.size();
return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index bc34ba7..3205c6c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4690,6 +4690,16 @@
}
}
+ @Override
+ public void cancelTaskThumbnailTransition(int taskId) {
+ synchronized (mWindowMap) {
+ Task task = mTaskIdToTask.get(taskId);
+ if (task != null) {
+ task.cancelTaskThumbnailTransition();
+ }
+ }
+ }
+
public void addTask(int taskId, int stackId, boolean toTop) {
synchronized (mWindowMap) {
if (DEBUG_STACK) Slog.i(TAG, "addTask: adding taskId=" + taskId
diff --git a/tests/NetworkSecurityConfigTest/AndroidManifest.xml b/tests/NetworkSecurityConfigTest/AndroidManifest.xml
index 811a3f4..4c1fbd3 100644
--- a/tests/NetworkSecurityConfigTest/AndroidManifest.xml
+++ b/tests/NetworkSecurityConfigTest/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.security.tests"
+ package="android.security.net.config"
android:sharedUserId="android.uid.system">
<application>
@@ -23,7 +23,7 @@
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="android.security.tests"
+ android:targetPackage="android.security.net.config"
android:label="ANSC Tests">
</instrumentation>
</manifest>
diff --git a/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der
new file mode 100644
index 0000000..235bd47
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der
Binary files differ
diff --git a/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem
new file mode 100644
index 0000000..413e3c0
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw
+WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE
+AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m
+OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu
+T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c
+JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR
+Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz
+PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm
+aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM
+TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g
+LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO
+BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv
+dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB
+AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL
+NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W
+b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
+BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
+I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
+CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
+2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
+2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
+-----END CERTIFICATE-----
diff --git a/tests/NetworkSecurityConfigTest/res/xml/attributes.xml b/tests/NetworkSecurityConfigTest/res/xml/attributes.xml
new file mode 100644
index 0000000..eff13c8
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/attributes.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config cleartextTrafficPermitted="false" hstsEnforced="true">
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml
new file mode 100644
index 0000000..6af855d
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config0.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <!-- Bad pin digest -->
+ <pin digest="I am probably not an algorithm">1HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml
new file mode 100644
index 0000000..d683b74
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config1.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <!-- Unknown pin digest -->
+ <pin>1HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml
new file mode 100644
index 0000000..6f3f8b4
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config2.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <!-- empty digest -->
+ <pin digest="SHA-256"></pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml
new file mode 100644
index 0000000..fb2126c
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config3.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ </domain-config>
+ <domain-config>
+ <!-- Same domain name used in two configs -->
+ <domain>android.com</domain>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml
new file mode 100644
index 0000000..95972ce
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config4.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <!-- domains are not allowed in base-config -->
+ <domain>android.com</domain>
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml
new file mode 100644
index 0000000..8b6b721
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_config5.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <!-- pins are not allowed in base-config -->
+ <pin-set>
+ </pin-set>
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml b/tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml
new file mode 100644
index 0000000..62a7b88
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/bad_pin.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">1HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/domain1.xml b/tests/NetworkSecurityConfigTest/res/xml/domain1.xml
new file mode 100644
index 0000000..6d8565c
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/domain1.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/empty_config.xml b/tests/NetworkSecurityConfigTest/res/xml/empty_config.xml
new file mode 100644
index 0000000..1bd94b6
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/empty_config.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml b/tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml
new file mode 100644
index 0000000..8093b9d
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/empty_trust.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml b/tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml
new file mode 100644
index 0000000..f9f8465
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/expired_pin.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <!-- Invalid pin that has expired -->
+ <pin-set expiration="2015-01-01">
+ <pin digest="SHA-256">aaaaaaaaaaa2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml b/tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml
new file mode 100644
index 0000000..df08467
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/multiple_configs.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <domain>google.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml
new file mode 100644
index 0000000..9743c5f
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/multiple_domains.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <domain>google.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
new file mode 100644
index 0000000..d45fd77
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain includeSubdomains="true">android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ </trust-anchors>
+ <!-- nested config that adds pins -->
+ <domain-config>
+ <domain>developer.android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+ </domain-config>
+ <base-config cleartextTrafficPermitted="false">
+ </base-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/nested_domains_override.xml b/tests/NetworkSecurityConfigTest/res/xml/nested_domains_override.xml
new file mode 100644
index 0000000..84e06e3
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/nested_domains_override.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config cleartextTrafficPermitted="false">
+ </base-config>
+ <!-- Nested config that overrides parent -->
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">android.com</domain>
+ <domain-config cleartextTrafficPermitted="false">
+ <domain>developer.android.com</domain>
+ </domain-config>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/override_pins.xml b/tests/NetworkSecurityConfigTest/res/xml/override_pins.xml
new file mode 100644
index 0000000..785714a
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/override_pins.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">aaaaaaaaIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ <trust-anchors>
+ <certificates src="system" overridePins="true" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/pins1.xml b/tests/NetworkSecurityConfigTest/res/xml/pins1.xml
new file mode 100644
index 0000000..1773d280
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/pins1.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml
new file mode 100644
index 0000000..dfd6fd9
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_der.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_der" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml
new file mode 100644
index 0000000..894f29b
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/resource_anchors_pem.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_pem" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/subdomains.xml b/tests/NetworkSecurityConfigTest/res/xml/subdomains.xml
new file mode 100644
index 0000000..482b26c
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/subdomains.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain includeSubdomains="true">android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
index 9a1fe15..9f48d56 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
@@ -50,49 +50,6 @@
return data;
}
- private void assertConnectionFails(SSLContext context, String host, int port)
- throws Exception {
- try {
- Socket s = context.getSocketFactory().createSocket(host, port);
- s.getInputStream();
- fail("Expected connection to " + host + ":" + port + " to fail.");
- } catch (SSLHandshakeException expected) {
- }
- }
-
- private void assertConnectionSucceeds(SSLContext context, String host, int port)
- throws Exception {
- Socket s = context.getSocketFactory().createSocket(host, port);
- s.getInputStream();
- }
-
- private void assertUrlConnectionFails(SSLContext context, String host, int port)
- throws Exception {
- URL url = new URL("https://" + host + ":" + port);
- HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
- connection.setSSLSocketFactory(context.getSocketFactory());
- try {
- connection.getInputStream();
- fail("Connection to " + host + ":" + port + " expected to fail");
- } catch (SSLHandshakeException expected) {
- // ignored.
- }
- }
-
- private void assertUrlConnectionSucceeds(SSLContext context, String host, int port)
- throws Exception {
- URL url = new URL("https://" + host + ":" + port);
- HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
- connection.setSSLSocketFactory(context.getSocketFactory());
- connection.getInputStream();
- }
-
- private SSLContext getSSLContext(ConfigSource source) throws Exception {
- ApplicationConfig config = new ApplicationConfig(source);
- SSLContext context = SSLContext.getInstance("TLS");
- context.init(null, new TrustManager[] {config.getTrustManager()}, null);
- return context;
- }
/**
@@ -100,16 +57,14 @@
* SSLHandshakeException when used for a connection.
*/
private NetworkSecurityConfig getEmptyConfig() {
- return new NetworkSecurityConfig(true, false,
- new PinSet(new ArraySet<Pin>(), -1),
- new ArrayList<CertificatesEntryRef>());
+ return new NetworkSecurityConfig.Builder().build();
}
private NetworkSecurityConfig getSystemStoreConfig() {
- ArrayList<CertificatesEntryRef> defaultSource = new ArrayList<CertificatesEntryRef>();
- defaultSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
- return new NetworkSecurityConfig(true, false, new PinSet(new ArraySet<Pin>(),
- -1), defaultSource);
+ return new NetworkSecurityConfig.Builder()
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ .build();
}
public void testEmptyConfig() throws Exception {
@@ -117,8 +72,8 @@
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
ConfigSource testSource =
new TestConfigSource(domainMap, getEmptyConfig());
- SSLContext context = getSSLContext(testSource);
- assertConnectionFails(context, "android.com", 443);
+ SSLContext context = TestUtils.getSSLContext(testSource);
+ TestUtils.assertConnectionFails(context, "android.com", 443);
}
public void testEmptyPerNetworkSecurityConfig() throws Exception {
@@ -126,68 +81,64 @@
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), getEmptyConfig()));
- ArrayList<CertificatesEntryRef> defaultSource = new ArrayList<CertificatesEntryRef>();
- defaultSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
- NetworkSecurityConfig defaultConfig = new NetworkSecurityConfig(true, false,
- new PinSet(new ArraySet<Pin>(), -1),
- defaultSource);
- SSLContext context = getSSLContext(new TestConfigSource(domainMap, defaultConfig));
- assertConnectionFails(context, "android.com", 443);
- assertConnectionSucceeds(context, "google.com", 443);
+ NetworkSecurityConfig defaultConfig = getSystemStoreConfig();
+ SSLContext context = TestUtils.getSSLContext(new TestConfigSource(domainMap, defaultConfig));
+ TestUtils.assertConnectionFails(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "google.com", 443);
}
public void testBadPin() throws Exception {
- ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>();
- systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
ArraySet<Pin> pins = new ArraySet<Pin>();
pins.add(new Pin("SHA-256", new byte[0]));
- NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false,
- new PinSet(pins, Long.MAX_VALUE),
- systemSource);
+ NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
+ .setPinSet(new PinSet(pins, Long.MAX_VALUE))
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ .build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), domain));
SSLContext context
- = getSSLContext(new TestConfigSource(domainMap, getSystemStoreConfig()));
- assertConnectionFails(context, "android.com", 443);
- assertConnectionSucceeds(context, "google.com", 443);
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getSystemStoreConfig()));
+ TestUtils.assertConnectionFails(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "google.com", 443);
}
public void testGoodPin() throws Exception {
- ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>();
- systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
ArraySet<Pin> pins = new ArraySet<Pin>();
pins.add(new Pin("SHA-256", G2_SPKI_SHA256));
- NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false,
- new PinSet(pins, Long.MAX_VALUE),
- systemSource);
+ NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
+ .setPinSet(new PinSet(pins, Long.MAX_VALUE))
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ .build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), domain));
SSLContext context
- = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
- assertConnectionSucceeds(context, "android.com", 443);
- assertConnectionSucceeds(context, "developer.android.com", 443);
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
}
public void testOverridePins() throws Exception {
// Use a bad pin + granting the system CA store the ability to override pins.
- ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>();
- systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), true));
ArraySet<Pin> pins = new ArraySet<Pin>();
pins.add(new Pin("SHA-256", new byte[0]));
- NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false,
- new PinSet(pins, Long.MAX_VALUE),
- systemSource);
+ NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
+ .setPinSet(new PinSet(pins, Long.MAX_VALUE))
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(SystemCertificateSource.getInstance(), true))
+ .build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), domain));
SSLContext context
- = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
- assertConnectionSucceeds(context, "android.com", 443);
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
}
public void testMostSpecificNetworkSecurityConfig() throws Exception {
@@ -198,9 +149,9 @@
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("developer.android.com", false), getSystemStoreConfig()));
SSLContext context
- = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
- assertConnectionFails(context, "android.com", 443);
- assertConnectionSucceeds(context, "developer.android.com", 443);
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionFails(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
}
public void testSubdomainIncluded() throws Exception {
@@ -210,32 +161,51 @@
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), getSystemStoreConfig()));
SSLContext context
- = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
- assertConnectionSucceeds(context, "developer.android.com", 443);
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
// Now try without including subdomains.
domainMap = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", false), getSystemStoreConfig()));
- context = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
- assertConnectionFails(context, "developer.android.com", 443);
+ context = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertConnectionFails(context, "developer.android.com", 443);
+ }
+
+ public void testConfigBuilderUsesParents() throws Exception {
+ // Check that a builder with a parent uses the parent's values when non is set.
+ NetworkSecurityConfig config = new NetworkSecurityConfig.Builder()
+ .setParent(NetworkSecurityConfig.getDefaultBuilder())
+ .build();
+ assert(!config.getTrustAnchors().isEmpty());
+ }
+
+ public void testConfigBuilderParentLoop() throws Exception {
+ NetworkSecurityConfig.Builder config1 = new NetworkSecurityConfig.Builder();
+ NetworkSecurityConfig.Builder config2 = new NetworkSecurityConfig.Builder();
+ config1.setParent(config2);
+ try {
+ config2.setParent(config1);
+ fail("Loop in NetworkSecurityConfig parents");
+ } catch (IllegalArgumentException expected) {
+ }
}
public void testWithUrlConnection() throws Exception {
- ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>();
- systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
ArraySet<Pin> pins = new ArraySet<Pin>();
pins.add(new Pin("SHA-256", G2_SPKI_SHA256));
- NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false,
- new PinSet(pins, Long.MAX_VALUE),
- systemSource);
+ NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
+ .setPinSet(new PinSet(pins, Long.MAX_VALUE))
+ .addCertificatesEntryRef(
+ new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ .build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
new Domain("android.com", true), domain));
SSLContext context
- = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
- assertUrlConnectionSucceeds(context, "android.com", 443);
- assertUrlConnectionSucceeds(context, "developer.android.com", 443);
- assertUrlConnectionFails(context, "google.com", 443);
+ = TestUtils.getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "developer.android.com", 443);
+ TestUtils.assertUrlConnectionFails(context, "google.com", 443);
}
}
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
new file mode 100644
index 0000000..43c0e57
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.net.config;
+
+import java.net.Socket;
+import java.net.URL;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.TrustManager;
+
+import junit.framework.Assert;
+
+public final class TestUtils extends Assert {
+
+ private TestUtils() {
+ }
+
+ public static void assertConnectionFails(SSLContext context, String host, int port)
+ throws Exception {
+ try {
+ Socket s = context.getSocketFactory().createSocket(host, port);
+ s.getInputStream();
+ fail("Expected connection to " + host + ":" + port + " to fail.");
+ } catch (SSLHandshakeException expected) {
+ }
+ }
+
+ public static void assertConnectionSucceeds(SSLContext context, String host, int port)
+ throws Exception {
+ Socket s = context.getSocketFactory().createSocket(host, port);
+ s.getInputStream();
+ }
+
+ public static void assertUrlConnectionFails(SSLContext context, String host, int port)
+ throws Exception {
+ URL url = new URL("https://" + host + ":" + port);
+ HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
+ connection.setSSLSocketFactory(context.getSocketFactory());
+ try {
+ connection.getInputStream();
+ fail("Connection to " + host + ":" + port + " expected to fail");
+ } catch (SSLHandshakeException expected) {
+ // ignored.
+ }
+ }
+
+ public static void assertUrlConnectionSucceeds(SSLContext context, String host, int port)
+ throws Exception {
+ URL url = new URL("https://" + host + ":" + port);
+ HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
+ connection.setSSLSocketFactory(context.getSocketFactory());
+ connection.getInputStream();
+ }
+
+ public static SSLContext getSSLContext(ConfigSource source) throws Exception {
+ ApplicationConfig config = new ApplicationConfig(source);
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, new TrustManager[] {config.getTrustManager()}, null);
+ return context;
+ }
+}
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
new file mode 100644
index 0000000..f52a279
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.net.config;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.util.ArraySet;
+import android.util.Pair;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.TrustManager;
+
+public class XmlConfigTests extends AndroidTestCase {
+
+ public void testEmptyConfigFile() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_config);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertFalse(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+ assertNotNull(config);
+ // Check defaults.
+ assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isHstsEnforced());
+ assertFalse(config.getTrustAnchors().isEmpty());
+ PinSet pinSet = config.getPins();
+ assertTrue(pinSet.pins.isEmpty());
+ // Try some connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "google.com", 443);
+ }
+
+ public void testEmptyAnchors() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_trust);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertFalse(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+ assertNotNull(config);
+ // Check defaults.
+ assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isHstsEnforced());
+ assertTrue(config.getTrustAnchors().isEmpty());
+ PinSet pinSet = config.getPins();
+ assertTrue(pinSet.pins.isEmpty());
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionFails(context, "android.com", 443);
+ TestUtils.assertConnectionFails(context, "developer.android.com", 443);
+ TestUtils.assertUrlConnectionFails(context, "google.com", 443);
+ }
+
+ public void testBasicDomainConfig() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.domain1);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+ assertNotNull(config);
+ // Check defaults.
+ assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isHstsEnforced());
+ assertTrue(config.getTrustAnchors().isEmpty());
+ PinSet pinSet = config.getPins();
+ assertTrue(pinSet.pins.isEmpty());
+ // Check android.com.
+ config = appConfig.getConfigForHostname("android.com");
+ assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isHstsEnforced());
+ assertFalse(config.getTrustAnchors().isEmpty());
+ pinSet = config.getPins();
+ assertTrue(pinSet.pins.isEmpty());
+ // Try connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionFails(context, "developer.android.com", 443);
+ TestUtils.assertUrlConnectionFails(context, "google.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ }
+
+ public void testBasicPinning() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.pins1);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ // Check android.com.
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+ PinSet pinSet = config.getPins();
+ assertFalse(pinSet.pins.isEmpty());
+ // Try connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "google.com", 443);
+ }
+
+ public void testExpiredPin() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.expired_pin);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ // Check android.com.
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+ PinSet pinSet = config.getPins();
+ assertFalse(pinSet.pins.isEmpty());
+ // Try connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ }
+
+ public void testOverridesPins() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_pins);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ // Check android.com.
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+ PinSet pinSet = config.getPins();
+ assertFalse(pinSet.pins.isEmpty());
+ // Try connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ }
+
+ public void testBadPin() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ // Check android.com.
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+ PinSet pinSet = config.getPins();
+ assertFalse(pinSet.pins.isEmpty());
+ // Try connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionFails(context, "android.com", 443);
+ TestUtils.assertUrlConnectionFails(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "google.com", 443);
+ }
+
+ public void testMultipleDomains() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_domains);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+ assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isHstsEnforced());
+ assertFalse(config.getTrustAnchors().isEmpty());
+ PinSet pinSet = config.getPins();
+ assertTrue(pinSet.pins.isEmpty());
+ // Both android.com and google.com should use the same config
+ NetworkSecurityConfig other = appConfig.getConfigForHostname("google.com");
+ assertEquals(config, other);
+ // Try connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "google.com", 443);
+ TestUtils.assertConnectionFails(context, "developer.android.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ }
+
+ public void testMultipleDomainConfigs() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_configs);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ // Should be two different config objects
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+ NetworkSecurityConfig other = appConfig.getConfigForHostname("google.com");
+ MoreAsserts.assertNotEqual(config, other);
+ // Try connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "google.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ }
+
+ public void testIncludeSubdomains() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.subdomains);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ // Try connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "developer.android.com", 443);
+ TestUtils.assertConnectionFails(context, "google.com", 443);
+ }
+
+ public void testAttributes() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.attributes);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertFalse(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+ assertTrue(config.isHstsEnforced());
+ assertFalse(config.isCleartextTrafficPermitted());
+ }
+
+ public void testResourcePemCertificateSource() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_pem);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ // Check android.com.
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+ assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isHstsEnforced());
+ assertEquals(2, config.getTrustAnchors().size());
+ // Try connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionFails(context, "developer.android.com", 443);
+ TestUtils.assertUrlConnectionFails(context, "google.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ }
+
+ public void testResourceDerCertificateSource() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_der);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ // Check android.com.
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+ assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isHstsEnforced());
+ assertEquals(2, config.getTrustAnchors().size());
+ // Try connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionFails(context, "developer.android.com", 443);
+ TestUtils.assertUrlConnectionFails(context, "google.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ }
+
+ public void testNestedDomainConfigs() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
+ NetworkSecurityConfig child = appConfig.getConfigForHostname("developer.android.com");
+ MoreAsserts.assertNotEqual(parent, child);
+ MoreAsserts.assertEmpty(parent.getPins().pins);
+ MoreAsserts.assertNotEmpty(child.getPins().pins);
+ // Check that the child inherited the cleartext value and anchors.
+ assertFalse(child.isCleartextTrafficPermitted());
+ MoreAsserts.assertNotEmpty(child.getTrustAnchors());
+ // Test connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
+ }
+
+ public void testNestedDomainConfigsOverride() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains_override);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
+ NetworkSecurityConfig child = appConfig.getConfigForHostname("developer.android.com");
+ MoreAsserts.assertNotEqual(parent, child);
+ assertTrue(parent.isCleartextTrafficPermitted());
+ assertFalse(child.isCleartextTrafficPermitted());
+ }
+
+ private void testBadConfig(int configId) throws Exception {
+ try {
+ XmlConfigSource source = new XmlConfigSource(getContext(), configId);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ appConfig.getConfigForHostname("android.com");
+ fail("Bad config " + getContext().getResources().getResourceName(configId)
+ + " did not fail to parse");
+ } catch (RuntimeException e) {
+ MoreAsserts.assertAssignableFrom(XmlConfigSource.ParserException.class,
+ e.getCause());
+ }
+ }
+
+ public void testBadConfig0() throws Exception {
+ testBadConfig(R.xml.bad_config0);
+ }
+
+ public void testBadConfig1() throws Exception {
+ testBadConfig(R.xml.bad_config1);
+ }
+
+ public void testBadConfig2() throws Exception {
+ testBadConfig(R.xml.bad_config2);
+ }
+
+ public void testBadConfig3() throws Exception {
+ testBadConfig(R.xml.bad_config3);
+ }
+
+ public void testBadConfig4() throws Exception {
+ testBadConfig(R.xml.bad_config4);
+ }
+
+ public void testBadConfig5() throws Exception {
+ testBadConfig(R.xml.bad_config4);
+ }
+}
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index ceed21e..ec29c38 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -80,6 +80,7 @@
util/StringPiece_test.cpp \
util/Util_test.cpp \
ConfigDescription_test.cpp \
+ java/AnnotationProcessor_test.cpp \
java/JavaClassGenerator_test.cpp \
java/ManifestClassGenerator_test.cpp \
Locale_test.cpp \
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 39a4116..f2a1878 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -49,6 +49,13 @@
return {};
}
+/**
+ * Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
+ */
+static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) {
+ return ns.empty() && (name == u"skip" || name == u"eat-comment");
+}
+
ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
const ConfigDescription& config,
const ResourceParserOptions& options) :
@@ -205,16 +212,14 @@
}
}
- if (!res->value) {
- return true;
- }
+ if (res->value) {
+ // Attach the comment, source and config to the value.
+ res->value->setComment(std::move(res->comment));
+ res->value->setSource(std::move(res->source));
- // Attach the comment, source and config to the value.
- res->value->setComment(std::move(res->comment));
- res->value->setSource(std::move(res->source));
-
- if (!table->addResource(res->name, res->id, config, std::move(res->value), diag)) {
- return false;
+ if (!table->addResource(res->name, res->id, config, std::move(res->value), diag)) {
+ return false;
+ }
}
bool error = false;
@@ -259,17 +264,6 @@
continue;
}
- Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
- if (!maybeName) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "<" << elementName << "> tag must have a 'name' attribute");
- error = true;
- continue;
- }
-
- // Check if we should skip this product.
- const bool stripResource = shouldStripResource(parser, mOptions.product);
-
if (elementName == u"item") {
// Items simply have their type encoded in the type attribute.
if (Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type")) {
@@ -283,10 +277,22 @@
}
ParsedResource parsedResource;
- parsedResource.name.entry = maybeName.value().toString();
parsedResource.source = mSource.withLine(parser->getLineNumber());
parsedResource.comment = std::move(comment);
+ if (Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name")) {
+ parsedResource.name.entry = maybeName.value().toString();
+
+ } else if (elementName != u"public-group") {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "<" << elementName << "> tag must have a 'name' attribute");
+ error = true;
+ continue;
+ }
+
+ // Check if we should skip this product.
+ const bool stripResource = shouldStripResource(parser, mOptions.product);
+
bool result = true;
if (elementName == u"id") {
parsedResource.name.type = ResourceType::kId;
@@ -337,9 +343,24 @@
result = parsePublic(parser, &parsedResource);
} else if (elementName == u"java-symbol" || elementName == u"symbol") {
result = parseSymbol(parser, &parsedResource);
+ } else if (elementName == u"public-group") {
+ result = parsePublicGroup(parser, &parsedResource);
} else {
- mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "unknown resource type '" << elementName << "'");
+ // Try parsing the elementName (or type) as a resource. These shall only be
+ // resources like 'layout' or 'xml' and they can only be references.
+ if (const ResourceType* type = parseResourceType(elementName)) {
+ parsedResource.name.type = *type;
+ parsedResource.value = parseXml(parser, android::ResTable_map::TYPE_REFERENCE,
+ false);
+ if (!parsedResource.value) {
+ mDiag->error(DiagMessage(parsedResource.source) << "invalid value for type '"
+ << *type << "'. Expected a reference");
+ result = false;
+ }
+ } else {
+ mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "unknown resource type '" << elementName << "'");
+ }
}
if (result) {
@@ -559,6 +580,92 @@
return true;
}
+bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* outResource) {
+ const Source source = mSource.withLine(parser->getLineNumber());
+
+ Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+ if (!maybeType) {
+ mDiag->error(DiagMessage(source) << "<public-group> must have a 'type' attribute");
+ return false;
+ }
+
+ const ResourceType* parsedType = parseResourceType(maybeType.value());
+ if (!parsedType) {
+ mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value()
+ << "' in <public-group>");
+ return false;
+ }
+
+ Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"first-id");
+ if (!maybeId) {
+ mDiag->error(DiagMessage(source) << "<public-group> must have a 'first-id' attribute");
+ return false;
+ }
+
+ android::Res_value val;
+ bool result = android::ResTable::stringToInt(maybeId.value().data(),
+ maybeId.value().size(), &val);
+ ResourceId nextId(val.data);
+ if (!result || !nextId.isValid()) {
+ mDiag->error(DiagMessage(source) << "invalid resource ID '" << maybeId.value()
+ << "' in <public-group>");
+ return false;
+ }
+
+ std::u16string comment;
+ bool error = false;
+ const size_t depth = parser->getDepth();
+ while (XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == XmlPullParser::Event::kComment) {
+ comment = util::trimWhitespace(parser->getComment()).toString();
+ continue;
+ } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ // Skip text.
+ continue;
+ }
+
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
+ const std::u16string& elementNamespace = parser->getElementNamespace();
+ const std::u16string& elementName = parser->getElementName();
+ if (elementNamespace.empty() && elementName == u"public") {
+ Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+ if (!maybeName) {
+ mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute");
+ error = true;
+ continue;
+ }
+
+ if (findNonEmptyAttribute(parser, u"id")) {
+ mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>");
+ error = true;
+ continue;
+ }
+
+ if (findNonEmptyAttribute(parser, u"type")) {
+ mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>");
+ error = true;
+ continue;
+ }
+
+ ParsedResource childResource;
+ childResource.name.type = *parsedType;
+ childResource.name.entry = maybeName.value().toString();
+ childResource.id = nextId;
+ childResource.comment = std::move(comment);
+ childResource.source = itemSource;
+ childResource.symbolState = SymbolState::kPublic;
+ outResource->childResources.push_back(std::move(childResource));
+
+ nextId.id += 1;
+
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
+ error = true;
+ }
+ }
+ return !error;
+}
+
bool ResourceParser::parseSymbol(XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
@@ -608,12 +715,7 @@
return mask;
}
-/**
- * Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
- */
-static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) {
- return ns.empty() && (name == u"skip" || name == u"eat-comment");
-}
+
bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) {
outResource->source = mSource.withLine(parser->getLineNumber());
@@ -973,7 +1075,7 @@
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
- // Declare-styleable is always public, because it technically only exists in R.java.
+ // Declare-styleable is kPrivate by default, because it technically only exists in R.java.
outResource->symbolState = SymbolState::kPublic;
std::u16string comment;
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 06b2581..18101ee 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -83,6 +83,7 @@
bool parseColor(XmlPullParser* parser, ParsedResource* outResource);
bool parsePrimitive(XmlPullParser* parser, ParsedResource* outResource);
bool parsePublic(XmlPullParser* parser, ParsedResource* outResource);
+ bool parsePublicGroup(XmlPullParser* parser, ParsedResource* outResource);
bool parseSymbol(XmlPullParser* parser, ParsedResource* outResource);
bool parseAttr(XmlPullParser* parser, ParsedResource* outResource);
bool parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index ab7b172..b59eb95 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -489,4 +489,44 @@
ASSERT_FALSE(testParse(input, std::u16string(u"phone")));
}
+TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
+ std::string input = R"EOF(
+ <public-group type="attr" first-id="0x01010040">
+ <public name="foo" />
+ <public name="bar" />
+ </public-group>)EOF";
+ ASSERT_TRUE(testParse(input));
+
+ Maybe<ResourceTable::SearchResult> result = mTable.findResource(
+ test::parseNameOrDie(u"@attr/foo"));
+ AAPT_ASSERT_TRUE(result);
+
+ AAPT_ASSERT_TRUE(result.value().package->id);
+ AAPT_ASSERT_TRUE(result.value().type->id);
+ AAPT_ASSERT_TRUE(result.value().entry->id);
+ ResourceId actualId(result.value().package->id.value(),
+ result.value().type->id.value(),
+ result.value().entry->id.value());
+ EXPECT_EQ(ResourceId(0x01010040), actualId);
+
+ result = mTable.findResource(test::parseNameOrDie(u"@attr/bar"));
+ AAPT_ASSERT_TRUE(result);
+
+ AAPT_ASSERT_TRUE(result.value().package->id);
+ AAPT_ASSERT_TRUE(result.value().type->id);
+ AAPT_ASSERT_TRUE(result.value().entry->id);
+ actualId = ResourceId(result.value().package->id.value(),
+ result.value().type->id.value(),
+ result.value().entry->id.value());
+ EXPECT_EQ(ResourceId(0x01010041), actualId);
+}
+
+TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) {
+ std::string input = R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF";
+ ASSERT_TRUE(testParse(input));
+
+ input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF";
+ ASSERT_FALSE(testParse(input));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index fa4b109..deafe20 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -284,7 +284,7 @@
}
const auto endIter = entry->values.end();
- auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThan);
+ auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThanConfig);
if (iter == endIter || iter->config != config) {
// This resource did not exist before, add it.
entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index f312d75..8acff0d 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -44,7 +44,10 @@
}
RawString* RawString::clone(StringPool* newPool) const {
- return new RawString(newPool->makeRef(*value));
+ RawString* rs = new RawString(newPool->makeRef(*value));
+ rs->mComment = mComment;
+ rs->mSource = mSource;
+ return rs;
}
bool RawString::flatten(android::Res_value* outValue) const {
@@ -77,6 +80,8 @@
Reference* Reference::clone(StringPool* /*newPool*/) const {
Reference* ref = new Reference();
+ ref->mComment = mComment;
+ ref->mSource = mSource;
ref->referenceType = referenceType;
ref->name = name;
ref->id = id;
@@ -111,7 +116,10 @@
}
Id* Id::clone(StringPool* /*newPool*/) const {
- return new Id();
+ Id* id = new Id();
+ id->mComment = mComment;
+ id->mSource = mSource;
+ return id;
}
void Id::print(std::ostream* out) const {
@@ -133,7 +141,10 @@
}
String* String::clone(StringPool* newPool) const {
- return new String(newPool->makeRef(*value));
+ String* str = new String(newPool->makeRef(*value));
+ str->mComment = mComment;
+ str->mSource = mSource;
+ return str;
}
void String::print(std::ostream* out) const {
@@ -154,7 +165,10 @@
}
StyledString* StyledString::clone(StringPool* newPool) const {
- return new StyledString(newPool->makeRef(value));
+ StyledString* str = new StyledString(newPool->makeRef(value));
+ str->mComment = mComment;
+ str->mSource = mSource;
+ return str;
}
void StyledString::print(std::ostream* out) const {
@@ -175,7 +189,10 @@
}
FileReference* FileReference::clone(StringPool* newPool) const {
- return new FileReference(newPool->makeRef(*path));
+ FileReference* fr = new FileReference(newPool->makeRef(*path));
+ fr->mComment = mComment;
+ fr->mSource = mSource;
+ return fr;
}
void FileReference::print(std::ostream* out) const {
@@ -197,7 +214,10 @@
}
BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
- return new BinaryPrimitive(value);
+ BinaryPrimitive* bp = new BinaryPrimitive(value);
+ bp->mComment = mComment;
+ bp->mSource = mSource;
+ return bp;
}
void BinaryPrimitive::print(std::ostream* out) const {
@@ -236,6 +256,8 @@
Attribute* Attribute::clone(StringPool* /*newPool*/) const {
Attribute* attr = new Attribute(weak);
+ attr->mComment = mComment;
+ attr->mSource = mSource;
attr->typeMask = typeMask;
std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols));
return attr;
@@ -358,6 +380,8 @@
Style* style = new Style();
style->parent = parent;
style->parentInferred = parentInferred;
+ style->mComment = mComment;
+ style->mSource = mSource;
for (auto& entry : entries) {
style->entries.push_back(Entry{
entry.key,
@@ -390,6 +414,8 @@
Array* Array::clone(StringPool* newPool) const {
Array* array = new Array();
+ array->mComment = mComment;
+ array->mSource = mSource;
for (auto& item : items) {
array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool)));
}
@@ -404,6 +430,8 @@
Plural* Plural::clone(StringPool* newPool) const {
Plural* p = new Plural();
+ p->mComment = mComment;
+ p->mSource = mSource;
const size_t count = values.size();
for (size_t i = 0; i < count; i++) {
if (values[i]) {
@@ -423,6 +451,8 @@
Styleable* Styleable::clone(StringPool* /*newPool*/) const {
Styleable* styleable = new Styleable();
+ styleable->mComment = mComment;
+ styleable->mSource = mSource;
std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries));
return styleable;
}
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 2629153..7ae346a 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -91,7 +91,7 @@
*/
virtual void print(std::ostream* out) const = 0;
-private:
+protected:
Source mSource;
std::u16string mComment;
};
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 5d90ab9..39088bc 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -148,10 +148,14 @@
fin.close();
}
- ResourceTablePackage* pkg = table.createPackage(context->getCompilationPackage());
- if (!pkg->id) {
- // If no package ID was set while parsing (public identifiers), auto assign an ID.
- pkg->id = context->getPackageId();
+ // Ensure we have the compilation package at least.
+ table.createPackage(context->getCompilationPackage());
+
+ for (auto& pkg : table.packages) {
+ if (!pkg->id) {
+ // If no package ID was set while parsing (public identifiers), auto assign an ID.
+ pkg->id = context->getPackageId();
+ }
}
// Assign IDs to prepare the table for flattening.
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index b36682d..9c25d4e 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -25,33 +25,20 @@
static const std::string sDeprecated = "@deprecated";
static const std::string sSystemApi = "@SystemApi";
- if (comment.find(sDeprecated) != std::string::npos && !mDeprecated) {
- mDeprecated = true;
- if (!mAnnotations.empty()) {
- mAnnotations += "\n";
- }
- mAnnotations += mPrefix;
- mAnnotations += "@Deprecated";
+ if (comment.find(sDeprecated) != std::string::npos) {
+ mAnnotationBitMask |= kDeprecated;
}
- if (comment.find(sSystemApi) != std::string::npos && !mSystemApi) {
- mSystemApi = true;
- if (!mAnnotations.empty()) {
- mAnnotations += "\n";
- }
- mAnnotations += mPrefix;
- mAnnotations += "@android.annotations.SystemApi";
+ if (comment.find(sSystemApi) != std::string::npos) {
+ mAnnotationBitMask |= kSystemApi;
}
- if (mComment.empty()) {
- mComment += mPrefix;
- mComment += "/**";
+ if (!mHasComments) {
+ mHasComments = true;
+ mComment << "/**";
}
- mComment += "\n";
- mComment += mPrefix;
- mComment += " * ";
- mComment += std::move(comment);
+ mComment << "\n" << " * " << std::move(comment);
}
void AnnotationProcessor::appendComment(const StringPiece16& comment) {
@@ -73,17 +60,22 @@
}
}
-std::string AnnotationProcessor::buildComment() {
- if (!mComment.empty()) {
- mComment += "\n";
- mComment += mPrefix;
- mComment += " */";
+void AnnotationProcessor::writeToStream(std::ostream* out, const StringPiece& prefix) {
+ if (mHasComments) {
+ std::string result = mComment.str();
+ for (StringPiece line : util::tokenize<char>(result, '\n')) {
+ *out << prefix << line << "\n";
+ }
+ *out << prefix << " */" << "\n";
}
- return std::move(mComment);
-}
-std::string AnnotationProcessor::buildAnnotations() {
- return std::move(mAnnotations);
+ if (mAnnotationBitMask & kDeprecated) {
+ *out << prefix << "@Deprecated\n";
+ }
+
+ if (mAnnotationBitMask & kSystemApi) {
+ *out << prefix << "@android.annotation.SystemApi\n";
+ }
}
} // namespace aapt
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index 81a6f6e..e7f2be0 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -19,6 +19,7 @@
#include "util/StringPiece.h"
+#include <sstream>
#include <string>
namespace aapt {
@@ -54,13 +55,6 @@
class AnnotationProcessor {
public:
/**
- * Creates an AnnotationProcessor with a given prefix for each line generated.
- * This is usually a set of spaces for indentation.
- */
- AnnotationProcessor(const StringPiece& prefix) : mPrefix(prefix.toString()) {
- }
-
- /**
* Adds more comments. Since resources can have various values with different configurations,
* we need to collect all the comments.
*/
@@ -68,23 +62,20 @@
void appendComment(const StringPiece& comment);
/**
- * Finishes the comment and moves it to the caller. Subsequent calls to buildComment() have
- * undefined results.
+ * Writes the comments and annotations to the stream, with the given prefix before each line.
*/
- std::string buildComment();
-
- /**
- * Finishes the annotation and moves it to the caller. Subsequent calls to buildAnnotations()
- * have undefined results.
- */
- std::string buildAnnotations();
+ void writeToStream(std::ostream* out, const StringPiece& prefix);
private:
- std::string mPrefix;
- std::string mComment;
- std::string mAnnotations;
- bool mDeprecated = false;
- bool mSystemApi = false;
+ enum : uint32_t {
+ kDeprecated = 0x01,
+ kSystemApi = 0x02,
+ };
+
+ std::stringstream mComment;
+ std::stringstream mAnnotations;
+ bool mHasComments = false;
+ uint32_t mAnnotationBitMask = 0;
void appendCommentLine(const std::string& line);
};
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
new file mode 100644
index 0000000..d5a2b38
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 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 "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "XmlPullParser.h"
+
+#include "java/AnnotationProcessor.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+struct AnnotationProcessorTest : public ::testing::Test {
+ std::unique_ptr<IAaptContext> mContext;
+ ResourceTable mTable;
+
+ void SetUp() override {
+ mContext = test::ContextBuilder().build();
+ }
+
+ ::testing::AssertionResult parse(const StringPiece& str) {
+ ResourceParserOptions options;
+ ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{}, ConfigDescription{},
+ options);
+ std::stringstream in;
+ in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
+ XmlPullParser xmlParser(in);
+ if (parser.parse(&xmlParser)) {
+ return ::testing::AssertionSuccess();
+ }
+ return ::testing::AssertionFailure();
+ }
+};
+
+TEST_F(AnnotationProcessorTest, EmitsDeprecated) {
+ ASSERT_TRUE(parse(R"EOF(
+ <resources>
+ <declare-styleable name="foo">
+ <!-- Some comment, and it should contain
+ a marker word, something that marks
+ this resource as nor needed.
+ {@deprecated That's the marker! } -->
+ <attr name="autoText" format="boolean" />
+ </declare-styleable>
+ </resources>)EOF"));
+
+ Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/autoText");
+ ASSERT_NE(nullptr, attr);
+
+ AnnotationProcessor processor;
+ processor.appendComment(attr->getComment());
+
+ std::stringstream result;
+ processor.writeToStream(&result, "");
+ std::string annotations = result.str();
+
+ EXPECT_NE(std::string::npos, annotations.find("@Deprecated"));
+}
+
+} // namespace aapt
+
+
diff --git a/tools/aapt2/java/ClassDefinitionWriter.h b/tools/aapt2/java/ClassDefinitionWriter.h
new file mode 100644
index 0000000..b8886f9
--- /dev/null
+++ b/tools/aapt2/java/ClassDefinitionWriter.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 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_JAVA_CLASSDEFINITION_H
+#define AAPT_JAVA_CLASSDEFINITION_H
+
+#include "java/AnnotationProcessor.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+struct ClassDefinitionWriterOptions {
+ bool useFinalQualifier = false;
+ bool forceCreationIfEmpty = false;
+};
+
+/**
+ * Writes a class for use in R.java or Manifest.java.
+ */
+class ClassDefinitionWriter {
+public:
+ ClassDefinitionWriter(const StringPiece& name, const ClassDefinitionWriterOptions& options) :
+ mName(name.toString()), mOptions(options), mStarted(false) {
+ }
+
+ ClassDefinitionWriter(const StringPiece16& name, const ClassDefinitionWriterOptions& options) :
+ mName(util::utf16ToUtf8(name)), mOptions(options), mStarted(false) {
+ }
+
+ void addIntMember(const StringPiece& name, AnnotationProcessor* processor,
+ const uint32_t val) {
+ ensureClassDeclaration();
+ if (processor) {
+ processor->writeToStream(&mOut, kIndent);
+ }
+ mOut << kIndent << "public static " << (mOptions.useFinalQualifier ? "final " : "")
+ << "int " << name << "=" << val << ";\n";
+ }
+
+ void addStringMember(const StringPiece16& name, AnnotationProcessor* processor,
+ const StringPiece16& val) {
+ ensureClassDeclaration();
+ if (processor) {
+ processor->writeToStream(&mOut, kIndent);
+ }
+ mOut << kIndent << "public static " << (mOptions.useFinalQualifier ? "final " : "")
+ << "String " << name << "=\"" << val << "\";\n";
+ }
+
+ void addResourceMember(const StringPiece16& name, AnnotationProcessor* processor,
+ const ResourceId id) {
+ ensureClassDeclaration();
+ if (processor) {
+ processor->writeToStream(&mOut, kIndent);
+ }
+ mOut << kIndent << "public static " << (mOptions.useFinalQualifier ? "final " : "")
+ << "int " << name << "=" << id <<";\n";
+ }
+
+ template <typename Iterator, typename FieldAccessorFunc>
+ void addArrayMember(const StringPiece16& name, AnnotationProcessor* processor,
+ const Iterator begin, const Iterator end, FieldAccessorFunc f) {
+ ensureClassDeclaration();
+ if (processor) {
+ processor->writeToStream(&mOut, kIndent);
+ }
+ mOut << kIndent << "public static final int[] " << name << "={";
+
+ for (Iterator current = begin; current != end; ++current) {
+ if (std::distance(begin, current) % kAttribsPerLine == 0) {
+ mOut << "\n" << kIndent << kIndent;
+ }
+
+ mOut << f(*current);
+ if (std::distance(current, end) > 1) {
+ mOut << ", ";
+ }
+ }
+ mOut << "\n" << kIndent <<"};\n";
+ }
+
+ void writeToStream(std::ostream* out, const StringPiece& prefix,
+ AnnotationProcessor* processor=nullptr) {
+ if (mOptions.forceCreationIfEmpty) {
+ ensureClassDeclaration();
+ }
+
+ if (!mStarted) {
+ return;
+ }
+
+ if (processor) {
+ processor->writeToStream(out, prefix);
+ }
+
+ std::string result = mOut.str();
+ for (StringPiece line : util::tokenize<char>(result, '\n')) {
+ *out << prefix << line << "\n";
+ }
+ *out << prefix << "}\n";
+ }
+
+private:
+ constexpr static const char* kIndent = " ";
+
+ // The number of attributes to emit per line in a Styleable array.
+ constexpr static size_t kAttribsPerLine = 4;
+
+ void ensureClassDeclaration() {
+ if (!mStarted) {
+ mStarted = true;
+ mOut << "public static final class " << mName << " {\n";
+ }
+ }
+
+ std::stringstream mOut;
+ std::string mName;
+ ClassDefinitionWriterOptions mOptions;
+ bool mStarted;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_CLASSDEFINITION_H */
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index dfd2ef6..7280f3a 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -21,7 +21,9 @@
#include "ValueVisitor.h"
#include "java/AnnotationProcessor.h"
+#include "java/ClassDefinitionWriter.h"
#include "java/JavaClassGenerator.h"
+#include "util/Comparators.h"
#include "util/StringPiece.h"
#include <algorithm>
@@ -32,9 +34,6 @@
namespace aapt {
-// The number of attributes to emit per line in a Styleable array.
-constexpr size_t kAttribsPerLine = 4;
-
JavaClassGenerator::JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options) :
mTable(table), mOptions(options) {
}
@@ -92,12 +91,11 @@
return true;
}
-void JavaClassGenerator::generateStyleable(const StringPiece16& packageNameToGenerate,
- const std::u16string& entryName,
- const Styleable* styleable,
- std::ostream* out) {
- const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
-
+void JavaClassGenerator::writeStyleableEntryForClass(ClassDefinitionWriter* outClassDef,
+ AnnotationProcessor* processor,
+ const StringPiece16& packageNameToGenerate,
+ const std::u16string& entryName,
+ const Styleable* styleable) {
// This must be sorted by resource ID.
std::vector<std::pair<ResourceId, ResourceNameRef>> sortedAttributes;
sortedAttributes.reserve(styleable->entries.size());
@@ -110,36 +108,31 @@
}
std::sort(sortedAttributes.begin(), sortedAttributes.end());
+ auto accessorFunc = [](const std::pair<ResourceId, ResourceNameRef>& a) -> ResourceId {
+ return a.first;
+ };
+
// First we emit the array containing the IDs of each attribute.
- *out << " "
- << "public static final int[] " << transform(entryName) << " = {";
-
- const size_t attrCount = sortedAttributes.size();
- for (size_t i = 0; i < attrCount; i++) {
- if (i % kAttribsPerLine == 0) {
- *out << "\n ";
- }
-
- *out << sortedAttributes[i].first;
- if (i != attrCount - 1) {
- *out << ", ";
- }
- }
- *out << "\n };\n";
+ outClassDef->addArrayMember(transform(entryName), processor,
+ sortedAttributes.begin(),
+ sortedAttributes.end(),
+ accessorFunc);
// Now we emit the indices into the array.
+ size_t attrCount = sortedAttributes.size();
for (size_t i = 0; i < attrCount; i++) {
- *out << " "
- << "public static" << finalModifier
- << " int " << transform(entryName);
+ std::stringstream name;
+ name << transform(entryName);
// We may reference IDs from other packages, so prefix the entry name with
// the package.
const ResourceNameRef& itemName = sortedAttributes[i].second;
if (!itemName.package.empty() && packageNameToGenerate != itemName.package) {
- *out << "_" << transform(itemName.package);
+ name << "_" << transform(itemName.package);
}
- *out << "_" << transform(itemName.entry) << " = " << i << ";\n";
+ name << "_" << transform(itemName.entry);
+
+ outClassDef->addIntMember(name.str(), nullptr, i);
}
}
@@ -155,8 +148,8 @@
if (typeMask & android::ResTable_map::TYPE_STRING) {
processor->appendComment(
- "<p>May be a string value, using '\\;' to escape characters such as\n"
- "'\\n' or '\\uxxxx' for a unicode character;");
+ "<p>May be a string value, using '\\\\;' to escape characters such as\n"
+ "'\\\\n' or '\\\\uxxxx' for a unicode character;");
}
if (typeMask & android::ResTable_map::TYPE_INTEGER) {
@@ -222,14 +215,10 @@
}
}
-bool JavaClassGenerator::generateType(const StringPiece16& packageNameToGenerate,
- const ResourceTablePackage* package,
- const ResourceTableType* type,
- std::ostream* out) {
- const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
-
- std::u16string unmangledPackage;
- std::u16string unmangledName;
+bool JavaClassGenerator::writeEntriesForClass(ClassDefinitionWriter* outClassDef,
+ const StringPiece16& packageNameToGenerate,
+ const ResourceTablePackage* package,
+ const ResourceTableType* type) {
for (const auto& entry : type->entries) {
if (skipSymbol(entry->symbolStatus.state)) {
continue;
@@ -238,7 +227,8 @@
ResourceId id(package->id.value(), type->id.value(), entry->id.value());
assert(id.isValid());
- unmangledName = entry->name;
+ std::u16string unmangledPackage;
+ std::u16string unmangledName = entry->name;
if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
// The entry name was mangled, and we successfully unmangled it.
// Check that we want to emit this symbol.
@@ -246,12 +236,10 @@
// Skip the entry if it doesn't belong to the package we're writing.
continue;
}
- } else {
- if (packageNameToGenerate != package->name) {
- // We are processing a mangled package name,
- // but this is a non-mangled resource.
- continue;
- }
+ } else if (packageNameToGenerate != package->name) {
+ // We are processing a mangled package name,
+ // but this is a non-mangled resource.
+ continue;
}
if (!isValidSymbol(unmangledName)) {
@@ -262,39 +250,33 @@
return false;
}
+ // Build the comments and annotations for this entry.
+
+ AnnotationProcessor processor;
+ if (entry->symbolStatus.state != SymbolState::kUndefined) {
+ processor.appendComment(entry->symbolStatus.comment);
+ }
+
+ for (const auto& configValue : entry->values) {
+ processor.appendComment(configValue.value->getComment());
+ }
+
+ // If this is an Attribute, append the format Javadoc.
+ if (!entry->values.empty()) {
+ if (Attribute* attr = valueCast<Attribute>(entry->values.front().value.get())) {
+ // We list out the available values for the given attribute.
+ addAttributeFormatDoc(&processor, attr);
+ }
+ }
+
if (type->type == ResourceType::kStyleable) {
assert(!entry->values.empty());
- generateStyleable(packageNameToGenerate, unmangledName, static_cast<const Styleable*>(
- entry->values.front().value.get()), out);
+ const Styleable* styleable = static_cast<const Styleable*>(
+ entry->values.front().value.get());
+ writeStyleableEntryForClass(outClassDef, &processor, packageNameToGenerate,
+ unmangledName, styleable);
} else {
- AnnotationProcessor processor(" ");
- if (entry->symbolStatus.state != SymbolState::kUndefined) {
- processor.appendComment(entry->symbolStatus.comment);
- }
-
- for (const auto& configValue : entry->values) {
- processor.appendComment(configValue.value->getComment());
- }
-
- if (!entry->values.empty()) {
- if (Attribute* attr = valueCast<Attribute>(entry->values.front().value.get())) {
- // We list out the available values for the given attribute.
- addAttributeFormatDoc(&processor, attr);
- }
- }
-
- std::string comment = processor.buildComment();
- if (!comment.empty()) {
- *out << comment << "\n";
- }
-
- std::string annotations = processor.buildAnnotations();
- if (!annotations.empty()) {
- *out << annotations << "\n";
- }
-
- *out << " " << "public static" << finalModifier
- << " int " << transform(unmangledName) << " = " << id << ";\n";
+ outClassDef->addResourceMember(transform(unmangledName), &processor, id);
}
}
return true;
@@ -312,17 +294,42 @@
for (const auto& package : mTable->packages) {
for (const auto& type : package->types) {
- StringPiece16 typeStr;
if (type->type == ResourceType::kAttrPrivate) {
- typeStr = toString(ResourceType::kAttr);
- } else {
- typeStr = toString(type->type);
+ continue;
}
- *out << " public static final class " << typeStr << " {\n";
- if (!generateType(packageNameToGenerate, package.get(), type.get(), out)) {
+
+ ClassDefinitionWriterOptions classOptions;
+ classOptions.useFinalQualifier = mOptions.useFinal;
+ classOptions.forceCreationIfEmpty =
+ (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
+ ClassDefinitionWriter classDef(toString(type->type), classOptions);
+ bool result = writeEntriesForClass(&classDef, packageNameToGenerate,
+ package.get(), type.get());
+ if (!result) {
return false;
}
- *out << " }\n";
+
+ if (type->type == ResourceType::kAttr) {
+ // Also include private attributes in this same class.
+ auto iter = std::lower_bound(package->types.begin(), package->types.end(),
+ ResourceType::kAttrPrivate, cmp::lessThanType);
+ if (iter != package->types.end() && (*iter)->type == ResourceType::kAttrPrivate) {
+ result = writeEntriesForClass(&classDef, packageNameToGenerate,
+ package.get(), iter->get());
+ if (!result) {
+ return false;
+ }
+ }
+ }
+
+ AnnotationProcessor processor;
+ if (type->type == ResourceType::kStyleable &&
+ mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
+ // When generating a public R class, we don't want Styleable to be part of the API.
+ // It is only emitted for documentation purposes.
+ processor.appendComment("@doconly");
+ }
+ classDef.writeToStream(out, " ", &processor);
}
}
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index e53a765..023d6d6 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -27,6 +27,9 @@
namespace aapt {
+class AnnotationProcessor;
+class ClassDefinitionWriter;
+
struct JavaClassGeneratorOptions {
/*
* Specifies whether to use the 'final' modifier
@@ -40,9 +43,6 @@
kPublic,
};
- /*
- *
- */
SymbolTypes types = SymbolTypes::kAll;
};
@@ -69,15 +69,16 @@
const std::string& getError() const;
private:
- bool generateType(const StringPiece16& packageNameToGenerate,
- const ResourceTablePackage* package,
- const ResourceTableType* type,
- std::ostream* out);
+ bool writeEntriesForClass(ClassDefinitionWriter* outClassDef,
+ const StringPiece16& packageNameToGenerate,
+ const ResourceTablePackage* package,
+ const ResourceTableType* type);
- void generateStyleable(const StringPiece16& packageNameToGenerate,
- const std::u16string& entryName,
- const Styleable* styleable,
- std::ostream* out);
+ void writeStyleableEntryForClass(ClassDefinitionWriter* outClassDef,
+ AnnotationProcessor* processor,
+ const StringPiece16& packageNameToGenerate,
+ const std::u16string& entryName,
+ const Styleable* styleable);
bool skipSymbol(SymbolState state);
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 2dc387b..e9e7881 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -56,13 +56,13 @@
std::string output = out.str();
EXPECT_NE(std::string::npos,
- output.find("public static final int hey_man = 0x01020000;"));
+ output.find("public static final int hey_man=0x01020000;"));
EXPECT_NE(std::string::npos,
- output.find("public static final int[] hey_dude = {"));
+ output.find("public static final int[] hey_dude={"));
EXPECT_NE(std::string::npos,
- output.find("public static final int hey_dude_cool_attr = 0;"));
+ output.find("public static final int hey_dude_cool_attr=0;"));
}
TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) {
@@ -78,7 +78,7 @@
std::string output = out.str();
EXPECT_NE(std::string::npos, output.find("package com.android.internal;"));
- EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;"));
EXPECT_EQ(std::string::npos, output.find("two"));
EXPECT_EQ(std::string::npos, output.find("com_foo$two"));
}
@@ -86,6 +86,7 @@
TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) {
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
.setPackageId(u"android", 0x01)
+ .addSimple(u"@android:attr/two", ResourceId(0x01010001))
.addSimple(u"@android:^attr-private/one", ResourceId(0x01010000))
.build();
@@ -116,7 +117,7 @@
std::stringstream out;
ASSERT_TRUE(generator.generate(u"android", &out));
std::string output = out.str();
- EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;"));
EXPECT_EQ(std::string::npos, output.find("two"));
EXPECT_EQ(std::string::npos, output.find("three"));
}
@@ -127,8 +128,8 @@
std::stringstream out;
ASSERT_TRUE(generator.generate(u"android", &out));
std::string output = out.str();
- EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
- EXPECT_NE(std::string::npos, output.find("public static final int two = 0x01020001;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int two=0x01020001;"));
EXPECT_EQ(std::string::npos, output.find("three"));
}
@@ -138,9 +139,9 @@
std::stringstream out;
ASSERT_TRUE(generator.generate(u"android", &out));
std::string output = out.str();
- EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
- EXPECT_NE(std::string::npos, output.find("public static final int two = 0x01020001;"));
- EXPECT_NE(std::string::npos, output.find("public static final int three = 0x01020002;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int two=0x01020001;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int three=0x01020002;"));
}
}
@@ -194,8 +195,8 @@
EXPECT_TRUE(generator.generate(u"android", &out));
std::string output = out.str();
- EXPECT_NE(std::string::npos, output.find("int foo_bar ="));
- EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar ="));
+ EXPECT_NE(std::string::npos, output.find("int foo_bar="));
+ EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar="));
}
TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) {
@@ -218,7 +219,7 @@
* @deprecated
*/
@Deprecated
- public static final int foo = 0x01010000;)EOF"));
+ public static final int foo=0x01010000;)EOF"));
}
TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
index 901a344..d963d89 100644
--- a/tools/aapt2/java/ManifestClassGenerator.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -18,6 +18,7 @@
#include "XmlDom.h"
#include "java/AnnotationProcessor.h"
+#include "java/ClassDefinitionWriter.h"
#include "java/ManifestClassGenerator.h"
#include "util/Maybe.h"
@@ -58,8 +59,8 @@
return result;
}
-static bool writeSymbol(IDiagnostics* diag, const Source& source, xml::Element* el,
- std::ostream* out) {
+static bool writeSymbol(IDiagnostics* diag, ClassDefinitionWriter* outClassDef, const Source& source,
+ xml::Element* el) {
xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name");
if (!attr) {
diag->error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'");
@@ -72,18 +73,9 @@
return false;
}
- *out << "\n";
-
- if (!util::trimWhitespace(el->comment).empty()) {
- AnnotationProcessor processor(" ");
- processor.appendComment(el->comment);
- *out << processor.buildComment() << "\n";
- std::string annotations = processor.buildAnnotations();
- if (!annotations.empty()) {
- *out << annotations << "\n";
- }
- }
- *out << " public static final String " << result.value() << "=\"" << attr->value << "\";\n";
+ AnnotationProcessor processor;
+ processor.appendComment(el->comment);
+ outClassDef->addStringMember(result.value(), &processor, attr->value);
return true;
}
@@ -100,29 +92,32 @@
}
*out << "package " << package << ";\n\n"
- << "public class Manifest {\n";
+ << "public final class Manifest {\n";
bool error = false;
std::vector<xml::Element*> children = el->getChildElements();
+ ClassDefinitionWriterOptions classOptions;
+ classOptions.useFinalQualifier = true;
+ classOptions.forceCreationIfEmpty = false;
// First write out permissions.
- *out << " public static class permission {\n";
+ ClassDefinitionWriter classDef("permission", classOptions);
for (xml::Element* childEl : children) {
if (childEl->namespaceUri.empty() && childEl->name == u"permission") {
- error |= !writeSymbol(diag, res->file.source, childEl, out);
+ error |= !writeSymbol(diag, &classDef, res->file.source, childEl);
}
}
- *out << " }\n";
+ classDef.writeToStream(out, " ");
// Next write out permission groups.
- *out << " public static class permission_group {\n";
+ classDef = ClassDefinitionWriter("permission_group", classOptions);
for (xml::Element* childEl : children) {
if (childEl->namespaceUri.empty() && childEl->name == u"permission-group") {
- error |= !writeSymbol(diag, res->file.source, childEl, out);
+ error |= !writeSymbol(diag, &classDef, res->file.source, childEl);
}
}
- *out << " }\n";
+ classDef.writeToStream(out, " ");
*out << "}\n";
return !error;
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
index 1b5bc05..4081287 100644
--- a/tools/aapt2/java/ManifestClassGenerator_test.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -39,8 +39,9 @@
std::string actual = out.str();
- const size_t permissionClassPos = actual.find("public static class permission {");
- const size_t permissionGroupClassPos = actual.find("public static class permission_group {");
+ const size_t permissionClassPos = actual.find("public static final class permission {");
+ const size_t permissionGroupClassPos =
+ actual.find("public static final class permission_group {");
ASSERT_NE(std::string::npos, permissionClassPos);
ASSERT_NE(std::string::npos, permissionGroupClassPos);
@@ -113,7 +114,7 @@
* @hide
* @SystemApi
*/
- @android.annotations.SystemApi
+ @android.annotation.SystemApi
public static final String SECRET="android.permission.SECRET";)EOF"));
}
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
index 11fcc5d..c7e603e 100644
--- a/tools/aapt2/link/AutoVersioner.cpp
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -31,7 +31,7 @@
const int sdkVersionToGenerate) {
assert(sdkVersionToGenerate > config.sdkVersion);
const auto endIter = entry->values.end();
- auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThan);
+ auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThanConfig);
// The source config came from this list, so it should be here.
assert(iter != entry->values.end());
@@ -124,7 +124,7 @@
auto iter = std::lower_bound(entry->values.begin(),
entry->values.end(),
newConfig,
- cmp::lessThan);
+ cmp::lessThanConfig);
entry->values.insert(
iter,
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 0236e98..9ce3734 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -49,6 +49,7 @@
std::string manifestPath;
std::vector<std::string> includePaths;
Maybe<std::string> generateJavaClassPath;
+ std::vector<std::string> extraJavaPackages;
Maybe<std::string> generateProguardRulesPath;
bool noAutoVersion = false;
bool staticLib = false;
@@ -695,6 +696,9 @@
options.useFinal = false;
}
+ StringPiece16 actualPackage = mContext.getCompilationPackage();
+ StringPiece16 outputPackage = mContext.getCompilationPackage();
+
if (mOptions.privateSymbols) {
// If we defined a private symbols package, we only emit Public symbols
// to the original package, and private and public symbols to the private package.
@@ -706,16 +710,16 @@
}
options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
- if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
- mOptions.privateSymbols.value(), options)) {
- return 1;
- }
+ outputPackage = mOptions.privateSymbols.value();
+ }
- } else {
- // Emit Everything.
+ if (!writeJavaFile(&mergedTable, actualPackage, outputPackage, options)) {
+ return 1;
+ }
- if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
- mContext.getCompilationPackage(), options)) {
+ for (std::string& extraPackage : mOptions.extraJavaPackages) {
+ if (!writeJavaFile(&mergedTable, actualPackage, util::utf8ToUtf16(extraPackage),
+ options)) {
return 1;
}
}
@@ -770,6 +774,8 @@
"private symbols.\n"
"If not specified, public and private symbols will use the application's "
"package name", &privateSymbolsPackage)
+ .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
+ "package names", &options.extraJavaPackages)
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
if (!flags.parse("aapt2 link", args, &std::cerr)) {
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 636c2ba..1eea410 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -34,6 +34,9 @@
assert(mMasterPackage && "package name or ID already taken");
}
+/**
+ * This will merge packages with the same package name (or no package name).
+ */
bool TableMerger::merge(const Source& src, ResourceTable* table) {
const uint8_t desiredPackageId = mContext->getPackageId();
@@ -46,18 +49,37 @@
continue;
}
- bool manglePackage = false;
- if (!package->name.empty() && mContext->getCompilationPackage() != package->name) {
- manglePackage = true;
- mMergedPackages.insert(package->name);
+ if (package->name.empty() || mContext->getCompilationPackage() == package->name) {
+ // Merge here. Once the entries are merged and mangled, any references to
+ // them are still valid. This is because un-mangled references are
+ // mangled, then looked up at resolution time.
+ // Also, when linking, we convert references with no package name to use
+ // the compilation package name.
+ if (!doMerge(src, table, package.get(), false)) {
+ error = true;
+ }
+ }
+ }
+ return !error;
+}
+
+/**
+ * This will merge and mangle resources from a static library.
+ */
+bool TableMerger::mergeAndMangle(const Source& src, const StringPiece16& packageName,
+ ResourceTable* table) {
+ bool error = false;
+ for (auto& package : table->packages) {
+ // Warn of packages with an unrelated ID.
+ if (packageName != package->name) {
+ mContext->getDiagnostics()->warn(DiagMessage(src)
+ << "ignoring package " << package->name);
+ continue;
}
- // Merge here. Once the entries are merged and mangled, any references to
- // them are still valid. This is because un-mangled references are
- // mangled, then looked up at resolution time.
- // Also, when linking, we convert references with no package name to use
- // the compilation package name.
- if (!doMerge(src, table, package.get(), manglePackage)) {
+ bool mangle = packageName != mContext->getCompilationPackage();
+ mMergedPackages.insert(package->name);
+ if (!doMerge(src, table, package.get(), mangle)) {
error = true;
}
}
@@ -122,7 +144,7 @@
for (ResourceConfigValue& srcValue : srcEntry->values) {
auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(),
- srcValue.config, cmp::lessThan);
+ srcValue.config, cmp::lessThanConfig);
if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
const int collisionResult = ResourceTable::resolveValueCollision(
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 157c16e..c903f1b 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -60,8 +60,16 @@
return mMergedPackages;
}
+ /**
+ * Merges resources from the same or empty package. This is for local sources.
+ */
bool merge(const Source& src, ResourceTable* table);
+ /**
+ * Merges resources from the given package, mangling the name. This is for static libraries.
+ */
+ bool mergeAndMangle(const Source& src, const StringPiece16& package, ResourceTable* table);
+
private:
IAaptContext* mContext;
ResourceTable* mMasterTable;
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index fa7ce86..0af4314 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -60,7 +60,7 @@
TableMerger merger(mContext.get(), &finalTable);
ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_TRUE(merger.merge({}, tableB.get()));
+ ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get()));
EXPECT_TRUE(merger.getMergedPackages().count(u"com.app.b") != 0);
@@ -90,7 +90,7 @@
TableMerger merger(mContext.get(), &finalTable);
ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_TRUE(merger.merge({}, tableB.get()));
+ ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get()));
FileReference* f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/file");
ASSERT_NE(f, nullptr);
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 7309396..9a8b263 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -55,7 +55,7 @@
if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
const ConfigDescription kDefaultConfig;
auto iter = std::lower_bound(sr.entry->values.begin(), sr.entry->values.end(),
- kDefaultConfig, cmp::lessThan);
+ kDefaultConfig, cmp::lessThanConfig);
if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
// This resource has an Attribute.
diff --git a/tools/aapt2/util/Comparators.h b/tools/aapt2/util/Comparators.h
index 652018e..0ee0bf3 100644
--- a/tools/aapt2/util/Comparators.h
+++ b/tools/aapt2/util/Comparators.h
@@ -17,13 +17,20 @@
#ifndef AAPT_UTIL_COMPARATORS_H
#define AAPT_UTIL_COMPARATORS_H
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+
namespace aapt {
namespace cmp {
-inline bool lessThan(const ResourceConfigValue& a, const ConfigDescription& b) {
+inline bool lessThanConfig(const ResourceConfigValue& a, const ConfigDescription& b) {
return a.config < b;
}
+inline bool lessThanType(const std::unique_ptr<ResourceTableType>& a, ResourceType b) {
+ return a->type < b;
+}
+
} // namespace cmp
} // namespace aapt
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 7ee06f3..3c260a8 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -241,6 +241,13 @@
}
@Override
+ public void overridePendingAppTransitionMultiThumbFuture(
+ IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback startedCallback,
+ boolean scaleUp) throws RemoteException {
+
+ }
+
+ @Override
public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
IRemoteCallback callback0, IRemoteCallback callback1, boolean scaleUp) {
// TODO Auto-generated method stub
@@ -515,10 +522,23 @@
}
@Override
+ public int getDockedStackSide() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void setDockedStackResizing(boolean resizing) throws RemoteException {
+ }
+
+ @Override
public void cancelTaskWindowTransition(int taskId) {
}
@Override
+ public void cancelTaskThumbnailTransition(int taskId) {
+ }
+
+ @Override
public void endProlongedAnimations() {
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
index c2d8d0c..3662573 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
@@ -48,7 +48,7 @@
@Override
public void resized(Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5, Rect rect6,
- boolean b, Configuration configuration) throws RemoteException {
+ boolean b, Configuration configuration, Rect rect7) throws RemoteException {
// pass for now.
}