Loading core/java/android/app/time/LocationTimeZoneManager.java +9 −38 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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"; /** * Simulated provider test command that simulates the bind succeeding. */ 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"; /** 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 provider entering the "uncertain" * state. * 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_UNCERTAIN = "uncertain"; public static final String NULL_PACKAGE_NAME_TOKEN = "@null"; private LocationTimeZoneManager() { // No need to instantiate. Loading services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java +132 −10 Original line number Diff line number Diff line Loading @@ -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()}. Loading Loading @@ -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); Loading Loading @@ -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()); Loading @@ -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()); Loading Loading @@ -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); Loading services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java +3 −13 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } Loading Loading @@ -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) { Loading services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java +4 −32 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); } } Loading services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java +50 −63 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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}, Loading @@ -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 { Loading Loading @@ -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(); Loading Loading @@ -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(); } } } Loading @@ -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); Loading Loading @@ -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) { Loading Loading @@ -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") Loading @@ -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). Loading @@ -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) { Loading @@ -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 Loading Loading
core/java/android/app/time/LocationTimeZoneManager.java +9 −38 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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"; /** * Simulated provider test command that simulates the bind succeeding. */ 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"; /** 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 provider entering the "uncertain" * state. * 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_UNCERTAIN = "uncertain"; public static final String NULL_PACKAGE_NAME_TOKEN = "@null"; private LocationTimeZoneManager() { // No need to instantiate. Loading
services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java +132 −10 Original line number Diff line number Diff line Loading @@ -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()}. Loading Loading @@ -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); Loading Loading @@ -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()); Loading @@ -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()); Loading Loading @@ -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); Loading
services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java +3 −13 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } Loading Loading @@ -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) { Loading
services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java +4 −32 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); } } Loading
services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java +50 −63 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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}, Loading @@ -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 { Loading Loading @@ -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(); Loading Loading @@ -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(); } } } Loading @@ -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); Loading Loading @@ -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) { Loading Loading @@ -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") Loading @@ -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). Loading @@ -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) { Loading @@ -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 Loading