Fix a no-op uninstall being treated as a failure
Treat a stageUninstall() doing nothing as a success, not a
failure. This prevents the system retrying the uninstall
later.
Modified several tests accordingly and added new
ones to catch similar cases.
Test: Manual testing, adb dumpsys timezone
Test: PTS: run pts -m PtsTimeZoneTestCases
Test: CTS: run cts -m CtsLibcoreTestCases -t com.android.timezone.distro.installer.TimeZoneDistroInstallerTest
Bug: 65657176
Merged-in: I00b0cae4e2473a2b55480f0274af493bbc4f3ca8
Change-Id: I00b0cae4e2473a2b55480f0274af493bbc4f3ca8
(cherry picked from commit 5a9e9b9ed812b1df5d109a9fa99de4f32f6ea50b)
diff --git a/distro/installer/src/main/com/android/timezone/distro/installer/TimeZoneDistroInstaller.java b/distro/installer/src/main/com/android/timezone/distro/installer/TimeZoneDistroInstaller.java
index 5da7068..932eae6 100644
--- a/distro/installer/src/main/com/android/timezone/distro/installer/TimeZoneDistroInstaller.java
+++ b/distro/installer/src/main/com/android/timezone/distro/installer/TimeZoneDistroInstaller.java
@@ -36,15 +36,43 @@
public class TimeZoneDistroInstaller {
/** {@link #stageInstallWithErrorCode(TimeZoneDistro)} result code: Success. */
public final static int INSTALL_SUCCESS = 0;
+
/** {@link #stageInstallWithErrorCode(TimeZoneDistro)} result code: Distro corrupt. */
public final static int INSTALL_FAIL_BAD_DISTRO_STRUCTURE = 1;
- /** {@link #stageInstallWithErrorCode(TimeZoneDistro)} result code: Distro version incompatible. */
+
+ /**
+ * {@link #stageInstallWithErrorCode(TimeZoneDistro)} result code: Distro version incompatible.
+ */
public final static int INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION = 2;
- /** {@link #stageInstallWithErrorCode(TimeZoneDistro)} result code: Distro rules too old for device. */
+
+ /**
+ * {@link #stageInstallWithErrorCode(TimeZoneDistro)} result code: Distro rules too old for
+ * device.
+ */
public final static int INSTALL_FAIL_RULES_TOO_OLD = 3;
- /** {@link #stageInstallWithErrorCode(TimeZoneDistro)} result code: Distro content failed validation. */
+
+ /**
+ * {@link #stageInstallWithErrorCode(TimeZoneDistro)} result code: Distro content failed
+ * validation.
+ */
public final static int INSTALL_FAIL_VALIDATION_ERROR = 4;
+ /**
+ * {@link #stageUninstall()} result code: An uninstall has been successfully staged.
+ */
+ public final static int UNINSTALL_SUCCESS = 0;
+
+ /**
+ * {@link #stageUninstall()} result code: Nothing was installed that required an uninstall to be
+ * staged.
+ */
+ public final static int UNINSTALL_NOTHING_INSTALLED = 1;
+
+ /**
+ * {@link #stageUninstall()} result code: The uninstall could not be staged.
+ */
+ public final static int UNINSTALL_FAIL = 2;
+
// This constant must match one in system/timezone/tzdatacheck/tzdatacheck.cpp.
private static final String STAGED_TZ_DATA_DIR_NAME = "staged";
// This constant must match one in system/timezone/tzdatacheck/tzdatacheck.cpp.
@@ -111,7 +139,7 @@
* Stage an install of the supplied content, to be installed the next time the device boots.
*
* <p>Errors during unpacking or staging will throw an {@link IOException}.
- * Returns {@link #INSTALL_SUCCESS} or an error code.
+ * Returns {@link #INSTALL_SUCCESS} on success, or one of the failure codes.
*/
public int stageInstallWithErrorCode(TimeZoneDistro distro) throws IOException {
if (oldStagedDataDir.exists()) {
@@ -212,13 +240,17 @@
/**
* 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.
+ * device to using data from /system. If there was something else already staged it will be
+ * removed by this call.
+ *
+ * Returns {@link #UNINSTALL_SUCCESS} if staging the uninstallation was
+ * successful and reboot will be required. Returns {@link #UNINSTALL_NOTHING_INSTALLED} if
+ * there was nothing installed in /data that required an uninstall to be staged, anything that
+ * was staged will have been removed and therefore no reboot is required.
*
* <p>Errors encountered during uninstallation will throw an {@link IOException}.
*/
- public boolean stageUninstall() throws IOException {
+ public int stageUninstall() throws IOException {
Slog.i(logTag, "Uninstalling time zone update");
if (oldStagedDataDir.exists()) {
@@ -244,7 +276,7 @@
// stage anything.
if (!currentTzDataDir.exists()) {
Slog.i(logTag, "Nothing to uninstall at " + currentTzDataDir);
- return false;
+ return UNINSTALL_NOTHING_INSTALLED;
}
// Stage an uninstall in workingDir.
@@ -256,7 +288,7 @@
FileUtils.rename(workingDir, stagedTzDataDir);
Slog.i(logTag, "Uninstall staged: " + stagedTzDataDir + " successfully created");
- return true;
+ return UNINSTALL_SUCCESS;
} finally {
deleteBestEffort(oldStagedDataDir);
deleteBestEffort(workingDir);
diff --git a/distro/installer/src/test/com/android/timezone/distro/installer/TimeZoneDistroInstallerTest.java b/distro/installer/src/test/com/android/timezone/distro/installer/TimeZoneDistroInstallerTest.java
index 85235c9..e8ef268 100644
--- a/distro/installer/src/test/com/android/timezone/distro/installer/TimeZoneDistroInstallerTest.java
+++ b/distro/installer/src/test/com/android/timezone/distro/installer/TimeZoneDistroInstallerTest.java
@@ -197,6 +197,28 @@
assertNoInstalledDistro();
}
+ /**
+ * Tests staging an update when there's already an uninstall staged still results in a staged
+ * install.
+ */
+ public void testStageInstallWithErrorCode_existingStagedUninstall()
+ throws Exception {
+ byte[] distro1Bytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
+ simulateInstalledDistro(distro1Bytes);
+ assertInstalledDistro(distro1Bytes);
+
+ assertEquals(TimeZoneDistroInstaller.UNINSTALL_SUCCESS, installer.stageUninstall());
+ assertDistroUninstallStaged();
+ assertInstalledDistro(distro1Bytes);
+
+ byte[] distro2Bytes = createValidTimeZoneDistroBytes(NEWER_RULES_VERSION, 1);
+ assertEquals(
+ TimeZoneDistroInstaller.INSTALL_SUCCESS,
+ installer.stageInstallWithErrorCode(new TimeZoneDistro(distro2Bytes)));
+ assertInstalledDistro(distro1Bytes);
+ assertInstallDistroStaged(distro2Bytes);
+ }
+
/** Tests that a distro with a missing tzdata file will not update the content. */
public void testStageInstallWithErrorCode_missingTzDataFile() throws Exception {
byte[] stagedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
@@ -373,15 +395,19 @@
assertNoInstalledDistro();
}
+ /** Tests what happens if a stageUninstall() is attempted when there's nothing installed. */
public void testStageUninstall_noExistingDistro() throws Exception {
// To stage an uninstall, there would need to be installed rules.
- assertFalse(installer.stageUninstall());
+ assertEquals(
+ TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED,
+ installer.stageUninstall());
assertNoDistroOperationStaged();
assertNoInstalledDistro();
}
- public void testStageUninstall_existingStagedDataDistro() throws Exception {
+ /** Tests what happens if a stageUninstall() is attempted when there's something installed. */
+ public void testStageUninstall_existingInstalledDataDistro() throws Exception {
// To stage an uninstall, we need to have some installed rules.
byte[] installedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
simulateInstalledDistro(installedDistroBytes);
@@ -389,11 +415,63 @@
File stagedDataDir = installer.getStagedTzDataDir();
assertTrue(stagedDataDir.mkdir());
- assertTrue(installer.stageUninstall());
+ assertEquals(
+ TimeZoneDistroInstaller.UNINSTALL_SUCCESS,
+ installer.stageUninstall());
assertDistroUninstallStaged();
assertInstalledDistro(installedDistroBytes);
}
+ /**
+ * Tests what happens if a stageUninstall() is attempted when there's something installed
+ * and there's a staged install.
+ */
+ public void testStageUninstall_existingStagedInstall() throws Exception {
+ File stagedDataDir = installer.getStagedTzDataDir();
+ assertTrue(stagedDataDir.mkdir());
+
+ // Stage an install.
+ byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
+ assertEquals(TimeZoneDistroInstaller.INSTALL_SUCCESS,
+ installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes)));
+
+ // Now uninstall. It should just remove the staged install.
+ assertEquals(
+ TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED,
+ installer.stageUninstall());
+ assertNoDistroOperationStaged();
+ }
+
+ /**
+ * Tests what happens if a stageUninstall() is attempted when there's something installed
+ * and there's a staged uninstall.
+ */
+ public void testStageUninstall_existingStagedUninstall() throws Exception {
+ // To stage an uninstall, we need to have some installed rules.
+ byte[] installedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
+ simulateInstalledDistro(installedDistroBytes);
+
+ File stagedDataDir = installer.getStagedTzDataDir();
+ assertTrue(stagedDataDir.mkdir());
+
+ assertEquals(
+ TimeZoneDistroInstaller.UNINSTALL_SUCCESS,
+ installer.stageUninstall());
+ assertDistroUninstallStaged();
+ assertInstalledDistro(installedDistroBytes);
+
+ // Now stage a second uninstall.
+ assertEquals(
+ TimeZoneDistroInstaller.UNINSTALL_SUCCESS,
+ installer.stageUninstall());
+ assertDistroUninstallStaged();
+ assertInstalledDistro(installedDistroBytes);
+ }
+
+ /**
+ * Tests what happens if a stageUninstall() is attempted when there are unexpected working
+ * directories present.
+ */
public void testStageUninstall_oldDirsAlreadyExists() throws Exception {
// To stage an uninstall, we need to have some installed rules.
byte[] installedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
@@ -405,7 +483,9 @@
File workingDir = installer.getWorkingDir();
assertTrue(workingDir.mkdir());
- assertTrue(installer.stageUninstall());
+ assertEquals(
+ TimeZoneDistroInstaller.UNINSTALL_SUCCESS,
+ installer.stageUninstall());
assertDistroUninstallStaged();
assertFalse(workingDir.exists());
@@ -446,7 +526,9 @@
// Check result after unsuccessfully staging an uninstall.
// Can't stage an uninstall without an installed distro.
- assertFalse(installer.stageUninstall());
+ assertEquals(
+ TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED,
+ installer.stageUninstall());
assertNull(installer.getStagedDistroOperation());
assertNoDistroOperationStaged();
assertNoInstalledDistro();
@@ -463,7 +545,9 @@
// 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());
+ assertEquals(
+ TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED,
+ installer.stageUninstall());
assertNull(installer.getStagedDistroOperation());
assertNoDistroOperationStaged();
assertNoInstalledDistro();
@@ -473,7 +557,9 @@
assertInstalledDistro(distro1Bytes);
// Check state after successfully staging an uninstall.
- assertTrue(installer.stageUninstall());
+ assertEquals(
+ TimeZoneDistroInstaller.UNINSTALL_SUCCESS,
+ installer.stageUninstall());
StagedDistroOperation expectedStagedUninstall = StagedDistroOperation.uninstall();
assertEquals(expectedStagedUninstall, installer.getStagedDistroOperation());
assertDistroUninstallStaged();