Merge "Allow non default root CAs to be not subject to algorithm restrictions"
diff --git a/libart/src/main/java/dalvik/system/ClassExt.java b/libart/src/main/java/dalvik/system/ClassExt.java
index 772bd2e..3daa971 100644
--- a/libart/src/main/java/dalvik/system/ClassExt.java
+++ b/libart/src/main/java/dalvik/system/ClassExt.java
@@ -48,7 +48,7 @@
private Object obsoleteMethods;
/**
- * If set, the bytes of the original dex-file associated with the related class.
+ * If set, the bytes or DexCache of the original dex-file associated with the related class.
*
* In this instance 'original' means either (1) the dex-file loaded for this class when it was
* first loaded after all non-retransformation capable transformations had been performed but
@@ -59,7 +59,7 @@
*
* This field is a logical part of the 'Class' type.
*/
- private byte[] originalDexFile;
+ private Object originalDexFile;
/**
* If class verify fails, we must return same error on subsequent tries. We may store either
diff --git a/luni/src/main/java/libcore/util/TimeZoneDataFiles.java b/luni/src/main/java/libcore/util/TimeZoneDataFiles.java
new file mode 100644
index 0000000..8361339
--- /dev/null
+++ b/luni/src/main/java/libcore/util/TimeZoneDataFiles.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.util;
+
+/**
+ * Utility methods associated with finding updateable time zone data files.
+ */
+public final class TimeZoneDataFiles {
+
+ private static final String ANDROID_ROOT_ENV = "ANDROID_ROOT";
+ private static final String ANDROID_DATA_ENV = "ANDROID_DATA";
+
+ private TimeZoneDataFiles() {}
+
+ // VisibleForTesting
+ public static String[] getTimeZoneFilePaths(String fileName) {
+ return new String[] {
+ getDataTimeZoneFile(fileName),
+ getSystemTimeZoneFile(fileName)
+ };
+ }
+
+ private static String getDataTimeZoneFile(String fileName) {
+ return System.getenv(ANDROID_DATA_ENV) + "/misc/zoneinfo/current/" + fileName;
+ }
+
+ // VisibleForTesting
+ public static String getSystemTimeZoneFile(String fileName) {
+ return System.getenv(ANDROID_ROOT_ENV) + "/usr/share/zoneinfo/" + fileName;
+ }
+
+ public static String generateIcuDataPath() {
+ StringBuilder icuDataPathBuilder = new StringBuilder();
+ // ICU should first look in ANDROID_DATA. This is used for (optional) timezone data.
+ String dataIcuDataPath = getEnvironmentPath(ANDROID_DATA_ENV, "/misc/zoneinfo/current/icu");
+ if (dataIcuDataPath != null) {
+ icuDataPathBuilder.append(dataIcuDataPath);
+ }
+
+ // ICU should always look in ANDROID_ROOT.
+ String systemIcuDataPath = getEnvironmentPath(ANDROID_ROOT_ENV, "/usr/icu");
+ if (systemIcuDataPath != null) {
+ if (icuDataPathBuilder.length() > 0) {
+ icuDataPathBuilder.append(":");
+ }
+ icuDataPathBuilder.append(systemIcuDataPath);
+ }
+ return icuDataPathBuilder.toString();
+ }
+
+ /**
+ * Creates a path by combining the value of an environment variable with a relative path.
+ * Returns {@code null} if the environment variable is not set.
+ */
+ private static String getEnvironmentPath(String environmentVariable, String path) {
+ String variable = System.getenv(environmentVariable);
+ if (variable == null) {
+ return null;
+ }
+ return variable + path;
+ }
+}
diff --git a/luni/src/main/java/libcore/util/ZoneInfoDB.java b/luni/src/main/java/libcore/util/ZoneInfoDB.java
index d9ff323..acb9c12 100644
--- a/luni/src/main/java/libcore/util/ZoneInfoDB.java
+++ b/luni/src/main/java/libcore/util/ZoneInfoDB.java
@@ -37,9 +37,12 @@
* @hide - used to implement TimeZone
*/
public final class ZoneInfoDB {
- private static final TzData DATA = TzData.loadTzDataWithFallback(
- System.getenv("ANDROID_DATA") + "/misc/zoneinfo/current/tzdata",
- System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/tzdata");
+
+ // VisibleForTesting
+ public static final String TZDATA_FILE = "tzdata";
+
+ private static final TzData DATA =
+ TzData.loadTzDataWithFallback(TimeZoneDataFiles.getTimeZoneFilePaths(TZDATA_FILE));
public static class TzData {
@@ -114,7 +117,7 @@
// We didn't find any usable tzdata on disk, so let's just hard-code knowledge of "GMT".
// This is actually implemented in TimeZone itself, so if this is the only time zone
// we report, we won't be asked any more questions.
- System.logE("Couldn't find any tzdata!");
+ System.logE("Couldn't find any " + TZDATA_FILE + " file!");
return TzData.createFallback();
}
@@ -183,7 +186,7 @@
// Something's wrong with the file.
// Log the problem and return false so we try the next choice.
- System.logE("tzdata file \"" + path + "\" was present but invalid!", ex);
+ System.logE(TZDATA_FILE + " file \"" + path + "\" was present but invalid!", ex);
return false;
}
}
diff --git a/luni/src/test/java/libcore/java/lang/SystemTest.java b/luni/src/test/java/libcore/java/lang/SystemTest.java
index 2de659d..48f4591 100644
--- a/luni/src/test/java/libcore/java/lang/SystemTest.java
+++ b/luni/src/test/java/libcore/java/lang/SystemTest.java
@@ -16,17 +16,16 @@
package libcore.java.lang;
+import junit.framework.TestCase;
+
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.lang.SecurityException;
-import java.lang.SecurityManager;
import java.util.Formatter;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
-import junit.framework.TestCase;
public class SystemTest extends TestCase {
@@ -250,14 +249,4 @@
} catch (SecurityException expected) {
}
}
-
- // http://b/34867424
- public void testIcuPathIncludesTimeZoneOverride() {
- String icuDataPath = System.getProperty("android.icu.impl.ICUBinary.dataPath");
- String[] paths = icuDataPath.split(":");
- assertEquals(2, paths.length);
-
- assertTrue(paths[0].contains("/misc/zoneinfo/current/icu"));
- assertTrue(paths[1].contains("/usr/icu"));
- }
}
diff --git a/luni/src/test/java/libcore/java/util/TimeZoneTest.java b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
index 6b692cf..375eb36 100644
--- a/luni/src/test/java/libcore/java/util/TimeZoneTest.java
+++ b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
@@ -216,15 +216,26 @@
// http://b/7955614 and http://b/8026776.
public void testDisplayNames() throws Exception {
+ checkDisplayNames(Locale.US);
+ }
+
+ public void testDisplayNames_nonUS() throws Exception {
+ // run checkDisplayNames with an arbitrary set of Locales.
+ checkDisplayNames(Locale.CHINESE);
+ checkDisplayNames(Locale.FRENCH);
+ checkDisplayNames(Locale.forLanguageTag("bn-BD"));
+ }
+
+ public void checkDisplayNames(Locale locale) throws Exception {
// Check that there are no time zones that use DST but have the same display name for
// both standard and daylight time.
StringBuilder failures = new StringBuilder();
for (String id : TimeZone.getAvailableIDs()) {
TimeZone tz = TimeZone.getTimeZone(id);
- String longDst = tz.getDisplayName(true, TimeZone.LONG, Locale.US);
- String longStd = tz.getDisplayName(false, TimeZone.LONG, Locale.US);
- String shortDst = tz.getDisplayName(true, TimeZone.SHORT, Locale.US);
- String shortStd = tz.getDisplayName(false, TimeZone.SHORT, Locale.US);
+ String longDst = tz.getDisplayName(true, TimeZone.LONG, locale);
+ String longStd = tz.getDisplayName(false, TimeZone.LONG, locale);
+ String shortDst = tz.getDisplayName(true, TimeZone.SHORT, locale);
+ String shortStd = tz.getDisplayName(false, TimeZone.SHORT, locale);
if (tz.useDaylightTime()) {
// The long std and dst strings must differ!
@@ -329,15 +340,6 @@
return String.format("GMT%c%02d:%02d", sign, offset / 60, offset % 60);
}
- public void testAllDisplayNames() throws Exception {
- for (Locale locale : Locale.getAvailableLocales()) {
- for (String id : TimeZone.getAvailableIDs()) {
- TimeZone tz = TimeZone.getTimeZone(id);
- assertNotNull(tz.getDisplayName(false, TimeZone.LONG, locale));
- }
- }
- }
-
// http://b/18839557
public void testOverflowing32BitUnixDates() {
final TimeZone tz = TimeZone.getTimeZone("America/New_York");
diff --git a/luni/src/test/java/libcore/javax/crypto/CipherOutputStreamTest.java b/luni/src/test/java/libcore/javax/crypto/CipherOutputStreamTest.java
new file mode 100644
index 0000000..dd9a9a0
--- /dev/null
+++ b/luni/src/test/java/libcore/javax/crypto/CipherOutputStreamTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.javax.crypto;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.spec.AlgorithmParameterSpec;
+import javax.crypto.AEADBadTagException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+public final class CipherOutputStreamTest extends TestCase {
+
+ // From b/36636576. CipherOutputStream had a bug where it would ignore exceptions
+ // thrown during close().
+ public void testDecryptCorruptGCM() throws Exception {
+ for (Provider provider : Security.getProviders()) {
+ Cipher cipher;
+ try {
+ cipher = Cipher.getInstance("AES/GCM/NoPadding", provider);
+ } catch (NoSuchAlgorithmException e) {
+ continue;
+ }
+ SecretKey key;
+ if (provider.getName().equals("AndroidKeyStoreBCWorkaround")) {
+ key = getAndroidKeyStoreSecretKey();
+ } else {
+ KeyGenerator keygen = KeyGenerator.getInstance("AES");
+ keygen.init(256);
+ key = keygen.generateKey();
+ }
+ GCMParameterSpec params = new GCMParameterSpec(128, new byte[12]);
+ byte[] unencrypted = new byte[200];
+
+ // Normal providers require specifying the IV, but KeyStore prohibits it, so
+ // we have to special-case it
+ if (provider.getName().equals("AndroidKeyStoreBCWorkaround")) {
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ } else {
+ cipher.init(Cipher.ENCRYPT_MODE, key, params);
+ }
+ byte[] encrypted = cipher.doFinal(unencrypted);
+
+ // Corrupt the final byte, which will corrupt the authentication tag
+ encrypted[encrypted.length - 1] ^= 1;
+
+ cipher.init(Cipher.DECRYPT_MODE, key, params);
+ CipherOutputStream cos = new CipherOutputStream(new ByteArrayOutputStream(), cipher);
+ try {
+ cos.write(encrypted);
+ cos.close();
+ fail("Writing a corrupted stream should throw an exception."
+ + " Provider: " + provider);
+ } catch (IOException expected) {
+ assertTrue(expected.getCause() instanceof AEADBadTagException);
+ }
+ }
+
+ }
+
+ // The AndroidKeyStoreBCWorkaround provider can't use keys created by anything
+ // but Android KeyStore, which requires using its own parameters class to create
+ // keys. Since we're in javax, we can't link against the frameworks classes, so
+ // we have to use reflection to make a suitable key. This will always be safe
+ // because if we're making a key for AndroidKeyStoreBCWorkaround, the KeyStore
+ // classes must be present.
+ private static SecretKey getAndroidKeyStoreSecretKey() throws Exception {
+ KeyGenerator keygen = KeyGenerator.getInstance("AES", "AndroidKeyStore");
+ Class<?> keyParamsBuilderClass = keygen.getClass().getClassLoader().loadClass(
+ "android.security.keystore.KeyGenParameterSpec$Builder");
+ Object keyParamsBuilder = keyParamsBuilderClass.getConstructor(String.class, Integer.TYPE)
+ // 3 is PURPOSE_ENCRYPT | PURPOSE_DECRYPT
+ .newInstance("testDecryptCorruptGCM", 3);
+ keyParamsBuilderClass.getMethod("setBlockModes", new Class[]{String[].class})
+ .invoke(keyParamsBuilder, new Object[]{new String[]{"GCM"}});
+ keyParamsBuilderClass.getMethod("setEncryptionPaddings", new Class[]{String[].class})
+ .invoke(keyParamsBuilder, new Object[]{new String[]{"NoPadding"}});
+ AlgorithmParameterSpec spec = (AlgorithmParameterSpec)
+ keyParamsBuilderClass.getMethod("build", new Class[]{}).invoke(keyParamsBuilder);
+ keygen.init(spec);
+ return keygen.generateKey();
+ }
+}
diff --git a/luni/src/test/java/libcore/util/TimeZoneDataFilesTest.java b/luni/src/test/java/libcore/util/TimeZoneDataFilesTest.java
new file mode 100644
index 0000000..efba900
--- /dev/null
+++ b/luni/src/test/java/libcore/util/TimeZoneDataFilesTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TimeZoneDataFilesTest {
+
+ @Test
+ public void getTimeZoneFilePaths() {
+ String[] paths = TimeZoneDataFiles.getTimeZoneFilePaths("foo");
+ assertEquals(2, paths.length);
+
+ assertTrue(paths[0].contains("/misc/zoneinfo/current/"));
+ assertTrue(paths[0].endsWith("foo"));
+
+ assertTrue(paths[1].contains("/usr/share/zoneinfo/"));
+ assertTrue(paths[1].endsWith("foo"));
+ }
+
+ // http://b/34867424
+ @Test
+ public void generateIcuDataPath_includesTimeZoneOverride() {
+ String icuDataPath = System.getProperty("android.icu.impl.ICUBinary.dataPath");
+ assertEquals(icuDataPath, TimeZoneDataFiles.generateIcuDataPath());
+
+ String[] paths = icuDataPath.split(":");
+ assertEquals(2, paths.length);
+
+ assertTrue(paths[0].contains("/misc/zoneinfo/current/icu"));
+ assertTrue(paths[1].contains("/usr/icu"));
+ }
+}
diff --git a/luni/src/test/java/libcore/util/ZoneInfoDBTest.java b/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
index 901519e..e6ec4df 100644
--- a/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
+++ b/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
@@ -28,27 +28,27 @@
public class ZoneInfoDBTest extends junit.framework.TestCase {
// The base tzdata file, always present on a device.
- private static final String TZDATA_IN_ROOT =
- System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/tzdata";
+ private static final String SYSTEM_TZDATA_FILE =
+ TimeZoneDataFiles.getSystemTimeZoneFile(ZoneInfoDB.TZDATA_FILE);
// An empty override file should fall back to the default file.
public void testLoadTzDataWithFallback_emptyOverrideFile() throws Exception {
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
String emptyFilePath = makeEmptyFile().getPath();
ZoneInfoDB.TzData dataWithEmptyOverride =
- ZoneInfoDB.TzData.loadTzDataWithFallback(emptyFilePath, TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData.loadTzDataWithFallback(emptyFilePath, SYSTEM_TZDATA_FILE);
assertEquals(data.getVersion(), dataWithEmptyOverride.getVersion());
assertEquals(data.getAvailableIDs().length, dataWithEmptyOverride.getAvailableIDs().length);
}
// A corrupt override file should fall back to the default file.
public void testLoadTzDataWithFallback_corruptOverrideFile() throws Exception {
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
String corruptFilePath = makeCorruptFile().getPath();
ZoneInfoDB.TzData dataWithCorruptOverride =
- ZoneInfoDB.TzData.loadTzDataWithFallback(corruptFilePath, TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData.loadTzDataWithFallback(corruptFilePath, SYSTEM_TZDATA_FILE);
assertEquals(data.getVersion(), dataWithCorruptOverride.getVersion());
assertEquals(data.getAvailableIDs().length, dataWithCorruptOverride.getAvailableIDs().length);
}
@@ -64,7 +64,7 @@
// Given a valid override file, we should find ourselves using that.
public void testLoadTzDataWithFallback_goodOverrideFile() throws Exception {
- RandomAccessFile in = new RandomAccessFile(TZDATA_IN_ROOT, "r");
+ RandomAccessFile in = new RandomAccessFile(SYSTEM_TZDATA_FILE, "r");
byte[] content = new byte[(int) in.length()];
in.readFully(content);
in.close();
@@ -79,9 +79,9 @@
File goodFile = makeTemporaryFile(content);
try {
ZoneInfoDB.TzData dataWithOverride =
- ZoneInfoDB.TzData.loadTzDataWithFallback(goodFile.getPath(), TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData.loadTzDataWithFallback(goodFile.getPath(), SYSTEM_TZDATA_FILE);
assertEquals("9999z", dataWithOverride.getVersion());
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
assertEquals(data.getAvailableIDs().length, dataWithOverride.getAvailableIDs().length);
} finally {
goodFile.delete();
@@ -89,7 +89,7 @@
}
public void testLoadTzData_badHeader() throws Exception {
- RandomAccessFile in = new RandomAccessFile(TZDATA_IN_ROOT, "r");
+ RandomAccessFile in = new RandomAccessFile(SYSTEM_TZDATA_FILE, "r");
byte[] content = new byte[(int) in.length()];
in.readFully(content);
in.close();
@@ -212,7 +212,7 @@
// Confirms any caching that exists correctly handles TimeZone mutability.
public void testMakeTimeZone_timeZoneMutability() throws Exception {
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
String tzId = "Europe/London";
ZoneInfo first = data.makeTimeZone(tzId);
ZoneInfo second = data.makeTimeZone(tzId);
@@ -229,21 +229,21 @@
}
public void testMakeTimeZone_notFound() throws Exception {
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
assertNull(data.makeTimeZone("THIS_TZ_DOES_NOT_EXIST"));
assertFalse(data.hasTimeZone("THIS_TZ_DOES_NOT_EXIST"));
}
public void testMakeTimeZone_found() throws Exception {
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
assertNotNull(data.makeTimeZone("Europe/London"));
assertTrue(data.hasTimeZone("Europe/London"));
}
public void testGetRulesVersion() throws Exception {
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
- String rulesVersion = ZoneInfoDB.TzData.getRulesVersion(new File(TZDATA_IN_ROOT));
+ String rulesVersion = ZoneInfoDB.TzData.getRulesVersion(new File(SYSTEM_TZDATA_FILE));
assertEquals(data.getVersion(), rulesVersion);
}
diff --git a/non_openjdk_java_files.mk b/non_openjdk_java_files.mk
index fd1353f..31ee139 100644
--- a/non_openjdk_java_files.mk
+++ b/non_openjdk_java_files.mk
@@ -307,6 +307,7 @@
luni/src/main/java/libcore/util/Objects.java \
luni/src/main/java/libcore/util/RecoverySystem.java \
luni/src/main/java/libcore/util/SneakyThrow.java \
+ luni/src/main/java/libcore/util/TimeZoneDataFiles.java \
luni/src/main/java/libcore/util/ZoneInfo.java \
luni/src/main/java/libcore/util/ZoneInfoDB.java \
luni/src/main/java/libcore/util/HexEncoding.java \
diff --git a/ojluni/src/main/java/java/beans/ChangeListenerMap.java b/ojluni/src/main/java/java/beans/ChangeListenerMap.java
index fead0a4..fa8be47 100644
--- a/ojluni/src/main/java/java/beans/ChangeListenerMap.java
+++ b/ojluni/src/main/java/java/beans/ChangeListenerMap.java
@@ -76,7 +76,7 @@
*/
public final synchronized void add(String name, L listener) {
if (this.map == null) {
- this.map = new HashMap<String, L[]>();
+ this.map = new HashMap<>();
}
L[] array = this.map.get(name);
int size = (array != null)
@@ -146,7 +146,7 @@
public final void set(String name, L[] listeners) {
if (listeners != null) {
if (this.map == null) {
- this.map = new HashMap<String, L[]>();
+ this.map = new HashMap<>();
}
this.map.put(name, listeners);
}
@@ -167,7 +167,7 @@
if (this.map == null) {
return newArray(0);
}
- List<L> list = new ArrayList<L>();
+ List<L> list = new ArrayList<>();
L[] listeners = this.map.get(null);
if (listeners != null) {
diff --git a/ojluni/src/main/java/java/lang/System.java b/ojluni/src/main/java/java/lang/System.java
index 4797c1b..6e8f74f 100644
--- a/ojluni/src/main/java/java/lang/System.java
+++ b/ojluni/src/main/java/java/lang/System.java
@@ -41,6 +41,8 @@
import java.util.PropertyPermission;
import libcore.icu.ICU;
import libcore.io.Libcore;
+import libcore.util.TimeZoneDataFiles;
+
import sun.reflect.CallerSensitive;
import sun.security.util.SecurityConstants;
/**
@@ -996,7 +998,7 @@
// is prioritized over the properties in ICUConfig.properties. The issue with using
// that is that it doesn't play well with jarjar and it needs complicated build rules
// to change its default value.
- String icuDataPath = generateIcuDataPath();
+ String icuDataPath = TimeZoneDataFiles.generateIcuDataPath();
p.put("android.icu.impl.ICUBinary.dataPath", icuDataPath);
parsePropertyAssignments(p, specialProperties());
@@ -1023,37 +1025,6 @@
return p;
}
- private static String generateIcuDataPath() {
- StringBuilder icuDataPathBuilder = new StringBuilder();
- // ICU should first look in ANDROID_DATA. This is used for (optional) timezone data.
- String dataIcuDataPath = getEnvironmentPath("ANDROID_DATA", "/misc/zoneinfo/current/icu");
- if (dataIcuDataPath != null) {
- icuDataPathBuilder.append(dataIcuDataPath);
- }
-
- // ICU should always look in ANDROID_ROOT.
- String systemIcuDataPath = getEnvironmentPath("ANDROID_ROOT", "/usr/icu");
- if (systemIcuDataPath != null) {
- if (icuDataPathBuilder.length() > 0) {
- icuDataPathBuilder.append(":");
- }
- icuDataPathBuilder.append(systemIcuDataPath);
- }
- return icuDataPathBuilder.toString();
- }
-
- /**
- * Creates a path by combining the value of an environment variable with a relative path.
- * Returns {@code null} if the environment variable is not set.
- */
- private static String getEnvironmentPath(String environmentVariable, String path) {
- String variable = getenv(environmentVariable);
- if (variable == null) {
- return null;
- }
- return variable + path;
- }
-
private static Properties initProperties() {
Properties p = new PropertiesWithNonOverrideableDefaults(unchangeableProps);
setDefaultChangeableProperties(p);
diff --git a/ojluni/src/main/java/java/lang/reflect/Array.java b/ojluni/src/main/java/java/lang/reflect/Array.java
index 8285be7..790a930 100644
--- a/ojluni/src/main/java/java/lang/reflect/Array.java
+++ b/ojluni/src/main/java/java/lang/reflect/Array.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -57,6 +57,8 @@
* Array.newInstance(componentType, x);
* </pre>
* </blockquote>
+ * <p>The number of dimensions of the new array must not
+ * exceed 255.
*
* @param componentType the {@code Class} object representing the
* component type of the new array
@@ -64,7 +66,9 @@
* @return the new array
* @exception NullPointerException if the specified
* {@code componentType} parameter is null
- * @exception IllegalArgumentException if componentType is {@link Void#TYPE}
+ * @exception IllegalArgumentException if componentType is {@link
+ * Void#TYPE} or if the number of dimensions of the requested array
+ * instance exceed 255.
* @exception NegativeArraySizeException if the specified {@code length}
* is negative
*/
@@ -88,8 +92,7 @@
* {@code componentType}.
*
* <p>The number of dimensions of the new array must not
- * exceed the number of array dimensions supported by the
- * implementation (typically 255).
+ * exceed 255.
*
* @param componentType the {@code Class} object representing the component
* type of the new array
@@ -99,10 +102,9 @@
* @exception NullPointerException if the specified
* {@code componentType} argument is null
* @exception IllegalArgumentException if the specified {@code dimensions}
- * argument is a zero-dimensional array, or if the number of
- * requested dimensions exceeds the limit on the number of array dimensions
- * supported by the implementation (typically 255), or if componentType
- * is {@link Void#TYPE}.
+ * argument is a zero-dimensional array, if componentType is {@link
+ * Void#TYPE}, or if the number of dimensions of the requested array
+ * instance exceed 255.
* @exception NegativeArraySizeException if any of the components in
* the specified {@code dimensions} argument is negative.
*/
diff --git a/ojluni/src/main/java/java/lang/reflect/Constructor.java b/ojluni/src/main/java/java/lang/reflect/Constructor.java
index 3054e5f..de7176b 100644
--- a/ojluni/src/main/java/java/lang/reflect/Constructor.java
+++ b/ojluni/src/main/java/java/lang/reflect/Constructor.java
@@ -139,6 +139,7 @@
/**
* {@inheritDoc}
+ * @since 1.8
*/
public int getParameterCount() {
// Android-changed: This is handled by Executable.
diff --git a/ojluni/src/main/java/java/lang/reflect/Method.java b/ojluni/src/main/java/java/lang/reflect/Method.java
index 55d741c..ddda93d 100644
--- a/ojluni/src/main/java/java/lang/reflect/Method.java
+++ b/ojluni/src/main/java/java/lang/reflect/Method.java
@@ -186,6 +186,7 @@
/**
* {@inheritDoc}
+ * @since 1.8
*/
public int getParameterCount() {
// Android-changed: This is handled by Executable.
diff --git a/ojluni/src/main/java/java/lang/reflect/ReflectPermission.java b/ojluni/src/main/java/java/lang/reflect/ReflectPermission.java
index b8e8e63..96a2d21 100644
--- a/ojluni/src/main/java/java/lang/reflect/ReflectPermission.java
+++ b/ojluni/src/main/java/java/lang/reflect/ReflectPermission.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/java/security/AccessControlContext.java b/ojluni/src/main/java/java/security/AccessControlContext.java
index 86a88f0..e95cff2 100644
--- a/ojluni/src/main/java/java/security/AccessControlContext.java
+++ b/ojluni/src/main/java/java/security/AccessControlContext.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/java/security/AccessController.java b/ojluni/src/main/java/java/security/AccessController.java
index 1f7bdc5..b4d544c 100644
--- a/ojluni/src/main/java/java/security/AccessController.java
+++ b/ojluni/src/main/java/java/security/AccessController.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/java/security/AuthProvider.java b/ojluni/src/main/java/java/security/AuthProvider.java
index 0308291..23ddb0a 100644
--- a/ojluni/src/main/java/java/security/AuthProvider.java
+++ b/ojluni/src/main/java/java/security/AuthProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/java/security/CodeSource.java b/ojluni/src/main/java/java/security/CodeSource.java
index d678011..7396d91 100644
--- a/ojluni/src/main/java/java/security/CodeSource.java
+++ b/ojluni/src/main/java/java/security/CodeSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/java/security/DomainCombiner.java b/ojluni/src/main/java/java/security/DomainCombiner.java
index 7dc0849..5426bf6 100644
--- a/ojluni/src/main/java/java/security/DomainCombiner.java
+++ b/ojluni/src/main/java/java/security/DomainCombiner.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java b/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java
index fb34981..951e44e 100644
--- a/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java
+++ b/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2003, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -58,13 +58,13 @@
}
/**
- * Creates a <code>NoSuchAlgorithmException</code> with the specified
+ * Creates a {@code NoSuchAlgorithmException} with the specified
* detail message and cause.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
- * {@link #getCause()} method). (A <tt>null</tt> value is permitted,
+ * {@link #getCause()} method). (A {@code null} value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @since 1.5
*/
@@ -73,13 +73,13 @@
}
/**
- * Creates a <code>NoSuchAlgorithmException</code> with the specified cause
- * and a detail message of <tt>(cause==null ? null : cause.toString())</tt>
+ * Creates a {@code NoSuchAlgorithmException} with the specified cause
+ * and a detail message of {@code (cause==null ? null : cause.toString())}
* (which typically contains the class and detail message of
- * <tt>cause</tt>).
+ * {@code cause}).
*
* @param cause the cause (which is saved for later retrieval by the
- * {@link #getCause()} method). (A <tt>null</tt> value is permitted,
+ * {@link #getCause()} method). (A {@code null} value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @since 1.5
*/
diff --git a/ojluni/src/main/java/java/security/Permission.java b/ojluni/src/main/java/java/security/Permission.java
index 9f04ac4..8d170c0 100644
--- a/ojluni/src/main/java/java/security/Permission.java
+++ b/ojluni/src/main/java/java/security/Permission.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/java/security/Permissions.java b/ojluni/src/main/java/java/security/Permissions.java
index de5d451..7411e06 100644
--- a/ojluni/src/main/java/java/security/Permissions.java
+++ b/ojluni/src/main/java/java/security/Permissions.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/java/security/PrivilegedAction.java b/ojluni/src/main/java/java/security/PrivilegedAction.java
index ab39f98..bef7e44 100644
--- a/ojluni/src/main/java/java/security/PrivilegedAction.java
+++ b/ojluni/src/main/java/java/security/PrivilegedAction.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/java/security/PrivilegedExceptionAction.java b/ojluni/src/main/java/java/security/PrivilegedExceptionAction.java
index 87e4209..bae883e 100644
--- a/ojluni/src/main/java/java/security/PrivilegedExceptionAction.java
+++ b/ojluni/src/main/java/java/security/PrivilegedExceptionAction.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/java/security/SecurityPermission.java b/ojluni/src/main/java/java/security/SecurityPermission.java
index 432ee4b..7d2ec47 100644
--- a/ojluni/src/main/java/java/security/SecurityPermission.java
+++ b/ojluni/src/main/java/java/security/SecurityPermission.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/java/security/UnresolvedPermission.java b/ojluni/src/main/java/java/security/UnresolvedPermission.java
index dec952b..6d97fbe 100644
--- a/ojluni/src/main/java/java/security/UnresolvedPermission.java
+++ b/ojluni/src/main/java/java/security/UnresolvedPermission.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/javax/crypto/CipherOutputStream.java b/ojluni/src/main/java/javax/crypto/CipherOutputStream.java
index 6b8d273..370f7af 100644
--- a/ojluni/src/main/java/javax/crypto/CipherOutputStream.java
+++ b/ojluni/src/main/java/javax/crypto/CipherOutputStream.java
@@ -210,6 +210,8 @@
obuffer = cipher.doFinal();
} catch (IllegalBlockSizeException | BadPaddingException e) {
obuffer = null;
+ // Android-added: Throw an exception when the underlying cipher does. http://b/36636576
+ throw new IOException(e);
}
try {
flush();
diff --git a/tools/upstream/oj_upstream_comparison.py b/tools/upstream/oj_upstream_comparison.py
index 312483c..deab5fb 100755
--- a/tools/upstream/oj_upstream_comparison.py
+++ b/tools/upstream/oj_upstream_comparison.py
@@ -46,10 +46,12 @@
"""
import argparse
+import csv
import filecmp
import os
import re
import shutil
+import sys
def rel_paths_from_makefile(build_top):
"""Returns the list of relative paths to .java files parsed from openjdk_java_files.mk"""
@@ -83,28 +85,75 @@
return result
return None
-def compare_to_upstreams(build_top, upstream_root, upstreams, rel_paths):
+
+# For files with N and M lines, respectively, this runs in time
+# O(N+M) if the files are identical or O(N*M) if not. This could
+# be improved to O(D*(N+M)) for files with at most D lines
+# difference by only considering array elements within D cells
+# from the diagonal.
+def edit_distance_lines(file_a, file_b):
"""
- Returns a dict from rel_path to lists of length len(upstreams)
- Each list entry specifies whether the file at a particular
- rel_path is missing from, identical to, or different from
- a particular upstream.
+ Computes the line-based edit distance between two text files, i.e.
+ the smallest number of line deletions, additions or replacements
+ that would transform the content of one file into that of the other.
"""
- result = {}
+ if filecmp.cmp(file_a, file_b, shallow=False):
+ return 0 # files identical
+ with open(file_a) as f:
+ lines_a = f.readlines()
+ with open(file_b) as f:
+ lines_b = f.readlines()
+ prev_cost = range(0, len(lines_b) + 1)
+ for end_a in range(1, len(lines_a) + 1):
+ # For each valid index i, prev_cost[i] is the edit distance between
+ # lines_a[:end_a-1] and lines_b[:i].
+ # We now calculate cur_cost[end_b] as the edit distance between
+ # line_a[:end_a] and lines_b[:end_b]
+ cur_cost = [end_a]
+ for end_b in range(1, len(lines_b) + 1):
+ c = min(
+ cur_cost[-1] + 1, # append line from b
+ prev_cost[end_b] + 1, # append line from a
+ # match or replace line
+ prev_cost[end_b - 1] + (0 if lines_a[end_a - 1] == lines_b[end_b - 1] else 1)
+ )
+ cur_cost.append(c)
+ prev_cost = cur_cost
+ return prev_cost[-1]
+
+def compare_to_upstreams_and_save(out_file, build_top, upstream_root, upstreams, rel_paths, best_only=False):
+ """
+ Prints tab-separated values comparing ojluni files vs. each
+ upstream, for each of the rel_paths, suitable for human
+ analysis in a spreadsheet.
+ This includes whether the corresponding upstream file is
+ missing, identical, or by how many lines it differs, and
+ a guess as to the correct upstream based on minimal line
+ difference (ties broken in favor of upstreams that occur
+ earlier in the list).
+ """
+ writer = csv.writer(out_file, delimiter='\t')
+ writer.writerow(["rel_path", "guessed_upstream"] + upstreams)
for rel_path in rel_paths:
ojluni_file = ojluni_path(build_top, rel_path)
- status = []
+ upstream_comparisons = []
+ best_distance = sys.maxint
+ guessed_upstream = ""
for upstream in upstreams:
upstream_file = upstream_path(upstream_root, upstream, rel_path)
if upstream_file is None:
- upstream_status = "missing"
- elif filecmp.cmp(upstream_file, ojluni_file, shallow=False):
- upstream_status = "identical"
+ upstream_comparison = "missing"
else:
- upstream_status = "different"
- status.append(upstream_status)
- result[rel_path] = status
- return result
+ edit_distance = edit_distance_lines(upstream_file, ojluni_file)
+ if edit_distance == 0:
+ upstream_comparison = "identical"
+ else:
+ upstream_comparison = "different (%d lines)" % (edit_distance)
+ if edit_distance < best_distance:
+ best_distance = edit_distance
+ guessed_upstream = upstream
+ upstream_comparisons.append(upstream_comparison)
+ writer.writerow([rel_path, guessed_upstream ] + upstream_comparisons)
def copy_files(rel_paths, upstream_root, upstream, output_dir):
"""Copies files at the given rel_paths from upstream to output_dir"""
@@ -148,13 +197,12 @@
raise Exception("Upstream not found: " + upstream_path)
rel_paths = rel_paths_from_makefile(args.build_top)
- upstream_infos = compare_to_upstreams(args.build_top, args.upstream_root, upstreams, rel_paths)
+
+ compare_to_upstreams_and_save(
+ sys.stdout, args.build_top, args.upstream_root, upstreams, rel_paths)
if args.output_dir is not None:
copy_files(rel_paths, args.upstream_root, default_upstream, args.output_dir)
- for rel_path in rel_paths:
- print(rel_path + "\t" + "\t".join(upstream_infos[rel_path]))
-
if __name__ == '__main__':
main()
diff --git a/tzdata/shared2/src/main/libcore/tzdata/shared2/DistroVersion.java b/tzdata/shared2/src/main/libcore/tzdata/shared2/DistroVersion.java
index 3902d36..80f37bc 100644
--- a/tzdata/shared2/src/main/libcore/tzdata/shared2/DistroVersion.java
+++ b/tzdata/shared2/src/main/libcore/tzdata/shared2/DistroVersion.java
@@ -152,6 +152,15 @@
}
@Override
+ public int hashCode() {
+ int result = formatMajorVersion;
+ result = 31 * result + formatMinorVersion;
+ result = 31 * result + rulesVersion.hashCode();
+ result = 31 * result + revision;
+ return result;
+ }
+
+ @Override
public String toString() {
return "DistroVersion{" +
"formatMajorVersion=" + formatMajorVersion +
diff --git a/tzdata/shared2/src/main/libcore/tzdata/shared2/FileUtils.java b/tzdata/shared2/src/main/libcore/tzdata/shared2/FileUtils.java
index a170a9e..39626ac 100644
--- a/tzdata/shared2/src/main/libcore/tzdata/shared2/FileUtils.java
+++ b/tzdata/shared2/src/main/libcore/tzdata/shared2/FileUtils.java
@@ -17,6 +17,8 @@
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
@@ -173,4 +175,14 @@
return toReturn;
}
}
+
+ /**
+ * Creates an empty file.
+ *
+ * @param file the file to create
+ * @throws IOException if the file cannot be created
+ */
+ public static void createEmptyFile(File file) throws IOException {
+ new FileOutputStream(file, false /* append */).close();
+ }
}
diff --git a/tzdata/shared2/src/main/libcore/tzdata/shared2/StagedDistroOperation.java b/tzdata/shared2/src/main/libcore/tzdata/shared2/StagedDistroOperation.java
new file mode 100644
index 0000000..0492e12
--- /dev/null
+++ b/tzdata/shared2/src/main/libcore/tzdata/shared2/StagedDistroOperation.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.tzdata.shared2;
+
+/**
+ * Information about a staged time zone distro operation.
+ */
+public class StagedDistroOperation {
+
+ private static final StagedDistroOperation UNINSTALL_STAGED =
+ new StagedDistroOperation(true /* isUninstall */, null /* stagedVersion */);
+
+ public final boolean isUninstall;
+ public final DistroVersion distroVersion;
+
+ private StagedDistroOperation(boolean isUninstall, DistroVersion distroVersion) {
+ this.isUninstall = isUninstall;
+ this.distroVersion = distroVersion;
+ }
+
+ public static StagedDistroOperation install(DistroVersion distroVersion) {
+ if (distroVersion == null) {
+ throw new NullPointerException("distroVersion==null");
+ }
+ return new StagedDistroOperation(false /* isUninstall */, distroVersion);
+ }
+
+ public static StagedDistroOperation uninstall() {
+ return UNINSTALL_STAGED;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ StagedDistroOperation that = (StagedDistroOperation) o;
+
+ if (isUninstall != that.isUninstall) {
+ return false;
+ }
+ return distroVersion != null ? distroVersion.equals(that.distroVersion)
+ : that.distroVersion == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (isUninstall ? 1 : 0);
+ result = 31 * result + (distroVersion != null ? distroVersion.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "StagedDistroOperation{" +
+ "isUninstall=" + isUninstall +
+ ", distroVersion=" + distroVersion +
+ '}';
+ }
+}
diff --git a/tzdata/shared2/src/test/libcore/tzdata/shared2/FileUtilsTest.java b/tzdata/shared2/src/test/libcore/tzdata/shared2/FileUtilsTest.java
index 3b9ec5b..87e6e6e 100644
--- a/tzdata/shared2/src/test/libcore/tzdata/shared2/FileUtilsTest.java
+++ b/tzdata/shared2/src/test/libcore/tzdata/shared2/FileUtilsTest.java
@@ -253,6 +253,43 @@
assertTrue(Arrays.equals(contents, exhaustedFileRead));
}
+ public void testCreateEmptyFile() throws Exception {
+ File dir = createTempDir();
+ File file = new File(dir, "one");
+ assertFalse(file.exists());
+ FileUtils.createEmptyFile(file);
+ assertTrue(file.exists());
+ assertEquals(0, file.length());
+ }
+
+ public void testCreateEmptyFile_isDir() throws Exception {
+ File dir = createTempDir();
+ assertTrue(dir.exists());
+ assertTrue(dir.isDirectory());
+
+ try {
+ FileUtils.createEmptyFile(dir);
+ } catch (FileNotFoundException expected) {
+ }
+ assertTrue(dir.exists());
+ assertTrue(dir.isDirectory());
+ }
+
+ public void testCreateEmptyFile_truncatesExisting() throws Exception {
+ File dir = createTempDir();
+ File file = new File(dir, "one");
+
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ fos.write(new byte[1000]);
+ }
+ assertTrue(file.exists());
+ assertEquals(1000, file.length());
+
+ FileUtils.createEmptyFile(file);
+ assertTrue(file.exists());
+ assertEquals(0, file.length());
+ }
+
private File createFile(byte[] contents) throws IOException {
File file = File.createTempFile(getClass().getSimpleName(), ".txt");
try (FileOutputStream fos = new FileOutputStream(file)) {
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneDistroInstaller.java b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneDistroInstaller.java
index bb49fe0..e1ed794 100644
--- a/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneDistroInstaller.java
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneDistroInstaller.java
@@ -23,6 +23,7 @@
import libcore.tzdata.shared2.DistroException;
import libcore.tzdata.shared2.DistroVersion;
import libcore.tzdata.shared2.FileUtils;
+import libcore.tzdata.shared2.StagedDistroOperation;
import libcore.tzdata.shared2.TimeZoneDistro;
import libcore.util.ZoneInfoDB;
@@ -31,38 +32,55 @@
* testing. This class is not thread-safe: callers are expected to handle mutual exclusion.
*/
public class TimeZoneDistroInstaller {
- /** {@link #installWithErrorCode(byte[])} result code: Success. */
+ /** {@link #stageInstallWithErrorCode(byte[])} result code: Success. */
public final static int INSTALL_SUCCESS = 0;
- /** {@link #installWithErrorCode(byte[])} result code: Distro corrupt. */
+ /** {@link #stageInstallWithErrorCode(byte[])} result code: Distro corrupt. */
public final static int INSTALL_FAIL_BAD_DISTRO_STRUCTURE = 1;
- /** {@link #installWithErrorCode(byte[])} result code: Distro version incompatible. */
+ /** {@link #stageInstallWithErrorCode(byte[])} result code: Distro version incompatible. */
public final static int INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION = 2;
- /** {@link #installWithErrorCode(byte[])} result code: Distro rules too old for device. */
+ /** {@link #stageInstallWithErrorCode(byte[])} result code: Distro rules too old for device. */
public final static int INSTALL_FAIL_RULES_TOO_OLD = 3;
- /** {@link #installWithErrorCode(byte[])} result code: Distro content failed validation. */
+ /** {@link #stageInstallWithErrorCode(byte[])} result code: Distro content failed validation. */
public final static int INSTALL_FAIL_VALIDATION_ERROR = 4;
+ // This constant must match one in system/core/tzdatacheck.cpp.
+ private static final String STAGED_TZ_DATA_DIR_NAME = "staged";
+ // This constant must match one in system/core/tzdatacheck.cpp.
private static final String CURRENT_TZ_DATA_DIR_NAME = "current";
private static final String WORKING_DIR_NAME = "working";
private static final String OLD_TZ_DATA_DIR_NAME = "old";
+ /**
+ * The name of the file in the staged directory used to indicate a staged uninstallation.
+ */
+ // This constant must match one in system/core/tzdatacheck.cpp.
+ // VisibleForTesting.
+ public static final String UNINSTALL_TOMBSTONE_FILE_NAME = "STAGED_UNINSTALL_TOMBSTONE";
+
private final String logTag;
private final File systemTzDataFile;
- private final File oldTzDataDir;
+ private final File oldStagedDataDir;
+ private final File stagedTzDataDir;
private final File currentTzDataDir;
private final File workingDir;
public TimeZoneDistroInstaller(String logTag, File systemTzDataFile, File installDir) {
this.logTag = logTag;
this.systemTzDataFile = systemTzDataFile;
- oldTzDataDir = new File(installDir, OLD_TZ_DATA_DIR_NAME);
+ oldStagedDataDir = new File(installDir, OLD_TZ_DATA_DIR_NAME);
+ stagedTzDataDir = new File(installDir, STAGED_TZ_DATA_DIR_NAME);
currentTzDataDir = new File(installDir, CURRENT_TZ_DATA_DIR_NAME);
workingDir = new File(installDir, WORKING_DIR_NAME);
}
// VisibleForTesting
- File getOldTzDataDir() {
- return oldTzDataDir;
+ File getOldStagedDataDir() {
+ return oldStagedDataDir;
+ }
+
+ // VisibleForTesting
+ File getStagedTzDataDir() {
+ return stagedTzDataDir;
}
// VisibleForTesting
@@ -76,34 +94,35 @@
}
/**
- * Install the supplied content.
+ * Stage an install of the supplied content, to be installed the next time the device boots.
*
- * <p>Errors during unpacking or installation will throw an {@link IOException}.
+ * <p>Errors during unpacking or staging will throw an {@link IOException}.
* If the distro content is invalid this method returns {@code false}.
* If the installation completed successfully this method returns {@code true}.
*/
public boolean install(byte[] content) throws IOException {
- int result = installWithErrorCode(content);
+ int result = stageInstallWithErrorCode(content);
return result == INSTALL_SUCCESS;
}
/**
- * Install the supplied time zone distro.
+ * Stage an install of the supplied content, to be installed the next time the device boots.
*
- * <p>Errors during unpacking or installation will throw an {@link IOException}.
+ * <p>Errors during unpacking or staging will throw an {@link IOException}.
* Returns {@link #INSTALL_SUCCESS} or an error code.
*/
- public int installWithErrorCode(byte[] content) throws IOException {
- if (oldTzDataDir.exists()) {
- FileUtils.deleteRecursive(oldTzDataDir);
+ public int stageInstallWithErrorCode(byte[] content) throws IOException {
+ if (oldStagedDataDir.exists()) {
+ FileUtils.deleteRecursive(oldStagedDataDir);
}
if (workingDir.exists()) {
FileUtils.deleteRecursive(workingDir);
}
Slog.i(logTag, "Unpacking / verifying time zone update");
- unpackDistro(content, workingDir);
try {
+ unpackDistro(content, workingDir);
+
DistroVersion distroVersion;
try {
distroVersion = readDistroVersion(workingDir);
@@ -151,52 +170,78 @@
Slog.i(logTag, "Applying time zone update");
FileUtils.makeDirectoryWorldAccessible(workingDir);
- if (currentTzDataDir.exists()) {
- Slog.i(logTag, "Moving " + currentTzDataDir + " to " + oldTzDataDir);
- FileUtils.rename(currentTzDataDir, oldTzDataDir);
+ // Check if there is already a staged install or uninstall and remove it if there is.
+ if (!stagedTzDataDir.exists()) {
+ Slog.i(logTag, "Nothing to unstage at " + stagedTzDataDir);
+ } else {
+ Slog.i(logTag, "Moving " + stagedTzDataDir + " to " + oldStagedDataDir);
+ // Move stagedTzDataDir out of the way in one operation so we can't partially delete
+ // the contents.
+ FileUtils.rename(stagedTzDataDir, oldStagedDataDir);
}
- Slog.i(logTag, "Moving " + workingDir + " to " + currentTzDataDir);
- FileUtils.rename(workingDir, currentTzDataDir);
- Slog.i(logTag, "Update applied: " + currentTzDataDir + " successfully created");
+
+ // Move the workingDir to be the new staged directory.
+ Slog.i(logTag, "Moving " + workingDir + " to " + stagedTzDataDir);
+ FileUtils.rename(workingDir, stagedTzDataDir);
+ Slog.i(logTag, "Install staged: " + stagedTzDataDir + " successfully created");
return INSTALL_SUCCESS;
} finally {
- deleteBestEffort(oldTzDataDir);
+ deleteBestEffort(oldStagedDataDir);
deleteBestEffort(workingDir);
}
}
/**
- * Uninstall the current timezone update in /data, returning the device to using data from
- * /system. Returns {@code true} if uninstallation was successful, {@code false} if there was
- * nothing installed in /data to uninstall.
+ * Stage an uninstall of the current timezone update in /data which, on reboot, will return the
+ * device to using data from /system. Returns {@code true} if staging the uninstallation was
+ * successful, {@code false} if there was nothing installed in /data to uninstall. If there was
+ * something else staged it will be replaced by this call.
*
* <p>Errors encountered during uninstallation will throw an {@link IOException}.
*/
- public boolean uninstall() throws IOException {
+ public boolean stageUninstall() throws IOException {
Slog.i(logTag, "Uninstalling time zone update");
- // Make sure we don't have a dir where we're going to move the currently installed data to.
- if (oldTzDataDir.exists()) {
+ if (oldStagedDataDir.exists()) {
// If we can't remove this, an exception is thrown and we don't continue.
- FileUtils.deleteRecursive(oldTzDataDir);
+ FileUtils.deleteRecursive(oldStagedDataDir);
+ }
+ if (workingDir.exists()) {
+ FileUtils.deleteRecursive(workingDir);
}
- if (!currentTzDataDir.exists()) {
- Slog.i(logTag, "Nothing to uninstall at " + currentTzDataDir);
- return false;
+ try {
+ // Check if there is already an install or uninstall staged and remove it.
+ if (!stagedTzDataDir.exists()) {
+ Slog.i(logTag, "Nothing to unstage at " + stagedTzDataDir);
+ } else {
+ Slog.i(logTag, "Moving " + stagedTzDataDir + " to " + oldStagedDataDir);
+ // Move stagedTzDataDir out of the way in one operation so we can't partially delete
+ // the contents.
+ FileUtils.rename(stagedTzDataDir, oldStagedDataDir);
+ }
+
+ // If there's nothing actually installed, there's nothing to uninstall so no need to
+ // stage anything.
+ if (!currentTzDataDir.exists()) {
+ Slog.i(logTag, "Nothing to uninstall at " + currentTzDataDir);
+ return false;
+ }
+
+ // Stage an uninstall in workingDir.
+ FileUtils.ensureDirectoriesExist(workingDir, true /* makeWorldReadable */);
+ FileUtils.createEmptyFile(new File(workingDir, UNINSTALL_TOMBSTONE_FILE_NAME));
+
+ // Move the workingDir to be the new staged directory.
+ Slog.i(logTag, "Moving " + workingDir + " to " + stagedTzDataDir);
+ FileUtils.rename(workingDir, stagedTzDataDir);
+ Slog.i(logTag, "Uninstall staged: " + stagedTzDataDir + " successfully created");
+
+ return true;
+ } finally {
+ deleteBestEffort(oldStagedDataDir);
+ deleteBestEffort(workingDir);
}
-
- Slog.i(logTag, "Moving " + currentTzDataDir + " to " + oldTzDataDir);
- // Move currentTzDataDir out of the way in one operation so we can't partially delete
- // the contents, which would leave a partial install.
- FileUtils.rename(currentTzDataDir, oldTzDataDir);
-
- // Do our best to delete the now uninstalled timezone data.
- deleteBestEffort(oldTzDataDir);
-
- Slog.i(logTag, "Time zone update uninstalled.");
-
- return true;
}
/**
@@ -214,6 +259,24 @@
}
/**
+ * Reads information about any currently staged distro operation. Returns {@code null} if there
+ * is no distro operation staged.
+ *
+ * @throws IOException if there was a problem reading data from /data
+ * @throws DistroException if there was a problem with the staged distro format/structure
+ */
+ public StagedDistroOperation getStagedDistroOperation() throws DistroException, IOException {
+ if (!stagedTzDataDir.exists()) {
+ return null;
+ }
+ if (new File(stagedTzDataDir, UNINSTALL_TOMBSTONE_FILE_NAME).exists()) {
+ return StagedDistroOperation.uninstall();
+ } else {
+ return StagedDistroOperation.install(readDistroVersion(stagedTzDataDir));
+ }
+ }
+
+ /**
* Reads the timezone rules version present in /system. i.e. the version that would be present
* after a factory reset.
*
diff --git a/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneDistroInstallerTest.java b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneDistroInstallerTest.java
index 325b605..cf40bf6 100644
--- a/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneDistroInstallerTest.java
+++ b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneDistroInstallerTest.java
@@ -32,10 +32,13 @@
import libcore.io.Streams;
import libcore.tzdata.shared2.DistroVersion;
import libcore.tzdata.shared2.FileUtils;
+import libcore.tzdata.shared2.StagedDistroOperation;
import libcore.tzdata.shared2.TimeZoneDistro;
import libcore.tzdata.testing.ZoneInfoTestHelper;
import libcore.tzdata.update2.tools.TimeZoneDistroBuilder;
+import static org.junit.Assert.assertArrayEquals;
+
/**
* Tests for {@link TimeZoneDistroInstaller}.
*/
@@ -90,100 +93,110 @@
}
/** Tests the an update on a device will fail if the /system tzdata file cannot be found. */
- public void testInstall_badSystemFile() throws Exception {
+ public void testStageInstallWithErrorCode_badSystemFile() throws Exception {
File doesNotExist = new File(testSystemTzDataDir, "doesNotExist");
TimeZoneDistroInstaller brokenSystemInstaller = new TimeZoneDistroInstaller(
"TimeZoneDistroInstallerTest", doesNotExist, testInstallDir);
TimeZoneDistro tzData = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
try {
- brokenSystemInstaller.installWithErrorCode(tzData.getBytes());
+ brokenSystemInstaller.stageInstallWithErrorCode(tzData.getBytes());
fail();
} catch (IOException expected) {}
- assertNoContentInstalled();
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
/** Tests the first successful update on a device */
- public void testInstall_successfulFirstUpdate() throws Exception {
+ public void testStageInstallWithErrorCode_successfulFirstUpdate() throws Exception {
TimeZoneDistro distro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro.getBytes()));
- assertDistroInstalled(distro);
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertInstallDistroStaged(distro);
+ assertNoInstalledDistro();
}
/**
* Tests we can install an update the same version as is in /system.
*/
- public void testInstall_successfulFirstUpdate_sameVersionAsSystem() throws Exception {
+ public void testStageInstallWithErrorCode_successfulFirstUpdate_sameVersionAsSystem()
+ throws Exception {
TimeZoneDistro distro = createValidTimeZoneDistro(SYSTEM_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro.getBytes()));
- assertDistroInstalled(distro);
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertInstallDistroStaged(distro);
+ assertNoInstalledDistro();
}
/**
* Tests we cannot install an update older than the version in /system.
*/
- public void testInstall_unsuccessfulFirstUpdate_olderVersionThanSystem() throws Exception {
+ public void testStageInstallWithErrorCode_unsuccessfulFirstUpdate_olderVersionThanSystem()
+ throws Exception {
TimeZoneDistro distro = createValidTimeZoneDistro(OLDER_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD,
- installer.installWithErrorCode(distro.getBytes()));
- assertNoContentInstalled();
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
/**
- * Tests an update on a device when there is a prior update already applied.
+ * Tests an update on a device when there is a prior update already staged.
*/
- public void testInstall_successfulFollowOnUpdate_newerVersion() throws Exception {
+ public void testStageInstallWithErrorCode_successfulFollowOnUpdate_newerVersion()
+ throws Exception {
TimeZoneDistro distro1 = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro1.getBytes()));
- assertDistroInstalled(distro1);
+ installer.stageInstallWithErrorCode(distro1.getBytes()));
+ assertInstallDistroStaged(distro1);
TimeZoneDistro distro2 = createValidTimeZoneDistro(NEW_RULES_VERSION, 2);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro2.getBytes()));
- assertDistroInstalled(distro2);
+ installer.stageInstallWithErrorCode(distro2.getBytes()));
+ assertInstallDistroStaged(distro2);
TimeZoneDistro distro3 = createValidTimeZoneDistro(NEWER_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro3.getBytes()));
- assertDistroInstalled(distro3);
+ installer.stageInstallWithErrorCode(distro3.getBytes()));
+ assertInstallDistroStaged(distro3);
+ assertNoInstalledDistro();
}
/**
* Tests an update on a device when there is a prior update already applied, but the follow
* on update is older than in /system.
*/
- public void testInstall_unsuccessfulFollowOnUpdate_olderVersion() throws Exception {
+ public void testStageInstallWithErrorCode_unsuccessfulFollowOnUpdate_olderVersion()
+ throws Exception {
TimeZoneDistro distro1 = createValidTimeZoneDistro(NEW_RULES_VERSION, 2);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro1.getBytes()));
- assertDistroInstalled(distro1);
+ installer.stageInstallWithErrorCode(distro1.getBytes()));
+ assertInstallDistroStaged(distro1);
TimeZoneDistro distro2 = createValidTimeZoneDistro(OLDER_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD,
- installer.installWithErrorCode(distro2.getBytes()));
- assertDistroInstalled(distro1);
+ installer.stageInstallWithErrorCode(distro2.getBytes()));
+ assertInstallDistroStaged(distro1);
+ assertNoInstalledDistro();
}
/** Tests that a distro with a missing file will not update the content. */
- public void testInstall_missingTzDataFile() throws Exception {
- TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ public void testStageInstallWithErrorCode_missingTzDataFile() throws Exception {
+ TimeZoneDistro stagedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(installedDistro.getBytes()));
- assertDistroInstalled(installedDistro);
+ installer.stageInstallWithErrorCode(stagedDistro.getBytes()));
+ assertInstallDistroStaged(stagedDistro);
TimeZoneDistro incompleteDistro =
createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
@@ -191,17 +204,18 @@
.buildUnvalidated();
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
- installer.installWithErrorCode(incompleteDistro.getBytes()));
- assertDistroInstalled(installedDistro);
+ installer.stageInstallWithErrorCode(incompleteDistro.getBytes()));
+ assertInstallDistroStaged(stagedDistro);
+ assertNoInstalledDistro();
}
/** Tests that a distro with a missing file will not update the content. */
- public void testInstall_missingIcuFile() throws Exception {
- TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ public void testStageInstallWithErrorCode_missingIcuFile() throws Exception {
+ TimeZoneDistro stagedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(installedDistro.getBytes()));
- assertDistroInstalled(installedDistro);
+ installer.stageInstallWithErrorCode(stagedDistro.getBytes()));
+ assertInstallDistroStaged(stagedDistro);
TimeZoneDistro incompleteDistro =
createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
@@ -209,14 +223,15 @@
.buildUnvalidated();
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
- installer.installWithErrorCode(incompleteDistro.getBytes()));
- assertDistroInstalled(installedDistro);
+ installer.stageInstallWithErrorCode(incompleteDistro.getBytes()));
+ assertInstallDistroStaged(stagedDistro);
+ assertNoInstalledDistro();
}
/**
* Tests that an update will be unpacked even if there is a partial update from a previous run.
*/
- public void testInstall_withWorkingDir() throws Exception {
+ public void testStageInstallWithErrorCode_withWorkingDir() throws Exception {
File workingDir = installer.getWorkingDir();
assertTrue(workingDir.mkdir());
createFile(new File(workingDir, "myFile"), new byte[] { 'a' });
@@ -224,42 +239,45 @@
TimeZoneDistro distro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro.getBytes()));
- assertDistroInstalled(distro);
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertInstallDistroStaged(distro);
+ assertNoInstalledDistro();
}
/**
* Tests that a distro without a distro version file will be rejected.
*/
- public void testInstall_withMissingDistroVersionFile() throws Exception {
+ public void testStageInstallWithErrorCode_withMissingDistroVersionFile() throws Exception {
// Create a distro without a version file.
TimeZoneDistro distro = createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1)
.clearVersionForTests()
.buildUnvalidated();
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
- installer.installWithErrorCode(distro.getBytes()));
- assertNoContentInstalled();
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
/**
* Tests that a distro with an newer distro version will be rejected.
*/
- public void testInstall_withNewerDistroVersion() throws Exception {
+ public void testStageInstallWithErrorCode_withNewerDistroVersion() throws Exception {
// Create a distro that will appear to be newer than the one currently supported.
TimeZoneDistro distro = createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1)
.replaceFormatVersionForTests(2, 1)
.buildUnvalidated();
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION,
- installer.installWithErrorCode(distro.getBytes()));
- assertNoContentInstalled();
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
/**
* Tests that a distro with a badly formed distro version will be rejected.
*/
- public void testInstall_withBadlyFormedDistroVersion() throws Exception {
+ public void testStageInstallWithErrorCode_withBadlyFormedDistroVersion() throws Exception {
// Create a distro that has an invalid major distro version. It should be 3 numeric
// characters, "." and 3 more numeric characters.
DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1);
@@ -269,14 +287,15 @@
TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidFormatVersionBytes);
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
- installer.installWithErrorCode(distro.getBytes()));
- assertNoContentInstalled();
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
/**
* Tests that a distro with a badly formed revision will be rejected.
*/
- public void testInstall_withBadlyFormedRevision() throws Exception {
+ public void testStageInstallWithErrorCode_withBadlyFormedRevision() throws Exception {
// Create a distro that has an invalid revision. It should be 3 numeric characters.
DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1);
byte[] invalidRevisionBytes = validDistroVersion.toBytes();
@@ -285,14 +304,15 @@
TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRevisionBytes);
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
- installer.installWithErrorCode(distro.getBytes()));
- assertNoContentInstalled();
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
/**
* Tests that a distro with a badly formed rules version will be rejected.
*/
- public void testInstall_withBadlyFormedRulesVersion() throws Exception {
+ public void testStageInstallWithErrorCode_withBadlyFormedRulesVersion() throws Exception {
// Create a distro that has an invalid rules version. It should be in the form "2016c".
DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1);
byte[] invalidRulesVersionBytes = validDistroVersion.toBytes();
@@ -301,38 +321,124 @@
TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRulesVersionBytes);
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
- installer.installWithErrorCode(distro.getBytes()));
- assertNoContentInstalled();
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
- public void testUninstall_noExistingDataDistro() throws Exception {
- assertFalse(installer.uninstall());
- assertNoContentInstalled();
+ public void testStageUninstall_noExistingDistro() throws Exception {
+ // To stage an uninstall, there would need to be installed rules.
+ assertFalse(installer.stageUninstall());
+
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
- public void testUninstall_existingDataDistro() throws Exception {
- File currentDataDir = installer.getCurrentTzDataDir();
- assertTrue(currentDataDir.mkdir());
+ public void testStageUninstall_existingStagedDataDistro() throws Exception {
+ // To stage an uninstall, we need to have some installed rules.
+ TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ simulateInstalledDistro(installedDistro);
- assertTrue(installer.uninstall());
- assertNoContentInstalled();
+ File stagedDataDir = installer.getStagedTzDataDir();
+ assertTrue(stagedDataDir.mkdir());
+
+ assertTrue(installer.stageUninstall());
+ assertDistroUninstallStaged();
+ assertInstalledDistro(installedDistro);
}
- public void testUninstall_oldDirsAlreadyExists() throws Exception {
- File oldTzDataDir = installer.getOldTzDataDir();
- assertTrue(oldTzDataDir.mkdir());
+ public void testStageUninstall_oldDirsAlreadyExists() throws Exception {
+ // To stage an uninstall, we need to have some installed rules.
+ TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ simulateInstalledDistro(installedDistro);
- File currentDataDir = installer.getCurrentTzDataDir();
- assertTrue(currentDataDir.mkdir());
+ File oldStagedDataDir = installer.getOldStagedDataDir();
+ assertTrue(oldStagedDataDir.mkdir());
- assertTrue(installer.uninstall());
- assertNoContentInstalled();
+ File workingDir = installer.getWorkingDir();
+ assertTrue(workingDir.mkdir());
+
+ assertTrue(installer.stageUninstall());
+
+ assertDistroUninstallStaged();
+ assertFalse(workingDir.exists());
+ assertFalse(oldStagedDataDir.exists());
+ assertInstalledDistro(installedDistro);
}
public void testGetSystemRulesVersion() throws Exception {
assertEquals(SYSTEM_RULES_VERSION, installer.getSystemRulesVersion());
}
+ public void testGetInstalledDistroVersion() throws Exception {
+ // Check result when nothing installed.
+ assertNull(installer.getInstalledDistroVersion());
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
+
+ // Now simulate there being an existing install active.
+ TimeZoneDistro distro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ simulateInstalledDistro(distro);
+ assertInstalledDistro(distro);
+
+ // Check result when something installed.
+ assertEquals(distro.getDistroVersion(), installer.getInstalledDistroVersion());
+ assertNoDistroOperationStaged();
+ assertInstalledDistro(distro);
+ }
+
+ public void testGetStagedDistroOperation() throws Exception {
+ TimeZoneDistro distro1 = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ TimeZoneDistro distro2 = createValidTimeZoneDistro(NEWER_RULES_VERSION, 1);
+
+ // Check result when nothing staged.
+ assertNull(installer.getStagedDistroOperation());
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
+
+ // Check result after unsuccessfully staging an uninstall.
+ // Can't stage an uninstall without an installed distro.
+ assertFalse(installer.stageUninstall());
+ assertNull(installer.getStagedDistroOperation());
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
+
+ // Check result after staging an install.
+ assertTrue(installer.install(distro1.getBytes()));
+ StagedDistroOperation expectedStagedInstall =
+ StagedDistroOperation.install(distro1.getDistroVersion());
+ assertEquals(expectedStagedInstall, installer.getStagedDistroOperation());
+ assertInstallDistroStaged(distro1);
+ assertNoInstalledDistro();
+
+ // Check result after unsuccessfully staging an uninstall (but after removing a staged
+ // install). Can't stage an uninstall without an installed distro.
+ assertFalse(installer.stageUninstall());
+ assertNull(installer.getStagedDistroOperation());
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
+
+ // Now simulate there being an existing install active.
+ simulateInstalledDistro(distro1);
+ assertInstalledDistro(distro1);
+
+ // Check state after successfully staging an uninstall.
+ assertTrue(installer.stageUninstall());
+ StagedDistroOperation expectedStagedUninstall = StagedDistroOperation.uninstall();
+ assertEquals(expectedStagedUninstall, installer.getStagedDistroOperation());
+ assertDistroUninstallStaged();
+ assertInstalledDistro(distro1);
+
+ // Check state after successfully staging an install.
+ assertEquals(TimeZoneDistroInstaller.INSTALL_SUCCESS,
+ installer.stageInstallWithErrorCode(distro2.getBytes()));
+ StagedDistroOperation expectedStagedInstall2 =
+ StagedDistroOperation.install(distro2.getDistroVersion());
+ assertEquals(expectedStagedInstall2, installer.getStagedDistroOperation());
+ assertInstallDistroStaged(distro2);
+ assertInstalledDistro(distro1);
+ }
+
private static TimeZoneDistro createValidTimeZoneDistro(
String rulesVersion, int revision) throws Exception {
return createValidTimeZoneDistroBuilder(rulesVersion, revision).build();
@@ -354,24 +460,27 @@
.setIcuData(icuData);
}
- private void assertDistroInstalled(TimeZoneDistro expectedDistro) throws Exception {
+ private void assertInstallDistroStaged(TimeZoneDistro expectedDistro) throws Exception {
assertTrue(testInstallDir.exists());
- File currentTzDataDir = installer.getCurrentTzDataDir();
- assertTrue(currentTzDataDir.exists());
+ File stagedTzDataDir = installer.getStagedTzDataDir();
+ assertTrue(stagedTzDataDir.exists());
File distroVersionFile =
- new File(currentTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
+ new File(stagedTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
assertTrue(distroVersionFile.exists());
- File bionicFile = new File(currentTzDataDir, TimeZoneDistro.TZDATA_FILE_NAME);
+ File bionicFile = new File(stagedTzDataDir, TimeZoneDistro.TZDATA_FILE_NAME);
assertTrue(bionicFile.exists());
- File icuFile = new File(currentTzDataDir, TimeZoneDistro.ICU_DATA_FILE_NAME);
+ File icuFile = new File(stagedTzDataDir, TimeZoneDistro.ICU_DATA_FILE_NAME);
assertTrue(icuFile.exists());
- // Assert getInstalledDistroVersion() is reporting correctly.
- assertEquals(expectedDistro.getDistroVersion(), installer.getInstalledDistroVersion());
+ // Assert getStagedDistroState() is reporting correctly.
+ StagedDistroOperation stagedDistroOperation = installer.getStagedDistroOperation();
+ assertNotNull(stagedDistroOperation);
+ assertFalse(stagedDistroOperation.isUninstall);
+ assertEquals(expectedDistro.getDistroVersion(), stagedDistroOperation.distroVersion);
try (ZipInputStream zis = new ZipInputStream(
new ByteArrayInputStream(expectedDistro.getBytes()))) {
@@ -401,23 +510,64 @@
throws Exception {
byte[] actualBytes = IoUtils.readFileAsByteArray(actual.getPath());
byte[] expectedBytes = Streams.readFullyNoClose(expected);
- assertTrue(Arrays.equals(expectedBytes, actualBytes));
+ assertArrayEquals(expectedBytes, actualBytes);
}
- private void assertNoContentInstalled() throws Exception {
- assertNull(installer.getInstalledDistroVersion());
+ private void assertNoDistroOperationStaged() throws Exception {
+ assertNull(installer.getStagedDistroOperation());
- File currentTzDataDir = installer.getCurrentTzDataDir();
- assertFalse(currentTzDataDir.exists());
+ File stagedTzDataDir = installer.getStagedTzDataDir();
+ assertFalse(stagedTzDataDir.exists());
// Also check no working directories are left lying around.
File workingDir = installer.getWorkingDir();
assertFalse(workingDir.exists());
- File oldDataDir = installer.getOldTzDataDir();
+ File oldDataDir = installer.getOldStagedDataDir();
assertFalse(oldDataDir.exists());
}
+ private void assertDistroUninstallStaged() throws Exception {
+ assertEquals(StagedDistroOperation.uninstall(), installer.getStagedDistroOperation());
+
+ File stagedTzDataDir = installer.getStagedTzDataDir();
+ assertTrue(stagedTzDataDir.exists());
+ assertTrue(stagedTzDataDir.isDirectory());
+
+ File uninstallTombstone =
+ new File(stagedTzDataDir, TimeZoneDistroInstaller.UNINSTALL_TOMBSTONE_FILE_NAME);
+ assertTrue(uninstallTombstone.exists());
+ assertTrue(uninstallTombstone.isFile());
+
+ // Also check no working directories are left lying around.
+ File workingDir = installer.getWorkingDir();
+ assertFalse(workingDir.exists());
+
+ File oldDataDir = installer.getOldStagedDataDir();
+ assertFalse(oldDataDir.exists());
+ }
+
+ private void simulateInstalledDistro(TimeZoneDistro timeZoneDistro) throws Exception {
+ File currentTzDataDir = installer.getCurrentTzDataDir();
+ assertFalse(currentTzDataDir.exists());
+ assertTrue(currentTzDataDir.mkdir());
+ timeZoneDistro.extractTo(currentTzDataDir);
+ }
+
+ private void assertNoInstalledDistro() {
+ assertFalse(installer.getCurrentTzDataDir().exists());
+ }
+
+ private void assertInstalledDistro(TimeZoneDistro timeZoneDistro) throws Exception {
+ File currentTzDataDir = installer.getCurrentTzDataDir();
+ assertTrue(currentTzDataDir.exists());
+ File versionFile = new File(currentTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
+ assertTrue(versionFile.exists());
+ byte[] expectedVersionBytes = timeZoneDistro.getDistroVersion().toBytes();
+ byte[] actualVersionBytes = FileUtils.readBytes(versionFile, expectedVersionBytes.length);
+ assertArrayEquals(expectedVersionBytes, actualVersionBytes);
+ }
+
private static byte[] createTzData(String rulesVersion) {
return new ZoneInfoTestHelper.TzDataBuilder()
.initializeToValid()