diff options
5 files changed, 313 insertions, 35 deletions
diff --git a/anttasks/src/com/android/ant/MultiApkExportTask.java b/anttasks/src/com/android/ant/MultiApkExportTask.java index 151b81b..8c9ee3e 100644 --- a/anttasks/src/com/android/ant/MultiApkExportTask.java +++ b/anttasks/src/com/android/ant/MultiApkExportTask.java @@ -73,7 +73,7 @@ public class MultiApkExportTask extends Task { String appPackage = getValidatedProperty(antProject, "package"); System.out.println("Multi APK export for: " + appPackage); - String version = getValidatedProperty(antProject, "version"); + String version = getValidatedProperty(antProject, "versionCode"); int versionCode; try { versionCode = Integer.parseInt(version); diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ApkData.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ApkData.java index 88f28f6..f2cd97f 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ApkData.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ApkData.java @@ -39,7 +39,6 @@ public class ApkData implements Comparable<ApkData> { private static final String PROP_PROJECT = "project"; private static final String PROP_MINOR = "minor"; private static final String PROP_BUILDINFO = "buildinfo"; - private static final String PROP_OUTPUTNAME = "outputname"; private String mOutputName; private String mRelativePath; @@ -149,30 +148,26 @@ public class ApkData implements Comparable<ApkData> { @Override public String toString() { - StringBuilder sb = new StringBuilder(); - write(sb, PROP_OUTPUTNAME, mOutputName); - write(sb, PROP_BUILDINFO, mBuildInfo); - sb.append(getLogLine()); - - return sb.toString(); + return getLogLine(); } public String getLogLine() { StringBuilder sb = new StringBuilder(); - sb.append(mBuildInfo).append(':'); + sb.append(mOutputName).append(':'); + write(sb, PROP_BUILDINFO, mBuildInfo); write(sb, PROP_MINOR, mMinor); write(sb, PROP_PROJECT, mRelativePath); write(sb, PROP_API, mMinSdkVersion); if (mGlVersion != ManifestData.GL_ES_VERSION_NOT_SET) { - write(sb, PROP_GL, mGlVersion); + write(sb, PROP_GL, "0x" + Integer.toHexString(mGlVersion)); } if (mAbi != null) { write(sb, PROP_ABI, mAbi); } - write(sb, PROP_SCREENS, mSupportsScreens); + write(sb, PROP_SCREENS, mSupportsScreens.getEncodedValues()); return sb.toString(); } @@ -195,7 +190,7 @@ public class ApkData implements Comparable<ApkData> { return 1; } - comp = mSupportsScreens.compareTo(o.mSupportsScreens); + comp = mSupportsScreens.compareScreenSizesWith(o.mSupportsScreens); if (comp != 0) return comp; if (mGlVersion != ManifestData.GL_ES_VERSION_NOT_SET) { @@ -212,6 +207,24 @@ public class ApkData implements Comparable<ApkData> { return 0; } + public boolean hasSameApkProperties(ApkData apk) { + if (mMinSdkVersion != apk.mMinSdkVersion || + mSupportsScreens.equals(apk.mSupportsScreens) == false || + mGlVersion != apk.mGlVersion) { + return false; + } + + if (mAbi != null) { + if (mAbi.equals(apk.mAbi) == false) { + return false; + } + } else if (apk.mAbi != null) { + return false; + } + + return true; + } + /** * reads the apk description from a log line. * @param line The fields to read, comma-separated. @@ -220,7 +233,7 @@ public class ApkData implements Comparable<ApkData> { */ public void initFromLogLine(String line) { int colon = line.indexOf(':'); - mBuildInfo = Integer.parseInt(line.substring(0, colon)); + mOutputName = line.substring(0, colon); String[] properties = line.substring(colon+1).split(";"); HashMap<String, String> map = new HashMap<String, String>(); for (String prop : properties) { @@ -231,13 +244,19 @@ public class ApkData implements Comparable<ApkData> { } private void setValues(Map<String, String> values) { + mBuildInfo = Integer.parseInt(values.get(PROP_BUILDINFO)); mMinor = Integer.parseInt(values.get(PROP_MINOR)); mRelativePath = values.get(PROP_PROJECT); mMinSdkVersion = Integer.parseInt(values.get(PROP_API)); String tmp = values.get(PROP_GL); if (tmp != null) { - mGlVersion = Integer.parseInt(tmp); + try { + mGlVersion = Integer.decode(tmp); + } catch (NumberFormatException e) { + // pass. This is probably due to a manual edit, and it'll most likely + // generate an error when matching the log to the current setup. + } } tmp = values.get(PROP_ABI); diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java index 693a5f8..ae70e6f 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java @@ -28,6 +28,7 @@ import com.android.sdklib.io.StreamException; import com.android.sdklib.io.IAbstractFolder.FilenameFilter; import com.android.sdklib.xml.AndroidManifestParser; import com.android.sdklib.xml.ManifestData; +import com.android.sdklib.xml.ManifestData.SupportsScreens; import org.xml.sax.SAXException; @@ -126,14 +127,14 @@ public class MultiApkExportHelper { if (previousApks.length != apks.length) { throw new ExportException(String.format( "Project export is setup differently from previous export at versionCode %d.\n" + - "Any change in the multi-apk configuration requires an increment of the versionCode.", + "Any change in the multi-apk configuration requires an increment of the versionCode in export.properties.", mVersionCode)); } for (int i = 0 ; i < previousApks.length ; i++) { // update the minor value from what is in the log file. apks[i].setMinor(previousApks[i].getMinor()); - if (apks[i].compareTo(previousApks[i]) != 0) { + if (apks[i].hasSameApkProperties(previousApks[i]) == false) { throw new ExportException(String.format( "Project export is setup differently from previous export at versionCode %d.\n" + "Any change in the multi-apk configuration requires an increment of the versionCode.", @@ -143,7 +144,6 @@ public class MultiApkExportHelper { } return apks; - } /** @@ -161,7 +161,7 @@ public class MultiApkExportHelper { "# Multi-APK BUILD LOG.\n" + "# This file serves two purpose:\n" + "# - A log of what was built, showing what went in each APK and their properties.\n" + - "# You can refer to this if you get a bug report for a specific versionCode." + + "# You can refer to this if you get a bug report for a specific versionCode.\n" + "# - A way to update builds through minor revisions for specific APKs.\n" + "# Only edit manually to change the minor properties for build you wish to respin.\n" + "# Note that all APKs will be regenerated all the time.\n"); @@ -171,7 +171,7 @@ public class MultiApkExportHelper { writer.append( "# The format of the following lines is:\n" + - "# <build number>:<property1>;<property2>;<property3>;...\n" + + "# <filename>:<property1>;<property2>;<property3>;...\n" + "# Properties are written as <name>=<value>\n"); for (ApkData apk : apks) { @@ -309,19 +309,58 @@ public class MultiApkExportHelper { // - GL version // - ABI (not managed at the Manifest level). // if those values are the same between 2 manifest, then it's an error. - if (minSdkVersion == previousManifest.data.getMinSdkVersion() && - manifestData.getSupportsScreensValues().equals( - previousManifest.data.getSupportsScreensValues()) && - manifestData.getGlEsVersion() == previousManifest.data.getGlEsVersion()) { - throw new ExportException(String.format( - "Android manifests must differ in at least one of the following values:\n" + - "- minSdkVersion\n" + - "- SupportsScreen\n" + - "- GL ES version.\n" + - "%1$s and %2$s are considered identical for multi-apk export.", - androidManifest.getOsLocation(), - previousManifest.file.getOsLocation())); + // first the minSdkVersion. + if (minSdkVersion == previousManifest.data.getMinSdkVersion()) { + // if it's the same compare the rest. + SupportsScreens currentSS = manifestData.getSupportsScreensValues(); + SupportsScreens previousSS = previousManifest.data.getSupportsScreensValues(); + boolean sameSupportsScreens = currentSS.hasSameScreenSupportAs(previousSS); + + // if it's the same, then it's an error. Can't export 2 projects that have the + // same approved (for multi-apk export) hard-properties. + if (manifestData.getGlEsVersion() == previousManifest.data.getGlEsVersion() && + sameSupportsScreens) { + + throw new ExportException(String.format( + "Android manifests must differ in at least one of the following values:\n" + + "- minSdkVersion\n" + + "- SupportsScreen (screen sizes only)\n" + + "- GL ES version.\n" + + "%1$s and %2$s are considered identical for multi-apk export.", + androidManifest.getOsLocation(), + previousManifest.file.getOsLocation())); + } + + // At this point, either supports-screens or GL are different. + // Because supports-screens is the highest priority properties to be + // (potentially) different, we must do some extra checks on it. + // It must either be the same in both projects (difference is only on GL value), + // or follow theses rules: + // - Property in each projects must be strictly different, ie both projects + // cannot support the same screen size(s). + // - Property in each projects cannot overlap, ie a projects cannot support + // both a lower and a higher screen size than the other project. + // (ie APK1 supports small/large and APK2 supports normal). + if (sameSupportsScreens == false) { + if (currentSS.hasStrictlyDifferentScreenSupportAs(previousSS) == false) { + throw new ExportException(String.format( + "APK differentiation by Supports-Screens cannot support different APKs supporting the same screen size.\n" + + "%1$s supports %2$s\n" + + "%3$s supports %4$s\n", + androidManifest.getOsLocation(), currentSS.toString(), + previousManifest.file.getOsLocation(), previousSS.toString())); + } + + if (currentSS.overlapWith(previousSS)) { + throw new ExportException(String.format( + "Unable to compute APK priority due to incompatible difference in Supports-Screens values.\n" + + "%1$s supports %2$s\n" + + "%3$s supports %4$s\n", + androidManifest.getOsLocation(), currentSS.toString(), + previousManifest.file.getOsLocation(), previousSS.toString())); + } + } } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/ManifestData.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/ManifestData.java index da96cd4..cc1f3dd 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/ManifestData.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/ManifestData.java @@ -158,7 +158,7 @@ public final class ManifestData { * * To get an instance with all the actual values, use {@link #resolveSupportsScreensValues(int)} */ - public final static class SupportsScreens implements Comparable<SupportsScreens> { + public final static class SupportsScreens { private Boolean mResizeable; private Boolean mAnyDensity; private Boolean mSmallScreens; @@ -168,6 +168,11 @@ public final class ManifestData { public SupportsScreens() { } + /** + * Instantiate an instance from a string. The string must have been created with + * {@link #getEncodedValues()}. + * @param value the string. + */ public SupportsScreens(String value) { String[] values = value.split("\\|"); @@ -303,15 +308,144 @@ public final class ManifestData { return false; } - public int compareTo(SupportsScreens o) { + /** + * Returns true if the two instances support the same screen sizes. + * This is similar to {@link #equals(Object)} except that it ignores the values of + * {@link #getAnyDensity()} and {@link #getResizeable()}. + * @param support the other instance to compare to. + * @return true if the two instances support the same screen sizes. + */ + public boolean hasSameScreenSupportAs(SupportsScreens support) { + // since all the fields are guaranteed to be either Boolean.TRUE or Boolean.FALSE + // (or null), we can simply check they are identical and not bother with + // calling equals (which would require to check != null. + // see #getConstanntBoolean(Boolean) + + // This only checks that matter here are the screen sizes. resizeable and anyDensity + // are not checked. + return mSmallScreens == support.mSmallScreens && + mNormalScreens == support.mNormalScreens && + mLargeScreens == support.mLargeScreens; + } + + /** + * Returns true if the two instances have strictly different screen size support. + * This means that there is no screen size that they both support. + * @param support the other instance to compare to. + * @return true if they are stricly different. + */ + public boolean hasStrictlyDifferentScreenSupportAs(SupportsScreens support) { + // since all the fields are guaranteed to be either Boolean.TRUE or Boolean.FALSE + // (or null), we can simply check they are identical and not bother with + // calling equals (which would require to check != null. + // see #getConstanntBoolean(Boolean) + + // This only checks that matter here are the screen sizes. resizeable and anyDensity + // are not checked. + return (mSmallScreens != Boolean.TRUE || support.mSmallScreens != Boolean.TRUE) && + (mNormalScreens != Boolean.TRUE || support.mNormalScreens != Boolean.TRUE) && + (mLargeScreens != Boolean.TRUE || support.mLargeScreens != Boolean.TRUE); + } + + /** + * Comparison of 2 Supports-screens. This only uses screen sizes (ignores resizeable and + * anyDensity), and considers that + * {@link #hasStrictlyDifferentScreenSupportAs(SupportsScreens)} returns true and + * {@link #overlapWith(SupportsScreens)} returns false. + * @throws IllegalArgumentException if the two instanced are not strictly different or + * overlap each other + * @see #hasStrictlyDifferentScreenSupportAs(SupportsScreens) + * @see #overlapWith(SupportsScreens) + */ + public int compareScreenSizesWith(SupportsScreens o) { + if (hasStrictlyDifferentScreenSupportAs(o) == false) { + throw new IllegalArgumentException("The two instances are not strictly different."); + } + if (overlapWith(o)) { + throw new IllegalArgumentException("The two instances overlap each other."); + } + + int comp = mLargeScreens.compareTo(o.mLargeScreens); + if (comp != 0) return comp; + + comp = mNormalScreens.compareTo(o.mNormalScreens); + if (comp != 0) return comp; + + comp = mSmallScreens.compareTo(o.mSmallScreens); + if (comp != 0) return comp; + return 0; } - @Override - public String toString() { + /** + * Returns a string encoding of the content of the instance. This string can be used to + * instantiate a {@link SupportsScreens} object through + * {@link #SupportsScreens(String)}. + */ + public String getEncodedValues() { return String.format("%1$s|%2$s|%3$s|%4$s|%5$s", mAnyDensity, mResizeable, mSmallScreens, mNormalScreens, mLargeScreens); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + boolean alreadyOutputSomething = false; + + if (Boolean.TRUE.equals(mSmallScreens)) { + alreadyOutputSomething = true; + sb.append("small"); + } + + if (Boolean.TRUE.equals(mNormalScreens)) { + if (alreadyOutputSomething) { + sb.append(", "); + } + alreadyOutputSomething = true; + sb.append("normal"); + } + + if (Boolean.TRUE.equals(mLargeScreens)) { + if (alreadyOutputSomething) { + sb.append(", "); + } + alreadyOutputSomething = true; + sb.append("large"); + } + + if (alreadyOutputSomething == false) { + sb.append("<none>"); + } + + return sb.toString(); + } + + /** + * Returns true if the two instance overlap with each other. + * This can happen if one instances supports a size, when the other instance doesn't while + * supporting a size above and a size below. + * @param otherSS the other supports-screens to compare to. + */ + public boolean overlapWith(SupportsScreens otherSS) { + if (mSmallScreens == null || mNormalScreens == null || mLargeScreens == null || + otherSS.mSmallScreens == null || otherSS.mNormalScreens == null || + otherSS.mLargeScreens == null) { + throw new IllegalArgumentException("Some screen sizes Boolean are not initialized"); + } + + if (mSmallScreens == Boolean.TRUE && mNormalScreens == Boolean.FALSE && + mLargeScreens == Boolean.TRUE) { + return otherSS.mNormalScreens == Boolean.TRUE; + } + + if (otherSS.mSmallScreens == Boolean.TRUE && otherSS.mNormalScreens == Boolean.FALSE && + otherSS.mLargeScreens == Boolean.TRUE) { + return mNormalScreens == Boolean.TRUE; + } + + return false; + } } /** diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/xml/SupportsScreensTest.java b/sdkmanager/libs/sdklib/tests/com/android/sdklib/xml/SupportsScreensTest.java index c0cb12f..baf13b1 100644 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/xml/SupportsScreensTest.java +++ b/sdkmanager/libs/sdklib/tests/com/android/sdklib/xml/SupportsScreensTest.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.sdklib.xml; import com.android.sdklib.xml.ManifestData.SupportsScreens; @@ -57,4 +73,74 @@ public class SupportsScreensTest extends TestCase { assertEquals(Boolean.TRUE, supportsScreens.getNormalScreens()); assertEquals(Boolean.FALSE, supportsScreens.getLargeScreens()); } + + public void testOverlapWith() { + SupportsScreens supportsN = new SupportsScreens("false|false|false|true|false"); + SupportsScreens supportsSL = new SupportsScreens("false|false|true|false|true"); + + assertTrue(supportsN.overlapWith(supportsSL)); + assertTrue(supportsSL.overlapWith(supportsN)); + } + + public void testCompareScreenSizesWith() { + // set instance that support all combo of the three sizes. + SupportsScreens supportsS = new SupportsScreens("false|false|true|false|false"); + SupportsScreens supportsN = new SupportsScreens("false|false|false|true|false"); + SupportsScreens supportsL = new SupportsScreens("false|false|false|false|true"); + SupportsScreens supportsSL = new SupportsScreens("false|false|true|false|true"); + + assertEquals(-1, supportsS.compareScreenSizesWith(supportsN)); + assertEquals( 1, supportsN.compareScreenSizesWith(supportsS)); + assertEquals(-1, supportsN.compareScreenSizesWith(supportsL)); + assertEquals(-1, supportsS.compareScreenSizesWith(supportsL)); + + // test thrown exception for overlapWith == true + try { + supportsSL.compareScreenSizesWith(supportsN); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // success! + } + + // test thrown exception for hasStrictlyDifferentScreenSupportAs == false + try { + supportsSL.compareScreenSizesWith(supportsS); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // success! + } + + } + + public void testHasStrictlyDifferentScreenSupportAs() { + SupportsScreens supportsS = new SupportsScreens("false|false|true|false|false"); + SupportsScreens supportsN = new SupportsScreens("false|false|false|true|false"); + SupportsScreens supportsL = new SupportsScreens("false|false|false|false|true"); + + SupportsScreens supportsSN = new SupportsScreens("false|false|true|true|false"); + SupportsScreens supportsNL = new SupportsScreens("false|false|false|true|true"); + SupportsScreens supportsSL = new SupportsScreens("false|false|true|false|true"); + SupportsScreens supportsSNL = new SupportsScreens("false|false|true|true|true"); + + assertTrue(supportsS.hasStrictlyDifferentScreenSupportAs(supportsN)); + assertTrue(supportsS.hasStrictlyDifferentScreenSupportAs(supportsL)); + assertTrue(supportsN.hasStrictlyDifferentScreenSupportAs(supportsS)); + assertTrue(supportsN.hasStrictlyDifferentScreenSupportAs(supportsL)); + assertTrue(supportsL.hasStrictlyDifferentScreenSupportAs(supportsS)); + assertTrue(supportsL.hasStrictlyDifferentScreenSupportAs(supportsN)); + + + assertFalse(supportsSN.hasStrictlyDifferentScreenSupportAs(supportsS)); + assertFalse(supportsSN.hasStrictlyDifferentScreenSupportAs(supportsN)); + assertTrue(supportsSN.hasStrictlyDifferentScreenSupportAs(supportsL)); + assertFalse(supportsSL.hasStrictlyDifferentScreenSupportAs(supportsS)); + assertTrue(supportsSL.hasStrictlyDifferentScreenSupportAs(supportsN)); + assertFalse(supportsSL.hasStrictlyDifferentScreenSupportAs(supportsL)); + assertTrue(supportsNL.hasStrictlyDifferentScreenSupportAs(supportsS)); + assertFalse(supportsNL.hasStrictlyDifferentScreenSupportAs(supportsN)); + assertFalse(supportsNL.hasStrictlyDifferentScreenSupportAs(supportsL)); + assertFalse(supportsSNL.hasStrictlyDifferentScreenSupportAs(supportsS)); + assertFalse(supportsSNL.hasStrictlyDifferentScreenSupportAs(supportsN)); + assertFalse(supportsSNL.hasStrictlyDifferentScreenSupportAs(supportsL)); + } } |