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

Commit 9e34ba7c authored by Neil Fuller's avatar Neil Fuller
Browse files

Add shell commands and other support for host CTS

Host CTS tests are needed for metrics approval. The added shell commands
enable tests to assert LocationTimeZoneManager behavior / internal
states.

location_time_zone_manager:

The location time zone manager can be set into a mode where it records
provider state changes. It can also dump its state on request as a
proto. This will enable the impact of commands made during tests to be
asserted.

Example usages:

A command to start recording state changes:

$ adb shell cmd location_time_zone_manager record_provider_states true

A command to dump the manager's state:

$ adb shell cmd location_time_zone_manager dump_state

(--proto can be used to obtain a binary proto representation of the
state, easier for parsing / manipulating during tests than text output).

This commit also switches to using shell commands to set providers into
"simulated" and "disabled" mode. Previously this was handled with system
properties (requiring an -eng build) and a reboot to take effect. Now
it is achieved via the set_provider_mode_override shell command, is
non-persistent, and it leverages the new shell commands that can be used
to stop / start the location_time_zone_manager service.

time_zone_detector:

New commands have been added to query and modify higher-level time
zone-related settings for host tests:

+ is_auto_detection_enabled
+ set_auto_detection_enabled
+ is_geo_detection_supported
+ is_location_enabled
+ is_geo_detection_enabled
+ set_geo_detection_enabled

See also for help:

$ adb shell cmd location_time_zone_manager
$ adb shell cmd time_zone_detector

Bug: 172934905
Test: atest services/tests/servicestests/src/com/android/server/location/timezone/
Test: atest CtsLocationTimeZoneManagerHostTest
Change-Id: I318d82f292f48fa5bb106f84104c4856f7ef6e1c
parent 1d30d73d
Loading
Loading
Loading
Loading
+44 −32
Original line number Diff line number Diff line
@@ -40,17 +40,58 @@ public final class LocationTimeZoneManager {
    public static final String SHELL_COMMAND_SERVICE_NAME = "location_time_zone_manager";

    /**
     * Shell command that starts the service (after stop).
     * A shell command that starts the service (after stop).
     */
    public static final String SHELL_COMMAND_START = "start";

    /**
     * Shell command that stops the service.
     * A shell command that stops the service.
     */
    public static final String SHELL_COMMAND_STOP = "stop";

    /**
     * Shell command that sends test commands to a provider
     * A shell command that can put providers into different modes. Takes effect next time the
     * service is started.
     */
    public static final String SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE =
            "set_provider_mode_override";

    /**
     * The default provider mode.
     * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
     */
    public static final String PROVIDER_MODE_OVERRIDE_NONE = "none";

    /**
     * The "simulated" provider mode.
     * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
     */
    public static final String PROVIDER_MODE_OVERRIDE_SIMULATED = "simulated";

    /**
     * The "disabled" provider mode (equivalent to there being no provider configured).
     * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
     */
    public static final String PROVIDER_MODE_OVERRIDE_DISABLED = "disabled";

    /**
     * A shell command that tells the service to record state information during tests. The next
     * argument value is "true" or "false".
     */
    public static final String SHELL_COMMAND_RECORD_PROVIDER_STATES = "record_provider_states";

    /**
     * A shell command that tells the service to dump its current state.
     */
    public static final String SHELL_COMMAND_DUMP_STATE = "dump_state";

    /**
     * Option for {@link #SHELL_COMMAND_DUMP_STATE} that tells it to dump state as a binary proto.
     */
    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";
@@ -88,35 +129,6 @@ public final class LocationTimeZoneManager {
     */
    public static final String SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN = "uncertain";

    private static final String SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PREFIX =
            "persist.sys.geotz.";

    /**
     * The name of the system property that can be used to set the primary provider into test mode
     * (value = {@link #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED}) or disabled (value = {@link
     * #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED}).
     */
    public static final String SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY =
            SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PREFIX + PRIMARY_PROVIDER_NAME;

    /**
     * The name of the system property that can be used to set the secondary provider into test mode
     * (value = {@link #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED}) or disabled (value = {@link
     * #SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED}).
     */
    public static final String SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY =
            SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PREFIX + SECONDARY_PROVIDER_NAME;

    /**
     * The value of the provider mode system property to put a provider into test mode.
     */
    public static final String SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED = "simulated";

    /**
     * The value of the provider mode system property to put a provider into disabled mode.
     */
    public static final String SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED = "disabled";

    private LocationTimeZoneManager() {
        // No need to instantiate.
    }
+61 −3
Original line number Diff line number Diff line
@@ -29,11 +29,69 @@ import android.content.Context;
@SystemService(Context.TIME_ZONE_DETECTOR_SERVICE)
public interface TimeZoneDetector {

    /** @hide */
    /**
     * The name of the service for shell commands.
     * @hide
     */
    String SHELL_COMMAND_SERVICE_NAME = "time_zone_detector";

    /**
     * A shell command that prints the current "auto time zone detection" global setting value.
     * @hide
     */
    String SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED = "is_auto_detection_enabled";

    /**
     * A shell command that sets the current "auto time zone detection" global setting value.
     * @hide
     */
    String SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED = "set_auto_detection_enabled";

    /**
     * A shell command that prints whether the geolocation-based time zone detection feature is
     * supported on the device.
     * @hide
     */
    String SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED = "is_geo_detection_supported";

    /**
     * A shell command that prints the current user's "location enabled" setting.
     * @hide
     */
    String SHELL_COMMAND_IS_LOCATION_ENABLED = "is_location_enabled";

    /**
     * A shell command that prints the current user's "location-based time zone detection enabled"
     * setting.
     * @hide
     */
    String SHELL_COMMAND_IS_GEO_DETECTION_ENABLED = "is_geo_detection_enabled";

    /**
     * A shell command that sets the current user's "location-based time zone detection enabled"
     * setting.
     * @hide
     */
    String SHELL_COMMAND_SET_GEO_DETECTION_ENABLED = "set_geo_detection_enabled";

    /**
     * A shell command that injects a geolocation time zone suggestion (as if from the
     * location_time_zone_manager).
     * @hide
     */
    String SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE = "suggest_geo_location_time_zone";
    /** @hide */

    /**
     * A shell command that injects a manual time zone suggestion (as if from the SettingsUI or
     * similar).
     * @hide
     */
    String SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE = "suggest_manual_time_zone";
    /** @hide */

    /**
     * A shell command that injects a telephony time zone suggestion (as if from the phone app).
     * @hide
     */
    String SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE = "suggest_telephony_time_zone";

    /**
+32 −0
Original line number Diff line number Diff line
@@ -610,6 +610,38 @@ class ControllerImpl extends LocationTimeZoneProviderController {
        }
    }

    /**
     * Sets whether the controller should record provider state changes for later dumping via
     * {@link #getStateForTests()}.
     */
    void setProviderStateRecordingEnabled(boolean enabled) {
        mThreadingDomain.assertCurrentThread();

        synchronized (mSharedLock) {
            mPrimaryProvider.setStateChangeRecordingEnabled(enabled);
            mSecondaryProvider.setStateChangeRecordingEnabled(enabled);
        }
    }

    /**
     * Returns a snapshot of the current controller state for tests.
     */
    @NonNull
    LocationTimeZoneManagerServiceState getStateForTests() {
        mThreadingDomain.assertCurrentThread();

        synchronized (mSharedLock) {
            LocationTimeZoneManagerServiceState.Builder builder =
                    new LocationTimeZoneManagerServiceState.Builder();
            if (mLastSuggestion != null) {
                builder.setLastSuggestion(mLastSuggestion);
            }
            builder.setPrimaryProviderStateChanges(mPrimaryProvider.getRecordedStates())
                    .setSecondaryProviderStateChanges(mSecondaryProvider.getRecordedStates());
            return builder.build();
        }
    }

    @Nullable
    private LocationTimeZoneProvider getLocationTimeZoneProvider(@NonNull String providerName) {
        LocationTimeZoneProvider targetProvider;
+84 −25
Original line number Diff line number Diff line
@@ -17,11 +17,10 @@
package com.android.server.location.timezone;

import static android.app.time.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME;
import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DISABLED;
import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE;
import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED;
import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME;
import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY;
import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY;
import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED;
import static android.app.time.LocationTimeZoneManager.SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,7 +32,6 @@ import android.os.Handler;
import android.os.RemoteCallback;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.service.timezone.TimeZoneProviderService;
import android.util.IndentingPrintWriter;
import android.util.Log;
@@ -42,6 +40,7 @@ import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.SystemService;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
@@ -161,6 +160,14 @@ public class LocationTimeZoneManagerService extends Binder {
    @GuardedBy("mSharedLock")
    private ControllerEnvironmentImpl mEnvironment;

    @GuardedBy("mSharedLock")
    @NonNull
    private String mPrimaryProviderModeOverride = PROVIDER_MODE_OVERRIDE_NONE;

    @GuardedBy("mSharedLock")
    @NonNull
    private String mSecondaryProviderModeOverride = PROVIDER_MODE_OVERRIDE_NONE;

    LocationTimeZoneManagerService(Context context) {
        mContext = context.createAttributionContext(ATTRIBUTION_TAG);
        mHandler = FgThread.getHandler();
@@ -231,9 +238,9 @@ public class LocationTimeZoneManagerService extends Binder {

    private LocationTimeZoneProvider createPrimaryProvider() {
        LocationTimeZoneProviderProxy proxy;
        if (isInSimulationMode(PRIMARY_PROVIDER_NAME)) {
        if (isProviderInSimulationMode(PRIMARY_PROVIDER_NAME)) {
            proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
        } else if (isDisabled(PRIMARY_PROVIDER_NAME)) {
        } else if (isProviderDisabled(PRIMARY_PROVIDER_NAME)) {
            proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
        } else {
            proxy = new RealLocationTimeZoneProviderProxy(
@@ -250,9 +257,9 @@ public class LocationTimeZoneManagerService extends Binder {

    private LocationTimeZoneProvider createSecondaryProvider() {
        LocationTimeZoneProviderProxy proxy;
        if (isInSimulationMode(SECONDARY_PROVIDER_NAME)) {
        if (isProviderInSimulationMode(SECONDARY_PROVIDER_NAME)) {
            proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
        } else if (isDisabled(SECONDARY_PROVIDER_NAME)) {
        } else if (isProviderDisabled(SECONDARY_PROVIDER_NAME)) {
            proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
        } else {
            proxy = new RealLocationTimeZoneProviderProxy(
@@ -268,16 +275,14 @@ public class LocationTimeZoneManagerService extends Binder {
    }

    /** Used for bug triage and in tests to simulate provider events. */
    private static boolean isInSimulationMode(String providerName) {
        return isProviderModeSetInSystemProperties(providerName,
                SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED);
    private boolean isProviderInSimulationMode(String providerName) {
        return isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_SIMULATED);
    }

    /** Used for bug triage, tests and experiments to remove a provider. */
    private boolean isDisabled(String providerName) {
    private boolean isProviderDisabled(String providerName) {
        return !isProviderEnabledInConfig(providerName)
                || isProviderModeSetInSystemProperties(
                        providerName, SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED);
                || isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_DISABLED);
    }

    private boolean isProviderEnabledInConfig(String providerName) {
@@ -299,25 +304,18 @@ public class LocationTimeZoneManagerService extends Binder {
        return resources.getBoolean(providerEnabledConfigId);
    }

    private static boolean isProviderModeSetInSystemProperties(
            @NonNull String providerName, @NonNull String mode) {
        String systemPropertyKey;
    private boolean isProviderModeOverrideSet(@NonNull String providerName, @NonNull String mode) {
        switch (providerName) {
            case PRIMARY_PROVIDER_NAME: {
                systemPropertyKey = SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY;
                break;
                return Objects.equals(mPrimaryProviderModeOverride, mode);
            }
            case SECONDARY_PROVIDER_NAME: {
                systemPropertyKey = SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY;
                break;
                return Objects.equals(mSecondaryProviderModeOverride, mode);
            }
            default: {
                throw new IllegalArgumentException(providerName);
            }
        }

        String systemPropertyProviderMode = SystemProperties.get(systemPropertyKey, null);
        return Objects.equals(systemPropertyProviderMode, mode);
    }

    /**
@@ -347,6 +345,67 @@ public class LocationTimeZoneManagerService extends Binder {
                this, in, out, err, args, callback, resultReceiver);
    }

    /** Sets this service into provider state recording mode for tests. */
    void setProviderModeOverride(@NonNull String providerName, @NonNull String mode) {
        enforceManageTimeZoneDetectorPermission();

        Preconditions.checkArgument(
                PRIMARY_PROVIDER_NAME.equals(providerName)
                        || SECONDARY_PROVIDER_NAME.equals(providerName));
        Preconditions.checkArgument(PROVIDER_MODE_OVERRIDE_DISABLED.equals(mode)
                || PROVIDER_MODE_OVERRIDE_SIMULATED.equals(mode)
                || PROVIDER_MODE_OVERRIDE_NONE.equals(mode));

        mThreadingDomain.postAndWait(() -> {
            synchronized (mSharedLock) {
                switch (providerName) {
                    case PRIMARY_PROVIDER_NAME: {
                        mPrimaryProviderModeOverride = mode;
                        break;
                    }
                    case SECONDARY_PROVIDER_NAME: {
                        mSecondaryProviderModeOverride = mode;
                        break;
                    }
                }
            }
        }, BLOCKING_OP_WAIT_DURATION_MILLIS);
    }

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

        mThreadingDomain.postAndWait(() -> {
            synchronized (mSharedLock) {
                if (mLocationTimeZoneDetectorController != null) {
                    mLocationTimeZoneDetectorController.setProviderStateRecordingEnabled(enabled);
                }
            }
        }, BLOCKING_OP_WAIT_DURATION_MILLIS);
    }

    /** Returns a snapshot of the current controller state for tests. */
    @NonNull
    LocationTimeZoneManagerServiceState getStateForTests() {
        enforceManageTimeZoneDetectorPermission();

        try {
            return mThreadingDomain.postAndWait(
                    () -> {
                        synchronized (mSharedLock) {
                            if (mLocationTimeZoneDetectorController == null) {
                                return null;
                            }
                            return mLocationTimeZoneDetectorController.getStateForTests();
                        }
                    },
                    BLOCKING_OP_WAIT_DURATION_MILLIS);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Passes a {@link TestCommand} to the specified provider and waits for the response.
     */
+97 −0
Original line number Diff line number Diff line
/*
 * 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.location.timezone;

import android.annotation.NonNull;
import android.annotation.Nullable;

import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/** A snapshot of the location time zone manager service's state for tests. */
final class LocationTimeZoneManagerServiceState {

    @Nullable private final GeolocationTimeZoneSuggestion mLastSuggestion;
    @NonNull private final List<ProviderState> mPrimaryProviderStates;
    @NonNull private final List<ProviderState> mSecondaryProviderStates;

    LocationTimeZoneManagerServiceState(@NonNull Builder builder) {
        mLastSuggestion = builder.mLastSuggestion;
        mPrimaryProviderStates = Objects.requireNonNull(builder.mPrimaryProviderStates);
        mSecondaryProviderStates = Objects.requireNonNull(builder.mSecondaryProviderStates);
    }

    @Nullable
    public GeolocationTimeZoneSuggestion getLastSuggestion() {
        return mLastSuggestion;
    }

    @NonNull
    public List<ProviderState> getPrimaryProviderStates() {
        return Collections.unmodifiableList(mPrimaryProviderStates);
    }

    @NonNull
    public List<ProviderState> getSecondaryProviderStates() {
        return Collections.unmodifiableList(mSecondaryProviderStates);
    }

    @Override
    public String toString() {
        return "LocationTimeZoneManagerServiceState{"
                + "mLastSuggestion=" + mLastSuggestion
                + ", mPrimaryProviderStates=" + mPrimaryProviderStates
                + ", mSecondaryProviderStates=" + mSecondaryProviderStates
                + '}';
    }

    static final class Builder {

        private GeolocationTimeZoneSuggestion mLastSuggestion;
        private List<ProviderState> mPrimaryProviderStates;
        private List<ProviderState> mSecondaryProviderStates;

        @NonNull
        Builder setLastSuggestion(@NonNull GeolocationTimeZoneSuggestion lastSuggestion) {
            mLastSuggestion = Objects.requireNonNull(lastSuggestion);
            return this;
        }

        @NonNull
        Builder setPrimaryProviderStateChanges(@NonNull List<ProviderState> primaryProviderStates) {
            mPrimaryProviderStates = new ArrayList<>(primaryProviderStates);
            return this;
        }

        @NonNull
        Builder setSecondaryProviderStateChanges(
                @NonNull List<ProviderState> secondaryProviderStates) {
            mSecondaryProviderStates = new ArrayList<>(secondaryProviderStates);
            return this;
        }

        @NonNull
        LocationTimeZoneManagerServiceState build() {
            return new LocationTimeZoneManagerServiceState(this);
        }
    }
}
Loading