diff --git a/core/java/android/app/time/LocationTimeZoneManager.java b/core/java/android/app/time/LocationTimeZoneManager.java index f506f12a7ba03..17e9e939f03b1 100644 --- a/core/java/android/app/time/LocationTimeZoneManager.java +++ b/core/java/android/app/time/LocationTimeZoneManager.java @@ -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. diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java index 2be8e355b0161..58281306c0859 100644 --- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java @@ -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); diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java index 59df6ff3582e8..9d340e4fde86f 100644 --- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java @@ -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) { diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java index b1019f3fa4295..76ef958baf220 100644 --- a/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java @@ -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(); } } diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java index d8d44d47be624..8dbc520c583cc 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java @@ -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. * - *

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. - * *

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. + * + *

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 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 diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java index 0f0de5004be9b..3488956af5715 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java @@ -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 " + + " \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(" - 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 \n", - SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND); - pw.printf(" Passes a test command to the named provider.\n"); - pw.println(); - pw.printf(" = 0 (primary), 1 (secondary)\n"); - pw.println(); - pw.printf("%s details:\n", SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND); - pw.println(); - pw.printf("Provider 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; } } diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java index c0fd6b1d79fad..4e878333fe66d 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java @@ -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(); diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java index 43b1b5f017b21..7b1a77ce77d50 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java @@ -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. */ diff --git a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java index 1f45e828aad4c..4ef819fdee217 100644 --- a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java @@ -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) { diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java index b7ff733eabbc8..fcac3e8569132 100644 --- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java @@ -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) { diff --git a/services/core/java/com/android/server/timezonedetector/location/SimulatedLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/SimulatedLocationTimeZoneProviderProxy.java deleted file mode 100644 index 02b0a849c1b13..0000000000000 --- a/services/core/java/com/android/server/timezonedetector/location/SimulatedLocationTimeZoneProviderProxy.java +++ /dev/null @@ -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 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:

{@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: *"); - pw.println(); - pw.println("The is a string"); - pw.println("The 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" - + " 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)); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java index 036362540bf1c..da746ca46def6 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java @@ -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 primaryProviderStates = - state.getPrimaryProviderStates(); - assertEquals(1, primaryProviderStates.size()); - assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN, - primaryProviderStates.get(0).stateEnum); - List 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 primaryProviderStates = - state.getPrimaryProviderStates(); - assertEquals(1, primaryProviderStates.size()); - assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN, primaryProviderStates.get(0).stateEnum); - List 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 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) { diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java index 0edb559b04b31..03d56c782b591 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java @@ -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 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