Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c2e44a4a authored by Neil Fuller's avatar Neil Fuller Committed by Android (Google) Code Review
Browse files

Merge "Remove simulated LTZP and enable replacement"

parents ca06c654 9420adfd
Loading
Loading
Loading
Loading
+9 −38
Original line number Diff line number Diff line
@@ -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";

    /**
     * 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.
+132 −10
Original line number Diff line number Diff line
@@ -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);
+3 −13
Original line number Diff line number Diff line
@@ -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) {
+4 −32
Original line number Diff line number Diff line
@@ -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();
        }
    }

+50 −63
Original line number Diff line number Diff line
@@ -19,16 +19,13 @@ package com.android.server.timezonedetector.location;
import static android.app.time.LocationTimeZoneManager.SERVICE_NAME;

import static com.android.server.timezonedetector.ServiceConfigAccessor.PROVIDER_MODE_DISABLED;
import static com.android.server.timezonedetector.ServiceConfigAccessor.PROVIDER_MODE_SIMULATED;

import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteCallback;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.service.timezone.TimeZoneProviderService;
@@ -50,9 +47,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * A service class that acts as a container for the {@link LocationTimeZoneProviderController},
@@ -70,12 +64,6 @@ import java.util.concurrent.atomic.AtomicReference;
 * one indicated by {@link ThreadingDomain}. Because methods like {@link #dump} can be invoked on
 * another thread, the service and its related objects must still be thread-safe.
 *
 * <p>For testing / reproduction of bugs, it is possible to put providers into "simulation
 * mode" where the real binder clients are replaced by {@link
 * SimulatedLocationTimeZoneProviderProxy}. This means that the real client providers are never
 * bound (ensuring no real location events will be received) and simulated events / behaviors
 * can be injected via the command line.
 *
 * <p>See {@code adb shell cmd location_time_zone_manager help}" for details and more options.
 */
public class LocationTimeZoneManagerService extends Binder {
@@ -247,6 +235,36 @@ public class LocationTimeZoneManagerService extends Binder {
        }
    }

    /**
     * Starts the service with fake provider package names configured for tests. The config is
     * cleared when the service next stops.
     *
     * <p>Because this method posts work to the {@code mThreadingDomain} thread and waits for
     * completion, it cannot be called from the {@code mThreadingDomain} thread.
     */
    void startWithTestProviders(@Nullable String testPrimaryProviderPackageName,
            @Nullable String testSecondaryProviderPackageName,
            boolean recordProviderStateChanges) {
        enforceManageTimeZoneDetectorPermission();

        if (testPrimaryProviderPackageName == null && testSecondaryProviderPackageName == null) {
            throw new IllegalArgumentException("One or both test package names must be provided.");
        }

        mThreadingDomain.postAndWait(() -> {
            synchronized (mSharedLock) {
                stopOnDomainThread();

                mServiceConfigAccessor.setTestPrimaryLocationTimeZoneProviderPackageName(
                        testPrimaryProviderPackageName);
                mServiceConfigAccessor.setTestSecondaryLocationTimeZoneProviderPackageName(
                        testSecondaryProviderPackageName);
                mServiceConfigAccessor.setRecordProviderStateChanges(recordProviderStateChanges);
                startOnDomainThread();
            }
        }, BLOCKING_OP_WAIT_DURATION_MILLIS);
    }

    private void startOnDomainThread() {
        mThreadingDomain.assertCurrentThread();

@@ -295,6 +313,9 @@ public class LocationTimeZoneManagerService extends Binder {
                mLocationTimeZoneDetectorController = null;
                mEnvironment.destroy();
                mEnvironment = null;

                // Clear test state so it won't be used the next time the service is started.
                mServiceConfigAccessor.resetVolatileTestConfig();
            }
        }
    }
@@ -307,14 +328,14 @@ public class LocationTimeZoneManagerService extends Binder {
                this, in, out, err, args, callback, resultReceiver);
    }

    /** Sets this service into provider state recording mode for tests. */
    void setProviderStateRecordingEnabled(boolean enabled) {
    /** Clears recorded provider state for tests. */
    void clearRecordedProviderStates() {
        enforceManageTimeZoneDetectorPermission();

        mThreadingDomain.postAndWait(() -> {
            synchronized (mSharedLock) {
                if (mLocationTimeZoneDetectorController != null) {
                    mLocationTimeZoneDetectorController.setProviderStateRecordingEnabled(enabled);
                    mLocationTimeZoneDetectorController.clearRecordedProviderStates();
                }
            }
        }, BLOCKING_OP_WAIT_DURATION_MILLIS);
@@ -344,48 +365,6 @@ public class LocationTimeZoneManagerService extends Binder {
        }
    }

    /**
     * Passes a {@link TestCommand} to the specified provider and waits for the response.
     */
    @NonNull
    Bundle handleProviderTestCommand(@IntRange(from = 0, to = 1) int providerIndex,
            @NonNull TestCommand testCommand) {
        enforceManageTimeZoneDetectorPermission();

        // Because this method blocks and posts work to the threading domain thread, it would cause
        // a deadlock if it were called by the threading domain thread.
        mThreadingDomain.assertNotCurrentThread();

        AtomicReference<Bundle> resultReference = new AtomicReference<>();
        CountDownLatch latch = new CountDownLatch(1);
        RemoteCallback remoteCallback = new RemoteCallback(x -> {
            resultReference.set(x);
            latch.countDown();
        });

        mThreadingDomain.post(() -> {
            synchronized (mSharedLock) {
                if (mLocationTimeZoneDetectorController == null) {
                    remoteCallback.sendResult(null);
                    return;
                }
                mLocationTimeZoneDetectorController.handleProviderTestCommand(
                        providerIndex, testCommand, remoteCallback);
            }
        });

        try {
            // Wait, but not indefinitely.
            if (!latch.await(BLOCKING_OP_WAIT_DURATION_MILLIS, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Command did not complete in time");
            }
        } catch (InterruptedException e) {
            throw new AssertionError(e);
        }

        return resultReference.get();
    }

    @Override
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
            @Nullable String[] args) {
@@ -463,7 +442,8 @@ public class LocationTimeZoneManagerService extends Binder {
            LocationTimeZoneProviderProxy proxy = createProxy();
            ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(mIndex);
            return new BinderLocationTimeZoneProvider(
                    providerMetricsLogger, mThreadingDomain, mName, proxy);
                    providerMetricsLogger, mThreadingDomain, mName, proxy,
                    mServiceConfigAccessor.getRecordProviderStateChanges());
        }

        @GuardedBy("mSharedLock")
@@ -476,9 +456,7 @@ public class LocationTimeZoneManagerService extends Binder {
        @NonNull
        private LocationTimeZoneProviderProxy createProxy() {
            String mode = getMode();
            if (Objects.equals(mode, PROVIDER_MODE_SIMULATED)) {
                return new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
            } else if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
            if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
                return new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
            } else {
                // mode == PROVIDER_MODE_OVERRIDE_ENABLED (or unknown).
@@ -486,7 +464,7 @@ public class LocationTimeZoneManagerService extends Binder {
            }
        }

        /** Returns the mode of the provider. */
        /** Returns the mode of the provider (enabled/disabled). */
        @NonNull
        private String getMode() {
            if (mIndex == 0) {
@@ -499,10 +477,19 @@ public class LocationTimeZoneManagerService extends Binder {
        @NonNull
        private RealLocationTimeZoneProviderProxy createRealProxy() {
            String providerServiceAction = mServiceAction;
            boolean isTestProvider = isTestProvider();
            String providerPackageName = getPackageName();
            return new RealLocationTimeZoneProviderProxy(
                    mContext, mHandler, mThreadingDomain, providerServiceAction,
                    providerPackageName);
                    providerPackageName, isTestProvider);
        }

        private boolean isTestProvider() {
            if (mIndex == 0) {
                return mServiceConfigAccessor.isTestPrimaryLocationTimeZoneProvider();
            } else {
                return mServiceConfigAccessor.isTestSecondaryLocationTimeZoneProvider();
            }
        }

        @NonNull
Loading