Remove simulated LTZP and enable replacement
Remove "simulated" location time zone provider mode and enable "fake" location time zone providers. This allows for more realistic testing with binder-based LTZPs. Many of the changes in this commit are the removal of command-line infra and test logic associated with "simulated" mode. The new "start_with_test_providers" command is required to enable changes to how providers are discovered, since test providers are not found on the system image and will not have the full permissions expected (which are requirements of "real" providers). As part of this change "state recording", something only used in tests, can only be turned on by "start_with_test_providers". A "clear_recorded_provider_states" has been added to enable tests to clear the recorded state. The new test mode config deliberately does not survive a reboot. Bug: 188780558 Bug: 184947690 Test: atest CtsLocationTimeZoneManagerHostTest Test: atest com.android.server.timezonedetector Change-Id: I7d8fc217fa070aee791fb376efb398057320503a
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user