Merge "Remove simulated LTZP and enable replacement"

This commit is contained in:
Neil Fuller
2021-08-03 15:51:42 +00:00
committed by Android (Google) Code Review
14 changed files with 308 additions and 795 deletions

View File

@@ -50,10 +50,10 @@ public final class LocationTimeZoneManager {
public static final String SHELL_COMMAND_STOP = "stop";
/**
* A shell command that tells the service to record state information during tests. The next
* argument value is "true" or "false".
* A shell command that clears recorded provider state information during tests.
*/
public static final String SHELL_COMMAND_RECORD_PROVIDER_STATES = "record_provider_states";
public static final String SHELL_COMMAND_CLEAR_RECORDED_PROVIDER_STATES =
"clear_recorded_provider_states";
/**
* A shell command that tells the service to dump its current state.
@@ -65,44 +65,15 @@ public final class LocationTimeZoneManager {
*/
public static final String DUMP_STATE_OPTION_PROTO = "--proto";
/**
* A shell command that sends test commands to a provider
*/
public static final String SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND =
"send_provider_test_command";
/** A shell command that starts the location_time_zone_manager with named test providers. */
public static final String SHELL_COMMAND_START_WITH_TEST_PROVIDERS =
"start_with_test_providers";
/**
* Simulated provider test command that simulates the bind succeeding.
* The token that can be passed to {@link #SHELL_COMMAND_START_WITH_TEST_PROVIDERS} to indicate
* there is no provider.
*/
public static final String SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND = "on_bind";
/**
* Simulated provider test command that simulates the provider unbinding.
*/
public static final String SIMULATED_PROVIDER_TEST_COMMAND_ON_UNBIND = "on_unbind";
/**
* Simulated provider test command that simulates the provider entering the "permanent failure"
* state.
*/
public static final String SIMULATED_PROVIDER_TEST_COMMAND_PERM_FAILURE = "perm_fail";
/**
* Simulated provider test command that simulates the provider entering the "success" (time
* zone(s) detected) state.
*/
public static final String SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS = "success";
/**
* Argument for {@link #SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS} to specify TZDB time zone IDs.
*/
public static final String SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ = "tz";
/**
* Simulated provider test command that simulates the provider entering the "uncertain"
* state.
*/
public static final String SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN = "uncertain";
public static final String NULL_PACKAGE_NAME_TOKEN = "@null";
private LocationTimeZoneManager() {
// No need to instantiate.

View File

@@ -46,18 +46,11 @@ import java.util.Set;
public final class ServiceConfigAccessor {
@StringDef(prefix = "PROVIDER_MODE_",
value = { PROVIDER_MODE_SIMULATED, PROVIDER_MODE_DISABLED, PROVIDER_MODE_ENABLED})
value = { PROVIDER_MODE_DISABLED, PROVIDER_MODE_ENABLED})
@Retention(RetentionPolicy.SOURCE)
@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
@interface ProviderMode {}
/**
* The "simulated" provider mode.
* For use with {@link #getPrimaryLocationTimeZoneProviderMode()} and {@link
* #getSecondaryLocationTimeZoneProviderMode()}.
*/
public static final @ProviderMode String PROVIDER_MODE_SIMULATED = "simulated";
/**
* The "disabled" provider mode. For use with {@link #getPrimaryLocationTimeZoneProviderMode()}
* and {@link #getSecondaryLocationTimeZoneProviderMode()}.
@@ -110,6 +103,47 @@ public final class ServiceConfigAccessor {
@NonNull private final ServerFlags mServerFlags;
/**
* The mode to use for the primary location time zone provider in a test. Setting this
* disables some permission checks.
* This state is volatile: it is never written to storage / never survives a reboot. This is to
* avoid a test provider accidentally being left configured on a device.
* See also {@link #resetVolatileTestConfig()}.
*/
@Nullable
private String mTestPrimaryLocationTimeZoneProviderMode;
/**
* The package name to use for the primary location time zone provider in a test.
* This state is volatile: it is never written to storage / never survives a reboot. This is to
* avoid a test provider accidentally being left configured on a device.
* See also {@link #resetVolatileTestConfig()}.
*/
@Nullable
private String mTestPrimaryLocationTimeZoneProviderPackageName;
/**
* See {@link #mTestPrimaryLocationTimeZoneProviderMode}; this is the equivalent for the
* secondary provider.
*/
@Nullable
private String mTestSecondaryLocationTimeZoneProviderMode;
/**
* See {@link #mTestPrimaryLocationTimeZoneProviderPackageName}; this is the equivalent for the
* secondary provider.
*/
@Nullable
private String mTestSecondaryLocationTimeZoneProviderPackageName;
/**
* Whether to record state changes for tests.
* This state is volatile: it is never written to storage / never survives a reboot. This is to
* avoid a test state accidentally being left configured on a device.
* See also {@link #resetVolatileTestConfig()}.
*/
private boolean mRecordProviderStateChanges;
private ServiceConfigAccessor(@NonNull Context context) {
mContext = Objects.requireNonNull(context);
@@ -200,23 +234,98 @@ public final class ServiceConfigAccessor {
defaultEnabled);
}
/** Returns the package name of the app hosting the primary location time zone provider. */
@NonNull
public String getPrimaryLocationTimeZoneProviderPackageName() {
if (mTestPrimaryLocationTimeZoneProviderMode != null) {
// In test mode: use the test setting value.
return mTestPrimaryLocationTimeZoneProviderPackageName;
}
return mContext.getResources().getString(
R.string.config_primaryLocationTimeZoneProviderPackageName);
}
/**
* Sets the package name of the app hosting the primary location time zone provider for tests.
* Setting a {@code null} value means the provider is to be disabled.
* The values are reset with {@link #resetVolatileTestConfig()}.
*/
public void setTestPrimaryLocationTimeZoneProviderPackageName(
@Nullable String testPrimaryLocationTimeZoneProviderPackageName) {
mTestPrimaryLocationTimeZoneProviderPackageName =
testPrimaryLocationTimeZoneProviderPackageName;
mTestPrimaryLocationTimeZoneProviderMode =
mTestPrimaryLocationTimeZoneProviderPackageName == null
? PROVIDER_MODE_DISABLED : PROVIDER_MODE_ENABLED;
}
/**
* Returns {@code true} if the usual permission checks are to be bypassed for the primary
* provider. Returns {@code true} only if {@link
* #setTestPrimaryLocationTimeZoneProviderPackageName} has been called.
*/
public boolean isTestPrimaryLocationTimeZoneProvider() {
return mTestPrimaryLocationTimeZoneProviderMode != null;
}
/** Returns the package name of the app hosting the secondary location time zone provider. */
@NonNull
public String getSecondaryLocationTimeZoneProviderPackageName() {
if (mTestSecondaryLocationTimeZoneProviderMode != null) {
// In test mode: use the test setting value.
return mTestSecondaryLocationTimeZoneProviderPackageName;
}
return mContext.getResources().getString(
R.string.config_secondaryLocationTimeZoneProviderPackageName);
}
/**
* Returns {@code true} if the primary location time zone provider can be used.
* Sets the package name of the app hosting the secondary location time zone provider for tests.
* Setting a {@code null} value means the provider is to be disabled.
* The values are reset with {@link #resetVolatileTestConfig()}.
*/
public void setTestSecondaryLocationTimeZoneProviderPackageName(
@Nullable String testSecondaryLocationTimeZoneProviderPackageName) {
mTestSecondaryLocationTimeZoneProviderPackageName =
testSecondaryLocationTimeZoneProviderPackageName;
mTestSecondaryLocationTimeZoneProviderMode =
mTestSecondaryLocationTimeZoneProviderPackageName == null
? PROVIDER_MODE_DISABLED : PROVIDER_MODE_ENABLED;
}
/**
* Returns {@code true} if the usual permission checks are to be bypassed for the secondary
* provider. Returns {@code true} only if {@link
* #setTestSecondaryLocationTimeZoneProviderPackageName} has been called.
*/
public boolean isTestSecondaryLocationTimeZoneProvider() {
return mTestSecondaryLocationTimeZoneProviderMode != null;
}
/**
* Enables/disables the state recording mode for tests. The value is reset with {@link
* #resetVolatileTestConfig()}.
*/
public void setRecordProviderStateChanges(boolean enabled) {
mRecordProviderStateChanges = enabled;
}
/**
* Returns {@code true} if providers are expected to record their state changes for tests.
*/
public boolean getRecordProviderStateChanges() {
return mRecordProviderStateChanges;
}
/**
* Returns the mode for the primary location time zone provider.
*/
@NonNull
public @ProviderMode String getPrimaryLocationTimeZoneProviderMode() {
if (mTestPrimaryLocationTimeZoneProviderMode != null) {
// In test mode: use the test setting value.
return mTestPrimaryLocationTimeZoneProviderMode;
}
return mServerFlags.getOptionalString(
ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE)
.orElse(getPrimaryLocationTimeZoneProviderModeFromConfig());
@@ -230,9 +339,13 @@ public final class ServiceConfigAccessor {
}
/**
* Returns the mode for the secondary location time zone provider can be used.
* Returns the mode for the secondary location time zone provider.
*/
public @ProviderMode String getSecondaryLocationTimeZoneProviderMode() {
if (mTestSecondaryLocationTimeZoneProviderMode != null) {
// In test mode: use the test setting value.
return mTestSecondaryLocationTimeZoneProviderMode;
}
return mServerFlags.getOptionalString(
ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE)
.orElse(getSecondaryLocationTimeZoneProviderModeFromConfig());
@@ -298,6 +411,15 @@ public final class ServiceConfigAccessor {
DEFAULT_PROVIDER_UNCERTAINTY_DELAY);
}
/** Clears all in-memory test config. */
public void resetVolatileTestConfig() {
mTestPrimaryLocationTimeZoneProviderPackageName = null;
mTestPrimaryLocationTimeZoneProviderMode = null;
mTestSecondaryLocationTimeZoneProviderPackageName = null;
mTestSecondaryLocationTimeZoneProviderMode = null;
mRecordProviderStateChanges = false;
}
private boolean getConfigBoolean(int providerEnabledConfigId) {
Resources resources = mContext.getResources();
return resources.getBoolean(providerEnabledConfigId);

View File

@@ -26,7 +26,6 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteCallback;
import android.util.IndentingPrintWriter;
import java.time.Duration;
@@ -45,9 +44,10 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
@NonNull ProviderMetricsLogger providerMetricsLogger,
@NonNull ThreadingDomain threadingDomain,
@NonNull String providerName,
@NonNull LocationTimeZoneProviderProxy proxy) {
@NonNull LocationTimeZoneProviderProxy proxy,
boolean recordStateChanges) {
super(providerMetricsLogger, threadingDomain, providerName,
new ZoneInfoDbTimeZoneProviderEventPreProcessor());
new ZoneInfoDbTimeZoneProviderEventPreProcessor(), recordStateChanges);
mProxy = Objects.requireNonNull(proxy);
}
@@ -125,16 +125,6 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
mProxy.setRequest(request);
}
/**
* Passes the supplied test command to the current proxy.
*/
@Override
void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) {
mThreadingDomain.assertCurrentThread();
mProxy.handleTestCommand(testCommand, callback);
}
@Override
public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
synchronized (mSharedLock) {

View File

@@ -33,7 +33,6 @@ import android.annotation.DurationMillisLong;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteCallback;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
@@ -590,41 +589,14 @@ class ControllerImpl extends LocationTimeZoneProviderController {
}
/**
* Passes a test command to the specified provider. If the provider name does not match a
* known provider, then the command is logged and discarded.
* Clears recorded provider state changes (for use during tests).
*/
void handleProviderTestCommand(
@IntRange(from = 0, to = 1) int providerIndex, @NonNull TestCommand testCommand,
@Nullable RemoteCallback callback) {
mThreadingDomain.assertCurrentThread();
LocationTimeZoneProvider targetProvider = getLocationTimeZoneProvider(providerIndex);
if (targetProvider == null) {
warnLog("Unable to process test command:"
+ " providerIndex=" + providerIndex + ", testCommand=" + testCommand);
return;
}
synchronized (mSharedLock) {
try {
targetProvider.handleTestCommand(testCommand, callback);
} catch (Exception e) {
warnLog("Unable to process test command:"
+ " providerIndex=" + providerIndex + ", testCommand=" + testCommand, e);
}
}
}
/**
* Sets whether the controller should record provider state changes for later dumping via
* {@link #getStateForTests()}.
*/
void setProviderStateRecordingEnabled(boolean enabled) {
void clearRecordedProviderStates() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
mPrimaryProvider.setStateChangeRecordingEnabled(enabled);
mSecondaryProvider.setStateChangeRecordingEnabled(enabled);
mPrimaryProvider.clearRecordedStates();
mSecondaryProvider.clearRecordedStates();
}
}

View File

@@ -19,16 +19,13 @@ package com.android.server.timezonedetector.location;
import static android.app.time.LocationTimeZoneManager.SERVICE_NAME;
import static com.android.server.timezonedetector.ServiceConfigAccessor.PROVIDER_MODE_DISABLED;
import static com.android.server.timezonedetector.ServiceConfigAccessor.PROVIDER_MODE_SIMULATED;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteCallback;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.service.timezone.TimeZoneProviderService;
@@ -50,9 +47,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* A service class that acts as a container for the {@link LocationTimeZoneProviderController},
@@ -70,12 +64,6 @@ import java.util.concurrent.atomic.AtomicReference;
* one indicated by {@link ThreadingDomain}. Because methods like {@link #dump} can be invoked on
* another thread, the service and its related objects must still be thread-safe.
*
* <p>For testing / reproduction of bugs, it is possible to put providers into "simulation
* mode" where the real binder clients are replaced by {@link
* SimulatedLocationTimeZoneProviderProxy}. This means that the real client providers are never
* bound (ensuring no real location events will be received) and simulated events / behaviors
* can be injected via the command line.
*
* <p>See {@code adb shell cmd location_time_zone_manager help}" for details and more options.
*/
public class LocationTimeZoneManagerService extends Binder {
@@ -247,6 +235,36 @@ public class LocationTimeZoneManagerService extends Binder {
}
}
/**
* Starts the service with fake provider package names configured for tests. The config is
* cleared when the service next stops.
*
* <p>Because this method posts work to the {@code mThreadingDomain} thread and waits for
* completion, it cannot be called from the {@code mThreadingDomain} thread.
*/
void startWithTestProviders(@Nullable String testPrimaryProviderPackageName,
@Nullable String testSecondaryProviderPackageName,
boolean recordProviderStateChanges) {
enforceManageTimeZoneDetectorPermission();
if (testPrimaryProviderPackageName == null && testSecondaryProviderPackageName == null) {
throw new IllegalArgumentException("One or both test package names must be provided.");
}
mThreadingDomain.postAndWait(() -> {
synchronized (mSharedLock) {
stopOnDomainThread();
mServiceConfigAccessor.setTestPrimaryLocationTimeZoneProviderPackageName(
testPrimaryProviderPackageName);
mServiceConfigAccessor.setTestSecondaryLocationTimeZoneProviderPackageName(
testSecondaryProviderPackageName);
mServiceConfigAccessor.setRecordProviderStateChanges(recordProviderStateChanges);
startOnDomainThread();
}
}, BLOCKING_OP_WAIT_DURATION_MILLIS);
}
private void startOnDomainThread() {
mThreadingDomain.assertCurrentThread();
@@ -295,6 +313,9 @@ public class LocationTimeZoneManagerService extends Binder {
mLocationTimeZoneDetectorController = null;
mEnvironment.destroy();
mEnvironment = null;
// Clear test state so it won't be used the next time the service is started.
mServiceConfigAccessor.resetVolatileTestConfig();
}
}
}
@@ -307,14 +328,14 @@ public class LocationTimeZoneManagerService extends Binder {
this, in, out, err, args, callback, resultReceiver);
}
/** Sets this service into provider state recording mode for tests. */
void setProviderStateRecordingEnabled(boolean enabled) {
/** Clears recorded provider state for tests. */
void clearRecordedProviderStates() {
enforceManageTimeZoneDetectorPermission();
mThreadingDomain.postAndWait(() -> {
synchronized (mSharedLock) {
if (mLocationTimeZoneDetectorController != null) {
mLocationTimeZoneDetectorController.setProviderStateRecordingEnabled(enabled);
mLocationTimeZoneDetectorController.clearRecordedProviderStates();
}
}
}, BLOCKING_OP_WAIT_DURATION_MILLIS);
@@ -344,48 +365,6 @@ public class LocationTimeZoneManagerService extends Binder {
}
}
/**
* Passes a {@link TestCommand} to the specified provider and waits for the response.
*/
@NonNull
Bundle handleProviderTestCommand(@IntRange(from = 0, to = 1) int providerIndex,
@NonNull TestCommand testCommand) {
enforceManageTimeZoneDetectorPermission();
// Because this method blocks and posts work to the threading domain thread, it would cause
// a deadlock if it were called by the threading domain thread.
mThreadingDomain.assertNotCurrentThread();
AtomicReference<Bundle> resultReference = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
RemoteCallback remoteCallback = new RemoteCallback(x -> {
resultReference.set(x);
latch.countDown();
});
mThreadingDomain.post(() -> {
synchronized (mSharedLock) {
if (mLocationTimeZoneDetectorController == null) {
remoteCallback.sendResult(null);
return;
}
mLocationTimeZoneDetectorController.handleProviderTestCommand(
providerIndex, testCommand, remoteCallback);
}
});
try {
// Wait, but not indefinitely.
if (!latch.await(BLOCKING_OP_WAIT_DURATION_MILLIS, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Command did not complete in time");
}
} catch (InterruptedException e) {
throw new AssertionError(e);
}
return resultReference.get();
}
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
@Nullable String[] args) {
@@ -463,7 +442,8 @@ public class LocationTimeZoneManagerService extends Binder {
LocationTimeZoneProviderProxy proxy = createProxy();
ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(mIndex);
return new BinderLocationTimeZoneProvider(
providerMetricsLogger, mThreadingDomain, mName, proxy);
providerMetricsLogger, mThreadingDomain, mName, proxy,
mServiceConfigAccessor.getRecordProviderStateChanges());
}
@GuardedBy("mSharedLock")
@@ -476,9 +456,7 @@ public class LocationTimeZoneManagerService extends Binder {
@NonNull
private LocationTimeZoneProviderProxy createProxy() {
String mode = getMode();
if (Objects.equals(mode, PROVIDER_MODE_SIMULATED)) {
return new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
} else if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
return new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
} else {
// mode == PROVIDER_MODE_OVERRIDE_ENABLED (or unknown).
@@ -486,7 +464,7 @@ public class LocationTimeZoneManagerService extends Binder {
}
}
/** Returns the mode of the provider. */
/** Returns the mode of the provider (enabled/disabled). */
@NonNull
private String getMode() {
if (mIndex == 0) {
@@ -499,10 +477,19 @@ public class LocationTimeZoneManagerService extends Binder {
@NonNull
private RealLocationTimeZoneProviderProxy createRealProxy() {
String providerServiceAction = mServiceAction;
boolean isTestProvider = isTestProvider();
String providerPackageName = getPackageName();
return new RealLocationTimeZoneProviderProxy(
mContext, mHandler, mThreadingDomain, providerServiceAction,
providerPackageName);
providerPackageName, isTestProvider);
}
private boolean isTestProvider() {
if (mIndex == 0) {
return mServiceConfigAccessor.isTestPrimaryLocationTimeZoneProvider();
} else {
return mServiceConfigAccessor.isTestSecondaryLocationTimeZoneProvider();
}
}
@NonNull

View File

@@ -16,11 +16,12 @@
package com.android.server.timezonedetector.location;
import static android.app.time.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO;
import static android.app.time.LocationTimeZoneManager.NULL_PACKAGE_NAME_TOKEN;
import static android.app.time.LocationTimeZoneManager.SERVICE_NAME;
import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_CLEAR_RECORDED_PROVIDER_STATES;
import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_DUMP_STATE;
import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_RECORD_PROVIDER_STATES;
import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND;
import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_START;
import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_START_WITH_TEST_PROVIDERS;
import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_STOP;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
@@ -31,7 +32,6 @@ import static com.android.server.timedetector.ServerFlags.KEY_PRIMARY_LOCATION_T
import static com.android.server.timedetector.ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE;
import static com.android.server.timezonedetector.ServiceConfigAccessor.PROVIDER_MODE_DISABLED;
import static com.android.server.timezonedetector.ServiceConfigAccessor.PROVIDER_MODE_ENABLED;
import static com.android.server.timezonedetector.ServiceConfigAccessor.PROVIDER_MODE_SIMULATED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
@@ -41,12 +41,12 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_UNKNOWN;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.time.GeolocationTimeZoneSuggestionProto;
import android.app.time.LocationTimeZoneManagerProto;
import android.app.time.LocationTimeZoneManagerServiceStateProto;
import android.app.time.TimeZoneProviderStateProto;
import android.app.timezonedetector.TimeZoneDetector;
import android.os.Bundle;
import android.os.ShellCommand;
import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
@@ -79,14 +79,14 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
case SHELL_COMMAND_START: {
return runStart();
}
case SHELL_COMMAND_START_WITH_TEST_PROVIDERS: {
return runStartWithTestProviders();
}
case SHELL_COMMAND_STOP: {
return runStop();
}
case SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND: {
return runSendProviderTestCommand();
}
case SHELL_COMMAND_RECORD_PROVIDER_STATES: {
return runRecordProviderStates();
case SHELL_COMMAND_CLEAR_RECORDED_PROVIDER_STATES: {
return runClearRecordedProviderStates();
}
case SHELL_COMMAND_DUMP_STATE: {
return runDumpControllerState();
@@ -105,47 +105,33 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
pw.printf(" Print this help text.\n");
pw.printf(" %s\n", SHELL_COMMAND_START);
pw.printf(" Starts the service, creating location time zone providers.\n");
pw.printf(" %s <primary package name|%2$s> <secondary package name|%2$s>"
+ " <record states>\n",
SHELL_COMMAND_START_WITH_TEST_PROVIDERS, NULL_PACKAGE_NAME_TOKEN);
pw.printf(" Starts the service with test provider packages configured / provider"
+ " permission checks disabled.\n");
pw.printf(" <record states> - true|false, determines whether state recording is enabled."
+ "\n");
pw.printf(" See %s and %s.\n", SHELL_COMMAND_DUMP_STATE,
SHELL_COMMAND_CLEAR_RECORDED_PROVIDER_STATES);
pw.printf(" %s\n", SHELL_COMMAND_STOP);
pw.printf(" Stops the service, destroying location time zone providers.\n");
pw.printf(" %s (true|false)\n", SHELL_COMMAND_RECORD_PROVIDER_STATES);
pw.printf(" Enables / disables provider state recording mode. See also %s. The default"
+ " state is always \"false\".\n", SHELL_COMMAND_DUMP_STATE);
pw.printf(" Note: When enabled, this mode consumes memory and it is only intended for"
+ " testing.\n");
pw.printf(" It should be disabled after use, or the device can be rebooted to"
+ " reset the mode to disabled.\n");
pw.printf(" Disabling (or enabling repeatedly) clears any existing stored states.\n");
pw.printf(" %s\n", SHELL_COMMAND_CLEAR_RECORDED_PROVIDER_STATES);
pw.printf(" Clears recorded provider state. See also %s and %s.\n",
SHELL_COMMAND_START_WITH_TEST_PROVIDERS, SHELL_COMMAND_DUMP_STATE);
pw.printf(" Note: This is only intended for use during testing.\n");
pw.printf(" %s [%s]\n", SHELL_COMMAND_DUMP_STATE, DUMP_STATE_OPTION_PROTO);
pw.printf(" Dumps service state for tests as text or binary proto form.\n");
pw.printf(" See the LocationTimeZoneManagerServiceStateProto definition for details.\n");
pw.printf(" %s <provider index> <test command>\n",
SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND);
pw.printf(" Passes a test command to the named provider.\n");
pw.println();
pw.printf("<provider index> = 0 (primary), 1 (secondary)\n");
pw.println();
pw.printf("%s details:\n", SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND);
pw.println();
pw.printf("Provider <test command> encoding:\n");
pw.println();
TestCommand.printShellCommandEncodingHelp(pw);
pw.println();
pw.printf("Simulated provider mode can be used to test the system server behavior or to"
+ " reproduce bugs without the complexity of using real providers.\n");
pw.println();
pw.printf("The test commands for simulated providers are:\n");
SimulatedLocationTimeZoneProviderProxy.printTestCommandShellHelp(pw);
pw.println();
pw.printf("Test commands cannot currently be passed to real provider implementations.\n");
pw.println();
pw.printf("This service is also affected by the following device_config flags in the"
+ " %s namespace:\n", NAMESPACE_SYSTEM_TIME);
pw.printf(" %s\n", KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE);
pw.printf(" Overrides the mode of the primary provider. Values=%s|%s|%s\n",
PROVIDER_MODE_DISABLED, PROVIDER_MODE_ENABLED, PROVIDER_MODE_SIMULATED);
pw.printf(" Overrides the mode of the primary provider. Values=%s|%s\n",
PROVIDER_MODE_DISABLED, PROVIDER_MODE_ENABLED);
pw.printf(" %s\n", KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE);
pw.printf(" Overrides the mode of the secondary provider. Values=%s|%s|%s\n",
PROVIDER_MODE_DISABLED, PROVIDER_MODE_ENABLED, PROVIDER_MODE_SIMULATED);
pw.printf(" Overrides the mode of the secondary provider. Values=%s|%s\n",
PROVIDER_MODE_DISABLED, PROVIDER_MODE_ENABLED);
pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS);
pw.printf(" Sets the amount of time the service waits when uncertain before making an"
+ " 'uncertain' suggestion to the time zone detector.\n");
@@ -178,6 +164,23 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
return 0;
}
private int runStartWithTestProviders() {
String testPrimaryProviderPackageName = parseProviderPackageName(getNextArgRequired());
String testSecondaryProviderPackageName = parseProviderPackageName(getNextArgRequired());
boolean recordProviderStateChanges = Boolean.parseBoolean(getNextArgRequired());
try {
mService.startWithTestProviders(testPrimaryProviderPackageName,
testSecondaryProviderPackageName, recordProviderStateChanges);
} catch (RuntimeException e) {
reportError(e);
return 1;
}
PrintWriter outPrintWriter = getOutPrintWriter();
outPrintWriter.println("Service started (test mode)");
return 0;
}
private int runStop() {
try {
mService.stop();
@@ -190,20 +193,9 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
return 0;
}
private int runRecordProviderStates() {
PrintWriter outPrintWriter = getOutPrintWriter();
boolean enabled;
private int runClearRecordedProviderStates() {
try {
String nextArg = getNextArgRequired();
enabled = Boolean.parseBoolean(nextArg);
} catch (RuntimeException e) {
reportError(e);
return 1;
}
outPrintWriter.println("Setting provider state recording to " + enabled);
try {
mService.setProviderStateRecordingEnabled(enabled);
mService.clearRecordedProviderStates();
} catch (IllegalStateException e) {
reportError(e);
return 2;
@@ -293,47 +285,17 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
}
}
private int runSendProviderTestCommand() {
PrintWriter outPrintWriter = getOutPrintWriter();
int providerIndex;
TestCommand testCommand;
try {
providerIndex = parseProviderIndex(getNextArgRequired());
testCommand = createTestCommandFromNextShellArg();
} catch (RuntimeException e) {
reportError(e);
return 1;
}
outPrintWriter.println("Injecting testCommand=" + testCommand
+ " to providerIndex=" + providerIndex);
try {
Bundle result = mService.handleProviderTestCommand(providerIndex, testCommand);
outPrintWriter.println(result);
} catch (RuntimeException e) {
reportError(e);
return 2;
}
return 0;
}
@NonNull
private TestCommand createTestCommandFromNextShellArg() {
return TestCommand.createFromShellCommandArgs(this);
}
private void reportError(Throwable e) {
private void reportError(@NonNull Throwable e) {
PrintWriter errPrintWriter = getErrPrintWriter();
errPrintWriter.println("Error: ");
e.printStackTrace(errPrintWriter);
}
private static int parseProviderIndex(@NonNull String providerIndexString) {
int providerIndex = Integer.parseInt(providerIndexString);
if (providerIndex < 0 || providerIndex > 1) {
throw new IllegalArgumentException(providerIndexString);
@Nullable
private static String parseProviderPackageName(@NonNull String providerPackageNameString) {
if (providerPackageNameString.equals(NULL_PACKAGE_NAME_TOKEN)) {
return null;
}
return providerIndex;
return providerPackageNameString;
}
}

View File

@@ -16,9 +16,6 @@
package com.android.server.timezonedetector.location;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
@@ -35,9 +32,7 @@ import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteCallback;
import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
@@ -358,8 +353,7 @@ abstract class LocationTimeZoneProvider implements Dumpable {
/**
* Usually {@code false} but can be set to {@code true} for testing.
*/
@GuardedBy("mSharedLock")
private boolean mStateChangeRecording;
private final boolean mRecordStateChanges;
@GuardedBy("mSharedLock")
@NonNull
@@ -385,7 +379,8 @@ abstract class LocationTimeZoneProvider implements Dumpable {
LocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger,
@NonNull ThreadingDomain threadingDomain,
@NonNull String providerName,
@NonNull TimeZoneProviderEventPreProcessor timeZoneProviderEventPreProcessor) {
@NonNull TimeZoneProviderEventPreProcessor timeZoneProviderEventPreProcessor,
boolean recordStateChanges) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
mProviderMetricsLogger = Objects.requireNonNull(providerMetricsLogger);
mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue();
@@ -393,6 +388,7 @@ abstract class LocationTimeZoneProvider implements Dumpable {
mProviderName = Objects.requireNonNull(providerName);
mTimeZoneProviderEventPreProcessor =
Objects.requireNonNull(timeZoneProviderEventPreProcessor);
mRecordStateChanges = recordStateChanges;
}
/**
@@ -456,12 +452,11 @@ abstract class LocationTimeZoneProvider implements Dumpable {
abstract void onDestroy();
/**
* Sets the provider into state recording mode for tests.
* Clears recorded state changes.
*/
final void setStateChangeRecordingEnabled(boolean enabled) {
final void clearRecordedStates() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
mStateChangeRecording = enabled;
mRecordedStates.clear();
mRecordedStates.trimToSize();
}
@@ -478,12 +473,11 @@ abstract class LocationTimeZoneProvider implements Dumpable {
}
/**
* Set the current state, for use by this class and subclasses only. If {@code #notifyChanges}
* is {@code true} and {@code newState} is not equal to the old state, then {@link
* ProviderListener#onProviderStateChange(ProviderState)} must be called on
* {@link #mProviderListener}.
* Sets the current state. If {@code #notifyChanges} is {@code true} and {@code newState} is not
* equal to the old state, then {@link ProviderListener#onProviderStateChange(ProviderState)}
* will be called on {@link #mProviderListener}.
*/
final void setCurrentState(@NonNull ProviderState newState, boolean notifyChanges) {
private void setCurrentState(@NonNull ProviderState newState, boolean notifyChanges) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
ProviderState oldState = mCurrentState.get();
@@ -491,7 +485,7 @@ abstract class LocationTimeZoneProvider implements Dumpable {
onSetCurrentState(newState);
if (!Objects.equals(newState, oldState)) {
mProviderMetricsLogger.onProviderStateChanged(newState.stateEnum);
if (mStateChangeRecording) {
if (mRecordStateChanges) {
mRecordedStates.add(newState);
}
if (notifyChanges) {
@@ -609,23 +603,6 @@ abstract class LocationTimeZoneProvider implements Dumpable {
*/
abstract void onStopUpdates();
/**
* Overridden by subclasses to handle the supplied {@link TestCommand}. If {@code callback} is
* non-null, the default implementation sends a result {@link Bundle} with {@link
* android.service.timezone.TimeZoneProviderService#TEST_COMMAND_RESULT_SUCCESS_KEY} set to
* {@code false} and a "Not implemented" error message.
*/
void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) {
Objects.requireNonNull(testCommand);
if (callback != null) {
Bundle result = new Bundle();
result.putBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY, false);
result.putString(TEST_COMMAND_RESULT_ERROR_KEY, "Not implemented");
callback.sendResult(result);
}
}
/** For subclasses to invoke when a {@link TimeZoneProviderEvent} has been received. */
final void handleTimeZoneProviderEvent(@NonNull TimeZoneProviderEvent timeZoneProviderEvent) {
mThreadingDomain.assertCurrentThread();

View File

@@ -20,7 +20,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Handler;
import android.os.RemoteCallback;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
@@ -112,13 +111,6 @@ abstract class LocationTimeZoneProviderProxy implements Dumpable {
*/
abstract void setRequest(@NonNull TimeZoneProviderRequest request);
/**
* Processes the supplied test command. An optional callback can be supplied to listen for a
* response.
*/
abstract void handleTestCommand(@NonNull TestCommand testCommand,
@Nullable RemoteCallback callback);
/**
* Handles a {@link TimeZoneProviderEvent} from a remote process.
*/

View File

@@ -19,9 +19,6 @@ package com.android.server.timezonedetector.location;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteCallback;
import android.service.timezone.TimeZoneProviderService;
import android.util.IndentingPrintWriter;
/**
@@ -66,17 +63,6 @@ class NullLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
}
}
@Override
void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) {
if (callback != null) {
Bundle result = new Bundle();
result.putBoolean(TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY, false);
result.putString(TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY,
"Provider is disabled");
callback.sendResult(result);
}
}
@Override
public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
synchronized (mSharedLock) {

View File

@@ -18,16 +18,12 @@ package com.android.server.timezonedetector.location;
import static android.Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE;
import static android.Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.service.timezone.ITimeZoneProvider;
import android.service.timezone.ITimeZoneProviderManager;
import android.service.timezone.TimeZoneProviderSuggestion;
@@ -62,19 +58,27 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy im
RealLocationTimeZoneProviderProxy(
@NonNull Context context, @NonNull Handler handler,
@NonNull ThreadingDomain threadingDomain, @NonNull String action,
@NonNull String providerPackageName) {
@NonNull String providerPackageName, boolean isTestProvider) {
super(context, threadingDomain);
mManagerProxy = null;
mRequest = TimeZoneProviderRequest.createStopUpdatesRequest();
Objects.requireNonNull(providerPackageName);
mServiceWatcher = ServiceWatcher.create(context,
handler,
"RealLocationTimeZoneProviderProxy",
CurrentUserServiceSupplier.create(context, action,
providerPackageName, BIND_TIME_ZONE_PROVIDER_SERVICE,
INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE),
this);
CurrentUserServiceSupplier serviceSupplier;
if (isTestProvider) {
// For tests it is possible to bypass the provider service permission checks, since
// the tests are expected to install fake providers.
serviceSupplier = CurrentUserServiceSupplier.createUnsafeForTestsOnly(
context, action, providerPackageName, BIND_TIME_ZONE_PROVIDER_SERVICE,
/*servicePermission=*/null);
} else {
serviceSupplier = CurrentUserServiceSupplier.create(context, action,
providerPackageName, BIND_TIME_ZONE_PROVIDER_SERVICE,
INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE);
}
mServiceWatcher = ServiceWatcher.create(
context, handler, "RealLocationTimeZoneProviderProxy", serviceSupplier, this);
}
@Override
@@ -155,21 +159,6 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy im
});
}
/**
* A stubbed implementation.
*/
@Override
void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) {
mThreadingDomain.assertCurrentThread();
if (callback != null) {
Bundle result = new Bundle();
result.putBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY, false);
result.putString(TEST_COMMAND_RESULT_ERROR_KEY, "Not implemented");
callback.sendResult(result);
}
}
@Override
public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
synchronized (mSharedLock) {

View File

@@ -1,211 +0,0 @@
/*
* Copyright (C) 2020 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.timezonedetector.location;
import static android.app.time.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND;
import static android.app.time.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_ON_UNBIND;
import static android.app.time.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_PERM_FAILURE;
import static android.app.time.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS;
import static android.app.time.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ;
import static android.app.time.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteCallback;
import android.os.SystemClock;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
import com.android.server.timezonedetector.ReferenceWithHistory;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Objects;
/**
* A replacement for a real binder proxy for use during integration testing
* that can be used to inject simulated {@link LocationTimeZoneProviderProxy} behavior.
*/
class SimulatedLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
@GuardedBy("mSharedLock")
@NonNull private TimeZoneProviderRequest mRequest;
@GuardedBy("mSharedLock")
@NonNull private final ReferenceWithHistory<String> mLastEvent = new ReferenceWithHistory<>(50);
SimulatedLocationTimeZoneProviderProxy(
@NonNull Context context, @NonNull ThreadingDomain threadingDomain) {
super(context, threadingDomain);
mRequest = TimeZoneProviderRequest.createStopUpdatesRequest();
}
@Override
void onInitialize() {
// No-op - nothing to do for the simulated provider.
}
@Override
void onDestroy() {
// No-op - nothing to do for the simulated provider.
}
void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) {
mThreadingDomain.assertCurrentThread();
Objects.requireNonNull(testCommand);
synchronized (mSharedLock) {
Bundle resultBundle = new Bundle();
switch (testCommand.getName()) {
case SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND: {
mLastEvent.set("Simulating onProviderBound(), testCommand=" + testCommand);
mThreadingDomain.post(this::onBindOnHandlerThread);
resultBundle.putBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY, true);
break;
}
case SIMULATED_PROVIDER_TEST_COMMAND_ON_UNBIND: {
mLastEvent.set("Simulating onProviderUnbound(), testCommand=" + testCommand);
mThreadingDomain.post(this::onUnbindOnHandlerThread);
resultBundle.putBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY, true);
break;
}
case SIMULATED_PROVIDER_TEST_COMMAND_PERM_FAILURE:
case SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN:
case SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS: {
if (!mRequest.sendUpdates()) {
String errorMsg = "testCommand=" + testCommand
+ " is testing an invalid case:"
+ " updates are off. mRequest=" + mRequest;
mLastEvent.set(errorMsg);
resultBundle.putBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY, false);
resultBundle.putString(TEST_COMMAND_RESULT_ERROR_KEY, errorMsg);
break;
}
mLastEvent.set("Simulating TimeZoneProviderEvent, testCommand=" + testCommand);
TimeZoneProviderEvent timeZoneProviderEvent =
createTimeZoneProviderEventFromTestCommand(testCommand);
handleTimeZoneProviderEvent(timeZoneProviderEvent);
resultBundle.putBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY, true);
break;
}
default: {
String errorMsg = "Unknown test event type. testCommand=" + testCommand;
mLastEvent.set(errorMsg);
resultBundle.putBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY, false);
resultBundle.putString(TEST_COMMAND_RESULT_ERROR_KEY, errorMsg);
break;
}
}
if (callback != null) {
callback.sendResult(resultBundle);
}
}
}
private void onBindOnHandlerThread() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
mListener.onProviderBound();
}
}
private void onUnbindOnHandlerThread() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
mListener.onProviderUnbound();
}
}
@Override
final void setRequest(@NonNull TimeZoneProviderRequest request) {
mThreadingDomain.assertCurrentThread();
Objects.requireNonNull(request);
synchronized (mSharedLock) {
mLastEvent.set("Request received: " + request);
mRequest = request;
}
}
@Override
public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
synchronized (mSharedLock) {
ipw.println("{SimulatedLocationTimeZoneProviderProxy}");
ipw.println("mRequest=" + mRequest);
ipw.println("mLastEvent=" + mLastEvent);
ipw.increaseIndent();
ipw.println("Last event history:");
mLastEvent.dump(ipw);
ipw.decreaseIndent();
}
}
/**
* Prints the command line options that to create a {@link TestCommand} that can be passed to
* {@link #createTimeZoneProviderEventFromTestCommand(TestCommand)}.
*/
static void printTestCommandShellHelp(@NonNull PrintWriter pw) {
pw.printf("%s\n", SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND);
pw.printf("%s\n", SIMULATED_PROVIDER_TEST_COMMAND_ON_UNBIND);
pw.printf("%s\n", SIMULATED_PROVIDER_TEST_COMMAND_PERM_FAILURE);
pw.printf("%s\n", SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN);
pw.printf("%s %s=string_array:<time zone id>[&<time zone id>]+\n",
SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS,
SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ);
}
@NonNull
private static TimeZoneProviderEvent createTimeZoneProviderEventFromTestCommand(
@NonNull TestCommand testCommand) {
String name = testCommand.getName();
switch (name) {
case SIMULATED_PROVIDER_TEST_COMMAND_PERM_FAILURE: {
return TimeZoneProviderEvent.createPermanentFailureEvent("Simulated failure");
}
case SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN: {
return TimeZoneProviderEvent.createUncertainEvent();
}
case SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS: {
Bundle args = testCommand.getArgs();
String[] timeZoneIds = args.getStringArray(
SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ);
if (timeZoneIds == null) {
throw new IllegalArgumentException("No "
+ SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ + " arg found");
}
TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
.setTimeZoneIds(Arrays.asList(timeZoneIds))
.setElapsedRealtimeMillis(SystemClock.elapsedRealtime())
.build();
return TimeZoneProviderEvent.createSuggestionEvent(suggestion);
}
default: {
String msg = String.format("Error: Unknown command name %s", name);
throw new IllegalArgumentException(msg);
}
}
}
}

View File

@@ -1,194 +0,0 @@
/*
* Copyright (C) 2020 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.timezonedetector.location;
import android.annotation.NonNull;
import android.net.Uri;
import android.os.Bundle;
import android.os.ShellCommand;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A command used to trigger behaviors in a component during tests. Routing to the correct
* component is not handled by this class. The meaning of the {@code name} and {@code args}
* properties are component-specific.
*
* <p>{@link TestCommand}s can be encoded as arguments in a shell command. See
* {@link #createFromShellCommandArgs(ShellCommand)} and {@link
* #printShellCommandEncodingHelp(PrintWriter)}.
*/
final class TestCommand {
private static final Pattern SHELL_ARG_PATTERN = Pattern.compile("([^=]+)=([^:]+):(.*)");
private static final Pattern SHELL_ARG_VALUE_SPLIT_PATTERN = Pattern.compile("&");
@NonNull private final String mName;
@NonNull private final Bundle mArgs;
/** Creates a {@link TestCommand} from components. */
private TestCommand(@NonNull String type, @NonNull Bundle args) {
mName = Objects.requireNonNull(type);
mArgs = Objects.requireNonNull(args);
}
@VisibleForTesting
@NonNull
public static TestCommand createForTests(@NonNull String type, @NonNull Bundle args) {
return new TestCommand(type, args);
}
/**
* Creates a {@link TestCommand} from a {@link ShellCommand}'s remaining arguments.
*
* See {@link #printShellCommandEncodingHelp(PrintWriter)} for encoding details.
*/
@NonNull
public static TestCommand createFromShellCommandArgs(@NonNull ShellCommand shellCommand) {
String name = shellCommand.getNextArgRequired();
Bundle args = new Bundle();
String argKeyAndValue;
while ((argKeyAndValue = shellCommand.getNextArg()) != null) {
Matcher matcher = SHELL_ARG_PATTERN.matcher(argKeyAndValue);
if (!matcher.matches()) {
throw new IllegalArgumentException(
argKeyAndValue + " does not match " + SHELL_ARG_PATTERN);
}
String key = matcher.group(1);
String type = matcher.group(2);
String encodedValue = matcher.group(3);
Object value = getTypedValue(type, encodedValue);
args.putObject(key, value);
}
return new TestCommand(name, args);
}
/**
* Returns the command's name.
*/
@NonNull
public String getName() {
return mName;
}
/**
* Returns the arg values. Returns an empty bundle if there are no args.
*/
@NonNull
public Bundle getArgs() {
return mArgs.deepCopy();
}
@Override
public String toString() {
return "TestCommand{"
+ "mName=" + mName
+ ", mArgs=" + mArgs
+ '}';
}
/**
* Prints the text format that {@link #createFromShellCommandArgs(ShellCommand)} understands.
*/
public static void printShellCommandEncodingHelp(@NonNull PrintWriter pw) {
pw.println("Test commands are encoded on the command line as: <name> <arg>*");
pw.println();
pw.println("The <name> is a string");
pw.println("The <arg> encoding is: \"key=type:value\"");
pw.println();
pw.println("e.g. \"myKey=string:myValue\" represents an argument with the key \"myKey\""
+ " and a string value of \"myValue\"");
pw.println("Values are one or more URI-encoded strings separated by & characters. Only some"
+ " types support multiple values, e.g. string arrays.");
pw.println();
pw.println("Recognized types are: string, boolean, double, long, string_array.");
pw.println();
pw.println("When passing test commands via adb shell, the & can be escaped by quoting the"
+ " <arg> and escaping the & with \\");
pw.println("For example:");
pw.println(" $ adb shell ... my-command \"key1=string_array:value1\\&value2\"");
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TestCommand that = (TestCommand) o;
return mName.equals(that.mName)
&& mArgs.kindofEquals(that.mArgs);
}
@Override
public int hashCode() {
return Objects.hash(mName, mArgs);
}
private static Object getTypedValue(String type, String encodedValue) {
// The value is stored in a URL encoding. Multiple value types have values separated with
// a & character.
String[] values = SHELL_ARG_VALUE_SPLIT_PATTERN.split(encodedValue);
// URI decode the values.
for (int i = 0; i < values.length; i++) {
values[i] = Uri.decode(values[i]);
}
switch (type) {
case "boolean": {
checkSingleValue(values);
return Boolean.parseBoolean(values[0]);
}
case "double": {
checkSingleValue(values);
return Double.parseDouble(values[0]);
}
case "long": {
checkSingleValue(values);
return Long.parseLong(values[0]);
}
case "string": {
checkSingleValue(values);
return values[0];
}
case "string_array": {
return values;
}
default: {
throw new IllegalArgumentException("Unknown type: " + type);
}
}
}
private static void checkSingleValue(String[] values) {
if (values.length != 1) {
throw new IllegalArgumentException("Expected a single value, but there were multiple: "
+ Arrays.toString(values));
}
}
}

View File

@@ -1006,24 +1006,25 @@ public class ControllerImplTest {
@Test
public void stateRecording() {
// The test provider enables state recording by default.
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
// Initialize and check initial states.
controllerImpl.initialize(testEnvironment, mTestCallback);
{
LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
assertNull(state.getLastSuggestion());
assertTrue(state.getPrimaryProviderStates().isEmpty());
assertTrue(state.getSecondaryProviderStates().isEmpty());
assertProviderStates(state.getPrimaryProviderStates(),
PROVIDER_STATE_STOPPED, PROVIDER_STATE_STARTED_INITIALIZING);
assertProviderStates(state.getSecondaryProviderStates(), PROVIDER_STATE_STOPPED);
}
controllerImpl.clearRecordedProviderStates();
// State recording and simulate some provider behavior that will show up in the state
// recording.
controllerImpl.setProviderStateRecordingEnabled(true);
// Simulate some provider behavior that will show up in the state recording.
// Simulate an uncertain event from the primary. This will start the secondary.
mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -1032,19 +1033,14 @@ public class ControllerImplTest {
{
LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
assertNull(state.getLastSuggestion());
List<LocationTimeZoneProvider.ProviderState> primaryProviderStates =
state.getPrimaryProviderStates();
assertEquals(1, primaryProviderStates.size());
assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN,
primaryProviderStates.get(0).stateEnum);
List<LocationTimeZoneProvider.ProviderState> secondaryProviderStates =
state.getSecondaryProviderStates();
assertEquals(1, secondaryProviderStates.size());
assertEquals(PROVIDER_STATE_STARTED_INITIALIZING,
secondaryProviderStates.get(0).stateEnum);
assertProviderStates(
state.getPrimaryProviderStates(), PROVIDER_STATE_STARTED_UNCERTAIN);
assertProviderStates(
state.getSecondaryProviderStates(), PROVIDER_STATE_STARTED_INITIALIZING);
}
controllerImpl.clearRecordedProviderStates();
// Simulate an uncertain event from the primary. This will start the secondary.
// Simulate a certain event from the secondary.
mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
@@ -1052,23 +1048,27 @@ public class ControllerImplTest {
LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
state.getLastSuggestion().getZoneIds());
List<LocationTimeZoneProvider.ProviderState> primaryProviderStates =
state.getPrimaryProviderStates();
assertEquals(1, primaryProviderStates.size());
assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN, primaryProviderStates.get(0).stateEnum);
List<LocationTimeZoneProvider.ProviderState> secondaryProviderStates =
state.getSecondaryProviderStates();
assertEquals(2, secondaryProviderStates.size());
assertEquals(PROVIDER_STATE_STARTED_CERTAIN, secondaryProviderStates.get(1).stateEnum);
assertProviderStates(state.getPrimaryProviderStates());
assertProviderStates(
state.getSecondaryProviderStates(), PROVIDER_STATE_STARTED_CERTAIN);
}
controllerImpl.setProviderStateRecordingEnabled(false);
controllerImpl.clearRecordedProviderStates();
{
LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
state.getLastSuggestion().getZoneIds());
assertTrue(state.getPrimaryProviderStates().isEmpty());
assertTrue(state.getSecondaryProviderStates().isEmpty());
assertProviderStates(state.getPrimaryProviderStates());
assertProviderStates(state.getSecondaryProviderStates());
}
}
private static void assertProviderStates(
List<LocationTimeZoneProvider.ProviderState> providerStates,
int... expectedStates) {
assertEquals(expectedStates.length, providerStates.size());
for (int i = 0; i < expectedStates.length; i++) {
assertEquals(expectedStates[i], providerStates.get(i).stateEnum);
}
}
@@ -1228,7 +1228,7 @@ public class ControllerImplTest {
TestLocationTimeZoneProvider(ProviderMetricsLogger providerMetricsLogger,
ThreadingDomain threadingDomain, String providerName) {
super(providerMetricsLogger, threadingDomain, providerName,
new FakeTimeZoneProviderEventPreProcessor());
new FakeTimeZoneProviderEventPreProcessor(), true /* recordStateChanges */);
}
public void setFailDuringInitialization(boolean failInitialization) {

View File

@@ -15,9 +15,6 @@
*/
package com.android.server.timezonedetector.location;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
@@ -26,8 +23,6 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi
import static com.android.server.timezonedetector.location.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -36,8 +31,6 @@ import static java.util.Arrays.asList;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.RemoteCallback;
import android.platform.test.annotations.Presubmit;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
@@ -54,7 +47,6 @@ import java.time.Duration;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* Tests for {@link LocationTimeZoneProvider}.
@@ -168,27 +160,6 @@ public class LocationTimeZoneProviderTest {
provider.assertOnDestroyCalled();
}
@Test
public void defaultHandleTestCommandImpl() {
String providerName = "primary";
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
providerMetricsLogger,
mTestThreadingDomain,
providerName,
mTimeZoneProviderEventPreProcessor);
TestCommand testCommand = TestCommand.createForTests("test", new Bundle());
AtomicReference<Bundle> resultReference = new AtomicReference<>();
RemoteCallback callback = new RemoteCallback(resultReference::set);
provider.handleTestCommand(testCommand, callback);
Bundle result = resultReference.get();
assertNotNull(result);
assertFalse(result.getBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY));
assertNotNull(result.getString(TEST_COMMAND_RESULT_ERROR_KEY));
}
@Test
public void stateRecording() {
String providerName = "primary";
@@ -198,7 +169,6 @@ public class LocationTimeZoneProviderTest {
mTestThreadingDomain,
providerName,
mTimeZoneProviderEventPreProcessor);
provider.setStateChangeRecordingEnabled(true);
// initialize()
provider.initialize(mProviderListener);
@@ -244,7 +214,6 @@ public class LocationTimeZoneProviderTest {
mTestThreadingDomain,
providerName,
mTimeZoneProviderEventPreProcessor);
provider.setStateChangeRecordingEnabled(true);
provider.initialize(mProviderListener);
mTimeZoneProviderEventPreProcessor.enterUncertainMode();
@@ -315,8 +284,9 @@ public class LocationTimeZoneProviderTest {
@NonNull ThreadingDomain threadingDomain,
@NonNull String providerName,
@NonNull TimeZoneProviderEventPreProcessor timeZoneProviderEventPreProcessor) {
super(providerMetricsLogger,
threadingDomain, providerName, timeZoneProviderEventPreProcessor);
super(providerMetricsLogger, threadingDomain, providerName,
timeZoneProviderEventPreProcessor,
true /* recordStateChanges */);
}
@Override