Merge "Add support for time zone notifications"

This commit is contained in:
Neil Fuller
2018-02-12 15:14:49 +00:00
committed by Android (Google) Code Review
10 changed files with 167 additions and 19 deletions

View File

@@ -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.
*
* <p>See {@link #EXTRA_OPERATION_STAGED}
*
* <p>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,

View File

@@ -573,6 +573,10 @@
<protected-broadcast android:name="android.media.tv.action.CHANNEL_BROWSABLE_REQUESTED" />
<protected-broadcast android:name="com.android.server.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER" />
<!-- Time zone rules update intents fired by the system server -->
<protected-broadcast android:name="com.android.intent.action.timezone.RULES_UPDATE_OPERATION" />
<protected-broadcast android:name="com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK" />
<!-- Made protected in P (was introduced in JB-MR2) -->
<protected-broadcast android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
<protected-broadcast android:name="android.telephony.euicc.action.OTA_STATUS_CHANGED" />

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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)