diff --git a/core/java/android/app/timezone/RulesManager.java b/core/java/android/app/timezone/RulesManager.java
index 0a38eb9ae7772..dc79256941760 100644
--- a/core/java/android/app/timezone/RulesManager.java
+++ b/core/java/android/app/timezone/RulesManager.java
@@ -68,6 +68,23 @@ public final class RulesManager {
private static final String TAG = "timezone.RulesManager";
private static final boolean DEBUG = false;
+ /**
+ * The action of the intent that the Android system will broadcast when a time zone rules update
+ * operation has been successfully staged (i.e. to be applied next reboot) or unstaged.
+ *
+ *
See {@link #EXTRA_OPERATION_STAGED}
+ *
+ *
This is a protected intent that can only be sent by the system.
+ */
+ public static final String ACTION_RULES_UPDATE_OPERATION =
+ "com.android.intent.action.timezone.RULES_UPDATE_OPERATION";
+
+ /**
+ * The key for a boolean extra for the {@link #ACTION_RULES_UPDATE_OPERATION} intent used to
+ * indicate whether the operation was a "stage" or an "unstage".
+ */
+ public static final String EXTRA_OPERATION_STAGED = "staged";
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "SUCCESS", "ERROR_" }, value = {
SUCCESS,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1cf75a0f9229a..022cf229f4818 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -573,6 +573,10 @@
+
+
+
+
diff --git a/services/core/java/com/android/server/timezone/PackageTracker.java b/services/core/java/com/android/server/timezone/PackageTracker.java
index 0e8d8bc8e411e..8f4cada0d0ba2 100644
--- a/services/core/java/com/android/server/timezone/PackageTracker.java
+++ b/services/core/java/com/android/server/timezone/PackageTracker.java
@@ -59,7 +59,7 @@ public class PackageTracker {
private static final String TAG = "timezone.PackageTracker";
private final PackageManagerHelper mPackageManagerHelper;
- private final IntentHelper mIntentHelper;
+ private final PackageTrackerIntentHelper mIntentHelper;
private final ConfigHelper mConfigHelper;
private final PackageStatusStorage mPackageStatusStorage;
private final Clock mElapsedRealtimeClock;
@@ -103,13 +103,13 @@ public class PackageTracker {
helperImpl /* configHelper */,
helperImpl /* packageManagerHelper */,
new PackageStatusStorage(storageDir),
- new IntentHelperImpl(context));
+ new PackageTrackerIntentHelperImpl(context));
}
// A constructor that can be used by tests to supply mocked / faked dependencies.
PackageTracker(Clock elapsedRealtimeClock, ConfigHelper configHelper,
PackageManagerHelper packageManagerHelper, PackageStatusStorage packageStatusStorage,
- IntentHelper intentHelper) {
+ PackageTrackerIntentHelper intentHelper) {
mElapsedRealtimeClock = elapsedRealtimeClock;
mConfigHelper = configHelper;
mPackageManagerHelper = packageManagerHelper;
diff --git a/services/core/java/com/android/server/timezone/IntentHelper.java b/services/core/java/com/android/server/timezone/PackageTrackerIntentHelper.java
similarity index 97%
rename from services/core/java/com/android/server/timezone/IntentHelper.java
rename to services/core/java/com/android/server/timezone/PackageTrackerIntentHelper.java
index 5de5432130094..3753ece03bb0b 100644
--- a/services/core/java/com/android/server/timezone/IntentHelper.java
+++ b/services/core/java/com/android/server/timezone/PackageTrackerIntentHelper.java
@@ -21,7 +21,7 @@ package com.android.server.timezone;
* it is not possible to test various cases with the real one because of the need to simulate
* receiving and broadcasting intents.
*/
-interface IntentHelper {
+interface PackageTrackerIntentHelper {
void initialize(String updateAppPackageName, String dataAppPackageName,
PackageTracker packageTracker);
diff --git a/services/core/java/com/android/server/timezone/IntentHelperImpl.java b/services/core/java/com/android/server/timezone/PackageTrackerIntentHelperImpl.java
similarity index 93%
rename from services/core/java/com/android/server/timezone/IntentHelperImpl.java
rename to services/core/java/com/android/server/timezone/PackageTrackerIntentHelperImpl.java
index 6e6259d902dc0..4110d881f3fb4 100644
--- a/services/core/java/com/android/server/timezone/IntentHelperImpl.java
+++ b/services/core/java/com/android/server/timezone/PackageTrackerIntentHelperImpl.java
@@ -28,16 +28,16 @@ import android.os.UserHandle;
import android.util.Slog;
/**
- * The bona fide implementation of {@link IntentHelper}.
+ * The bona fide implementation of {@link PackageTrackerIntentHelper}.
*/
-final class IntentHelperImpl implements IntentHelper {
+final class PackageTrackerIntentHelperImpl implements PackageTrackerIntentHelper {
- private final static String TAG = "timezone.IntentHelperImpl";
+ private final static String TAG = "timezone.PackageTrackerIntentHelperImpl";
private final Context mContext;
private String mUpdaterAppPackageName;
- IntentHelperImpl(Context context) {
+ PackageTrackerIntentHelperImpl(Context context) {
mContext = context;
}
diff --git a/services/core/java/com/android/server/timezone/RulesManagerIntentHelper.java b/services/core/java/com/android/server/timezone/RulesManagerIntentHelper.java
new file mode 100644
index 0000000000000..bb317cf2f9887
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/RulesManagerIntentHelper.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.android.server.timezone;
+
+/**
+ * An easy-to-mock interface around intent sending / receiving for use by
+ * {@link RulesManagerService}; it is not possible to test various cases with the real one because
+ * of the need to simulate broadcasting intents.
+ */
+interface RulesManagerIntentHelper {
+
+ /**
+ * Send a broadcast informing listeners that a time zone operation is staged.
+ */
+ void sendTimeZoneOperationStaged();
+
+ /**
+ * Send a broadcast informing listeners that a time zone operation is no longer staged.
+ */
+ void sendTimeZoneOperationUnstaged();
+}
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
index be9b204721dbb..872d7237f4f11 100644
--- a/services/core/java/com/android/server/timezone/RulesManagerService.java
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -99,6 +99,7 @@ public final class RulesManagerService extends IRulesManager.Stub {
private final PermissionHelper mPermissionHelper;
private final PackageTracker mPackageTracker;
private final Executor mExecutor;
+ private final RulesManagerIntentHelper mIntentHelper;
private final TimeZoneDistroInstaller mInstaller;
private static RulesManagerService create(Context context) {
@@ -106,16 +107,19 @@ public final class RulesManagerService extends IRulesManager.Stub {
return new RulesManagerService(
helper /* permissionHelper */,
helper /* executor */,
+ helper /* intentHelper */,
PackageTracker.create(context),
new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR));
}
// A constructor that can be used by tests to supply mocked / faked dependencies.
- RulesManagerService(PermissionHelper permissionHelper,
- Executor executor, PackageTracker packageTracker,
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ RulesManagerService(PermissionHelper permissionHelper, Executor executor,
+ RulesManagerIntentHelper intentHelper, PackageTracker packageTracker,
TimeZoneDistroInstaller timeZoneDistroInstaller) {
mPermissionHelper = permissionHelper;
mExecutor = executor;
+ mIntentHelper = intentHelper;
mPackageTracker = packageTracker;
mInstaller = timeZoneDistroInstaller;
}
@@ -271,6 +275,10 @@ public final class RulesManagerService extends IRulesManager.Stub {
TimeZoneDistro distro = new TimeZoneDistro(is);
int installerResult = mInstaller.stageInstallWithErrorCode(distro);
+
+ // Notify interested parties that something is staged.
+ sendInstallNotificationIntentIfRequired(installerResult);
+
int resultCode = mapInstallerResultToApiCode(installerResult);
EventLogTags.writeTimezoneInstallComplete(toStringOrNull(mCheckToken), resultCode);
sendFinishedStatus(mCallback, resultCode);
@@ -291,6 +299,12 @@ public final class RulesManagerService extends IRulesManager.Stub {
}
}
+ private void sendInstallNotificationIntentIfRequired(int installerResult) {
+ if (installerResult == TimeZoneDistroInstaller.INSTALL_SUCCESS) {
+ mIntentHelper.sendTimeZoneOperationStaged();
+ }
+ }
+
private int mapInstallerResultToApiCode(int installerResult) {
switch (installerResult) {
case TimeZoneDistroInstaller.INSTALL_SUCCESS:
@@ -351,6 +365,10 @@ public final class RulesManagerService extends IRulesManager.Stub {
boolean packageTrackerStatus = false;
try {
int uninstallResult = mInstaller.stageUninstall();
+
+ // Notify interested parties that something is staged.
+ sendUninstallNotificationIntentIfRequired(uninstallResult);
+
packageTrackerStatus = (uninstallResult == TimeZoneDistroInstaller.UNINSTALL_SUCCESS
|| uninstallResult == TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED);
@@ -374,6 +392,20 @@ public final class RulesManagerService extends IRulesManager.Stub {
mOperationInProgress.set(false);
}
}
+
+ private void sendUninstallNotificationIntentIfRequired(int uninstallResult) {
+ switch (uninstallResult) {
+ case TimeZoneDistroInstaller.UNINSTALL_SUCCESS:
+ mIntentHelper.sendTimeZoneOperationStaged();
+ break;
+ case TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED:
+ mIntentHelper.sendTimeZoneOperationUnstaged();
+ break;
+ case TimeZoneDistroInstaller.UNINSTALL_FAIL:
+ default:
+ // No-op - unknown or nothing to notify about.
+ }
+ }
}
private void sendFinishedStatus(ICallback callback, int resultCode) {
diff --git a/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java b/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java
index e8a401e792350..8f5c7a783304b 100644
--- a/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java
+++ b/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java
@@ -18,22 +18,20 @@ package com.android.server.timezone;
import com.android.internal.util.DumpUtils;
+import android.app.timezone.RulesManager;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.content.Intent;
import android.os.AsyncTask;
-import android.os.Binder;
-import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
-import java.io.FileInputStream;
-import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
-import libcore.io.Streams;
/**
* A single class that implements multiple helper interfaces for use by {@link RulesManagerService}.
*/
-final class RulesManagerServiceHelperImpl implements PermissionHelper, Executor {
+final class RulesManagerServiceHelperImpl
+ implements PermissionHelper, Executor, RulesManagerIntentHelper {
private final Context mContext;
@@ -55,4 +53,22 @@ final class RulesManagerServiceHelperImpl implements PermissionHelper, Executor
public void execute(Runnable runnable) {
AsyncTask.execute(runnable);
}
+
+ @Override
+ public void sendTimeZoneOperationStaged() {
+ sendOperationIntent(true /* staged */);
+ }
+
+ @Override
+ public void sendTimeZoneOperationUnstaged() {
+ sendOperationIntent(false /* staged */);
+ }
+
+ private void sendOperationIntent(boolean staged) {
+ Intent intent = new Intent(RulesManager.ACTION_RULES_UPDATE_OPERATION);
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ intent.putExtra(RulesManager.EXTRA_OPERATION_STAGED, staged);
+ mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
index 9cf6392cab971..d9f4adfb5e063 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
@@ -31,7 +31,6 @@ import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import java.io.File;
-import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Clock;
@@ -1400,7 +1399,7 @@ public class PackageTrackerTest {
/**
* A fake IntentHelper implementation for use in tests.
*/
- private static class FakeIntentHelper implements IntentHelper {
+ private static class FakeIntentHelper implements PackageTrackerIntentHelper {
private PackageTracker mPackageTracker;
private String mUpdateAppPackageName;
diff --git a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
index 1cfae1ef1a6d8..f5969f39e87c2 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
@@ -68,6 +68,7 @@ public class RulesManagerServiceTest {
private FakeExecutor mFakeExecutor;
private PermissionHelper mMockPermissionHelper;
+ private RulesManagerIntentHelper mMockIntentHelper;
private PackageTracker mMockPackageTracker;
private TimeZoneDistroInstaller mMockTimeZoneDistroInstaller;
@@ -77,11 +78,13 @@ public class RulesManagerServiceTest {
mMockPackageTracker = mock(PackageTracker.class);
mMockPermissionHelper = mock(PermissionHelper.class);
+ mMockIntentHelper = mock(RulesManagerIntentHelper.class);
mMockTimeZoneDistroInstaller = mock(TimeZoneDistroInstaller.class);
mRulesManagerService = new RulesManagerService(
mMockPermissionHelper,
mFakeExecutor,
+ mMockIntentHelper,
mMockPackageTracker,
mMockTimeZoneDistroInstaller);
}
@@ -329,6 +332,7 @@ public class RulesManagerServiceTest {
mFakeExecutor.assertNothingQueued();
verifyNoInstallerCallsMade();
verifyNoPackageTrackerCallsMade();
+ verifyNoIntentsSent();
}
@Test
@@ -353,6 +357,7 @@ public class RulesManagerServiceTest {
mFakeExecutor.assertNothingQueued();
verifyNoInstallerCallsMade();
verifyNoPackageTrackerCallsMade();
+ verifyNoIntentsSent();
}
@Test
@@ -372,6 +377,7 @@ public class RulesManagerServiceTest {
mFakeExecutor.assertNothingQueued();
verifyNoInstallerCallsMade();
verifyNoPackageTrackerCallsMade();
+ verifyNoIntentsSent();
}
@Test
@@ -394,6 +400,7 @@ public class RulesManagerServiceTest {
mFakeExecutor.assertNothingQueued();
verifyNoInstallerCallsMade();
verifyNoPackageTrackerCallsMade();
+ verifyNoIntentsSent();
}
@Test
@@ -416,6 +423,7 @@ public class RulesManagerServiceTest {
callback.assertNoResultReceived();
verifyNoInstallerCallsMade();
verifyNoPackageTrackerCallsMade();
+ verifyNoIntentsSent();
// Set up the installer.
configureStageInstallExpectation(TimeZoneDistroInstaller.INSTALL_SUCCESS);
@@ -428,6 +436,7 @@ public class RulesManagerServiceTest {
// Verify the expected calls were made to other components.
verifyStageInstallCalled();
verifyPackageTrackerCalled(token, true /* success */);
+ verifyStagedOperationIntentSent();
// Check the callback was called.
callback.assertResultReceived(Callback.SUCCESS);
@@ -450,6 +459,7 @@ public class RulesManagerServiceTest {
// Assert nothing has happened yet.
verifyNoInstallerCallsMade();
callback.assertNoResultReceived();
+ verifyNoIntentsSent();
// Set up the installer.
configureStageInstallExpectation(TimeZoneDistroInstaller.INSTALL_SUCCESS);
@@ -462,6 +472,7 @@ public class RulesManagerServiceTest {
// Verify the expected calls were made to other components.
verifyStageInstallCalled();
verifyPackageTrackerCalled(null /* expectedToken */, true /* success */);
+ verifyStagedOperationIntentSent();
// Check the callback was received.
callback.assertResultReceived(Callback.SUCCESS);
@@ -486,6 +497,7 @@ public class RulesManagerServiceTest {
// Assert nothing has happened yet.
verifyNoInstallerCallsMade();
callback.assertNoResultReceived();
+ verifyNoIntentsSent();
// Set up the installer.
configureStageInstallExpectation(TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR);
@@ -502,6 +514,9 @@ public class RulesManagerServiceTest {
boolean expectedSuccess = true;
verifyPackageTrackerCalled(token, expectedSuccess);
+ // Nothing should be staged, so no intents sent.
+ verifyNoIntentsSent();
+
// Check the callback was received.
callback.assertResultReceived(Callback.ERROR_INSTALL_VALIDATION_ERROR);
}
@@ -529,6 +544,7 @@ public class RulesManagerServiceTest {
mFakeExecutor.assertNothingQueued();
verifyNoInstallerCallsMade();
verifyNoPackageTrackerCallsMade();
+ verifyNoIntentsSent();
}
@Test
@@ -548,6 +564,7 @@ public class RulesManagerServiceTest {
mFakeExecutor.assertNothingQueued();
verifyNoInstallerCallsMade();
verifyNoPackageTrackerCallsMade();
+ verifyNoIntentsSent();
}
@Test
@@ -566,6 +583,7 @@ public class RulesManagerServiceTest {
mFakeExecutor.assertNothingQueued();
verifyNoInstallerCallsMade();
verifyNoPackageTrackerCallsMade();
+ verifyNoIntentsSent();
}
@Test
@@ -585,6 +603,7 @@ public class RulesManagerServiceTest {
callback.assertNoResultReceived();
verifyNoInstallerCallsMade();
verifyNoPackageTrackerCallsMade();
+ verifyNoIntentsSent();
// Set up the installer.
configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_SUCCESS);
@@ -595,6 +614,7 @@ public class RulesManagerServiceTest {
// Verify the expected calls were made to other components.
verifyStageUninstallCalled();
verifyPackageTrackerCalled(token, true /* success */);
+ verifyStagedOperationIntentSent();
// Check the callback was called.
callback.assertResultReceived(Callback.SUCCESS);
@@ -617,6 +637,7 @@ public class RulesManagerServiceTest {
callback.assertNoResultReceived();
verifyNoInstallerCallsMade();
verifyNoPackageTrackerCallsMade();
+ verifyNoIntentsSent();
// Set up the installer.
configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED);
@@ -627,6 +648,7 @@ public class RulesManagerServiceTest {
// Verify the expected calls were made to other components.
verifyStageUninstallCalled();
verifyPackageTrackerCalled(token, true /* success */);
+ verifyUnstagedOperationIntentSent();
// Check the callback was called.
callback.assertResultReceived(Callback.SUCCESS);
@@ -645,6 +667,7 @@ public class RulesManagerServiceTest {
// Assert nothing has happened yet.
verifyNoInstallerCallsMade();
callback.assertNoResultReceived();
+ verifyNoIntentsSent();
// Set up the installer.
configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_SUCCESS);
@@ -655,6 +678,7 @@ public class RulesManagerServiceTest {
// Verify the expected calls were made to other components.
verifyStageUninstallCalled();
verifyPackageTrackerCalled(null /* expectedToken */, true /* success */);
+ verifyStagedOperationIntentSent();
// Check the callback was received.
callback.assertResultReceived(Callback.SUCCESS);
@@ -676,6 +700,7 @@ public class RulesManagerServiceTest {
// Assert nothing has happened yet.
verifyNoInstallerCallsMade();
callback.assertNoResultReceived();
+ verifyNoIntentsSent();
// Set up the installer.
configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_FAIL);
@@ -686,6 +711,7 @@ public class RulesManagerServiceTest {
// Verify the expected calls were made to other components.
verifyStageUninstallCalled();
verifyPackageTrackerCalled(token, false /* success */);
+ verifyNoIntentsSent();
// Check the callback was received.
callback.assertResultReceived(Callback.ERROR_UNKNOWN_FAILURE);
@@ -714,6 +740,7 @@ public class RulesManagerServiceTest {
// Verify the expected calls were made to other components.
verifyPackageTrackerCalled(token, true /* success */);
verifyNoInstallerCallsMade();
+ verifyNoIntentsSent();
}
@Test
@@ -734,6 +761,7 @@ public class RulesManagerServiceTest {
// Assert no other calls were made.
verifyNoInstallerCallsMade();
verifyNoPackageTrackerCallsMade();
+ verifyNoIntentsSent();
}
@Test
@@ -749,6 +777,7 @@ public class RulesManagerServiceTest {
// Assert everything required was done.
verifyNoInstallerCallsMade();
verifyPackageTrackerCalled(token, false /* success */);
+ verifyNoIntentsSent();
}
@Test
@@ -761,6 +790,7 @@ public class RulesManagerServiceTest {
// Assert everything required was done.
verifyNoInstallerCallsMade();
verifyPackageTrackerCalled(null /* token */, true /* success */);
+ verifyNoIntentsSent();
}
@Test
@@ -865,6 +895,21 @@ public class RulesManagerServiceTest {
reset(mMockPackageTracker);
}
+ private void verifyNoIntentsSent() {
+ verifyNoMoreInteractions(mMockIntentHelper);
+ reset(mMockIntentHelper);
+ }
+
+ private void verifyStagedOperationIntentSent() {
+ verify(mMockIntentHelper).sendTimeZoneOperationStaged();
+ reset(mMockIntentHelper);
+ }
+
+ private void verifyUnstagedOperationIntentSent() {
+ verify(mMockIntentHelper).sendTimeZoneOperationUnstaged();
+ reset(mMockIntentHelper);
+ }
+
private void configureCallerHasPermission() throws Exception {
doNothing()
.when(mMockPermissionHelper)