Merge "Add shell commands and other support for host CTS"

This commit is contained in:
TreeHugger Robot
2021-01-08 21:23:29 +00:00
committed by Android (Google) Code Review
11 changed files with 742 additions and 82 deletions

View File

@@ -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.
}

View File

@@ -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";
/**

View File

@@ -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;

View File

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

View File

@@ -0,0 +1,97 @@
/*
* 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);
}
}
}

View File

@@ -15,23 +15,46 @@
*/
package com.android.server.location.timezone;
import static android.app.time.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO;
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.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_SET_PROVIDER_MODE_OVERRIDE;
import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_START;
import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_STOP;
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 static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_UNKNOWN;
import android.annotation.NonNull;
import android.app.time.GeolocationTimeZoneSuggestionProto;
import android.app.time.LocationTimeZoneManagerProto;
import android.app.time.LocationTimeZoneManagerServiceStateProto;
import android.app.time.TimeZoneProviderStateProto;
import android.os.Bundle;
import android.os.ShellCommand;
import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/** Implements the shell command interface for {@link LocationTimeZoneManagerService}. */
class LocationTimeZoneManagerShellCommand extends ShellCommand {
@@ -58,9 +81,18 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
case SHELL_COMMAND_STOP: {
return runStop();
}
case SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE: {
return runSetProviderModeOverride();
}
case SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND: {
return runSendProviderTestCommand();
}
case SHELL_COMMAND_RECORD_PROVIDER_STATES: {
return runRecordProviderStates();
}
case SHELL_COMMAND_DUMP_STATE: {
return runDumpControllerState();
}
default: {
return handleDefaultCommands(cmd);
}
@@ -70,35 +102,42 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
pw.println("Location Time Zone Manager (location_time_zone_manager) commands:");
pw.println("Location Time Zone Manager (location_time_zone_manager) commands for tests:");
pw.println(" help");
pw.println(" Print this help text.");
pw.printf(" %s\n", SHELL_COMMAND_START);
pw.println(" Starts the location_time_zone_manager, creating time zone providers.");
pw.printf(" %s\n", SHELL_COMMAND_STOP);
pw.println(" Stops the location_time_zone_manager, destroying time zone providers.");
pw.printf(" %s <provider name> <mode>\n", SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE);
pw.println(" Sets a provider into a test mode next time the service started.");
pw.printf(" Values: %s|%s|%s\n", PROVIDER_MODE_OVERRIDE_NONE,
PROVIDER_MODE_OVERRIDE_DISABLED, PROVIDER_MODE_OVERRIDE_SIMULATED);
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.println(" Note: When enabled, this mode consumes memory and it is only intended for"
+ " testing.");
pw.println(" It should be disabled after use, or the device can be rebooted to"
+ " reset the mode to disabled.");
pw.println(" Disabling (or enabling repeatedly) clears any existing stored states.");
pw.printf(" %s [%s]\n", SHELL_COMMAND_DUMP_STATE, DUMP_STATE_OPTION_PROTO);
pw.println(" Dumps Location Time Zone Manager state for tests as text or binary proto"
+ " form.");
pw.println(" See the LocationTimeZoneManagerServiceStateProto definition for details.");
pw.printf(" %s <provider name> <test command>\n",
SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND);
pw.println(" Passes a test command to the named provider.");
pw.println();
pw.printf("%s details:\n", SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND);
pw.println();
pw.printf("<provider name> = One of %s\n", VALID_PROVIDER_NAMES);
pw.println();
pw.println("<test command> encoding:");
pw.printf("%s details:\n", SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND);
pw.println();
pw.println("Provider <test command> encoding:");
pw.println();
TestCommand.printShellCommandEncodingHelp(pw);
pw.println();
pw.printf("Provider modes can be modified by setting the \"%s\" or \"%s\"\n system"
+ " property and restarting the service or rebooting the device.\n",
SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_PRIMARY,
SYSTEM_PROPERTY_KEY_PROVIDER_MODE_OVERRIDE_SECONDARY);
pw.println("Values are:");
pw.printf(" %s - simulation mode (see below for commands)\n",
SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_SIMULATED);
pw.printf(" %s - disabled mode\n", SYSTEM_PROPERTY_VALUE_PROVIDER_MODE_DISABLED);
pw.println();
pw.println("Simulated providers can be used to test the system server behavior or to"
pw.println("Simulated provider mode can be used to test the system server behavior or to"
+ " reproduce bugs without the complexity of using real providers.");
pw.println();
pw.println("The test commands for simulated providers are:");
@@ -132,6 +171,119 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
return 0;
}
private int runSetProviderModeOverride() {
PrintWriter outPrintWriter = getOutPrintWriter();
try {
String providerName = getNextArgRequired();
String modeOverride = getNextArgRequired();
outPrintWriter.println("Setting provider mode override for " + providerName
+ " to " + modeOverride);
mService.setProviderModeOverride(providerName, modeOverride);
} catch (RuntimeException e) {
reportError(e);
return 1;
}
return 0;
}
private int runRecordProviderStates() {
PrintWriter outPrintWriter = getOutPrintWriter();
boolean enabled;
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);
} catch (IllegalStateException e) {
reportError(e);
return 2;
}
return 0;
}
private int runDumpControllerState() {
LocationTimeZoneManagerServiceState state;
try {
state = mService.getStateForTests();
} catch (RuntimeException e) {
reportError(e);
return 1;
}
DualDumpOutputStream outputStream;
boolean useProto = Objects.equals(DUMP_STATE_OPTION_PROTO, getNextOption());
if (useProto) {
FileDescriptor outFd = getOutFileDescriptor();
outputStream = new DualDumpOutputStream(new ProtoOutputStream(outFd));
} else {
outputStream = new DualDumpOutputStream(
new IndentingPrintWriter(getOutPrintWriter(), " "));
}
if (state.getLastSuggestion() != null) {
GeolocationTimeZoneSuggestion lastSuggestion = state.getLastSuggestion();
long lastSuggestionToken = outputStream.start(
"last_suggestion", LocationTimeZoneManagerServiceStateProto.LAST_SUGGESTION);
for (String zoneId : lastSuggestion.getZoneIds()) {
outputStream.write(
"zone_ids" , GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId);
}
for (String debugInfo : lastSuggestion.getDebugInfo()) {
outputStream.write(
"debug_info", GeolocationTimeZoneSuggestionProto.DEBUG_INFO, debugInfo);
}
outputStream.end(lastSuggestionToken);
}
writeProviderStates(outputStream, state.getPrimaryProviderStates(),
"primary_provider_states",
LocationTimeZoneManagerServiceStateProto.PRIMARY_PROVIDER_STATES);
writeProviderStates(outputStream, state.getSecondaryProviderStates(),
"secondary_provider_states",
LocationTimeZoneManagerServiceStateProto.SECONDARY_PROVIDER_STATES);
outputStream.flush();
return 0;
}
private static void writeProviderStates(DualDumpOutputStream outputStream,
List<LocationTimeZoneProvider.ProviderState> providerStates, String fieldName,
long fieldId) {
for (LocationTimeZoneProvider.ProviderState providerState : providerStates) {
long providerStateToken = outputStream.start(fieldName, fieldId);
outputStream.write("state", TimeZoneProviderStateProto.STATE,
convertProviderStateEnumToProtoEnum(providerState.stateEnum));
outputStream.end(providerStateToken);
}
}
private static int convertProviderStateEnumToProtoEnum(@ProviderStateEnum int stateEnum) {
switch (stateEnum) {
case PROVIDER_STATE_UNKNOWN:
return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_UNKNOWN;
case PROVIDER_STATE_STARTED_INITIALIZING:
return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_INITIALIZING;
case PROVIDER_STATE_STARTED_CERTAIN:
return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_CERTAIN;
case PROVIDER_STATE_STARTED_UNCERTAIN:
return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_UNCERTAIN;
case PROVIDER_STATE_STOPPED:
return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_DISABLED;
case PROVIDER_STATE_PERM_FAILED:
return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_PERM_FAILED;
case PROVIDER_STATE_DESTROYED:
return LocationTimeZoneManagerProto.TIME_ZONE_PROVIDER_STATE_DESTROYED;
default: {
throw new IllegalArgumentException("Unknown stateEnum=" + stateEnum);
}
}
}
private int runSendProviderTestCommand() {
PrintWriter outPrintWriter = getOutPrintWriter();

View File

@@ -49,6 +49,8 @@ import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.ReferenceWithHistory;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
@@ -338,12 +340,21 @@ abstract class LocationTimeZoneProvider implements Dumpable {
@NonNull final Object mSharedLock;
@NonNull final String mProviderName;
/**
* Usually {@code false} but can be set to {@code true} for testing.
*/
@GuardedBy("mSharedLock")
private boolean mStateChangeRecording;
@GuardedBy("mSharedLock")
@NonNull
private final ArrayList<ProviderState> mRecordedStates = new ArrayList<>(0);
/**
* The current state (with history for debugging).
*/
@GuardedBy("mSharedLock")
final ReferenceWithHistory<ProviderState> mCurrentState =
new ReferenceWithHistory<>(10);
final ReferenceWithHistory<ProviderState> mCurrentState = new ReferenceWithHistory<>(10);
/**
* Used for scheduling initialization timeouts, i.e. for providers that have just been started.
@@ -422,6 +433,28 @@ abstract class LocationTimeZoneProvider implements Dumpable {
@GuardedBy("mSharedLock")
abstract void onDestroy();
/**
* Sets the provider into state recording mode for tests.
*/
final void setStateChangeRecordingEnabled(boolean enabled) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
mStateChangeRecording = enabled;
mRecordedStates.clear();
mRecordedStates.trimToSize();
}
}
/**
* Returns recorded states.
*/
final List<ProviderState> getRecordedStates() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
return new ArrayList<>(mRecordedStates);
}
}
/**
* 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
@@ -434,8 +467,11 @@ abstract class LocationTimeZoneProvider implements Dumpable {
ProviderState oldState = mCurrentState.get();
mCurrentState.set(newState);
onSetCurrentState(newState);
if (notifyChanges) {
if (!Objects.equals(newState, oldState)) {
if (!Objects.equals(newState, oldState)) {
if (mStateChangeRecording) {
mRecordedStates.add(newState);
}
if (notifyChanges) {
mProviderListener.onProviderStateChange(newState);
}
}

View File

@@ -25,6 +25,7 @@ import android.app.timezonedetector.ITimeZoneDetectorService;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.content.Context;
import android.location.LocationManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -311,6 +312,19 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
mHandler.post(() -> mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion));
}
boolean isGeoTimeZoneDetectionSupported() {
enforceManageTimeZoneDetectorPermission();
return isGeoLocationTimeZoneDetectionEnabled(mContext);
}
boolean isLocationEnabled() {
enforceManageTimeZoneDetectorPermission();
return mContext.getSystemService(LocationManager.class)
.isLocationEnabledForUser(mContext.getUser());
}
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
@Nullable String[] args) {

View File

@@ -15,10 +15,17 @@
*/
package com.android.server.timezonedetector;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_LOCATION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.os.ShellCommand;
@@ -43,6 +50,18 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
}
switch (cmd) {
case SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED:
return runIsAutoDetectionEnabled();
case SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED:
return runSetAutoDetectionEnabled();
case SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED:
return runIsGeoDetectionSupported();
case SHELL_COMMAND_IS_LOCATION_ENABLED:
return runIsLocationEnabled();
case SHELL_COMMAND_IS_GEO_DETECTION_ENABLED:
return runIsGeoDetectionEnabled();
case SHELL_COMMAND_SET_GEO_DETECTION_ENABLED:
return runSetGeoDetectionEnabled();
case SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE:
return runSuggestGeolocationTimeZone();
case SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE:
@@ -55,6 +74,54 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
}
}
private int runIsAutoDetectionEnabled() {
final PrintWriter pw = getOutPrintWriter();
boolean enabled = mInterface.getCapabilitiesAndConfig()
.getConfiguration()
.isAutoDetectionEnabled();
pw.println(enabled);
return 0;
}
private int runIsGeoDetectionSupported() {
final PrintWriter pw = getOutPrintWriter();
boolean enabled = mInterface.isGeoTimeZoneDetectionSupported();
pw.println(enabled);
return 0;
}
private int runIsLocationEnabled() {
final PrintWriter pw = getOutPrintWriter();
boolean enabled = mInterface.isLocationEnabled();
pw.println(enabled);
return 0;
}
private int runIsGeoDetectionEnabled() {
final PrintWriter pw = getOutPrintWriter();
boolean enabled = mInterface.getCapabilitiesAndConfig()
.getConfiguration()
.isGeoDetectionEnabled();
pw.println(enabled);
return 0;
}
private int runSetAutoDetectionEnabled() {
boolean enabled = Boolean.parseBoolean(getNextArgRequired());
TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(enabled)
.build();
return mInterface.updateConfiguration(configuration) ? 0 : 1;
}
private int runSetGeoDetectionEnabled() {
boolean enabled = Boolean.parseBoolean(getNextArgRequired());
TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
.setGeoDetectionEnabled(enabled)
.build();
return mInterface.updateConfiguration(configuration) ? 0 : 1;
}
private int runSuggestGeolocationTimeZone() {
return runSuggestTimeZone(
() -> GeolocationTimeZoneSuggestion.parseCommandLineArg(this),
@@ -96,6 +163,20 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
pw.println("Time Zone Detector (time_zone_detector) commands:");
pw.println(" help");
pw.println(" Print this help text.");
pw.printf(" %s\n", SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED);
pw.println(" Prints true/false according to the automatic tz detection setting");
pw.printf(" %s true|false\n", SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED);
pw.println(" Sets the automatic tz detection setting.");
pw.printf(" %s\n", SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED);
pw.println(" Prints true/false according to whether geolocation time zone detection is"
+ " supported on this device");
pw.printf(" %s\n", SHELL_COMMAND_IS_LOCATION_ENABLED);
pw.println(" Prints true/false according to whether the master location toggle is"
+ " enabled for the current user");
pw.printf(" %s\n", SHELL_COMMAND_IS_GEO_DETECTION_ENABLED);
pw.println(" Prints true/false according to the geolocation tz detection setting");
pw.printf(" %s true|false\n", SHELL_COMMAND_SET_GEO_DETECTION_ENABLED);
pw.println(" Sets the geolocation tz detection setting.");
pw.printf(" %s <geolocation suggestion opts>\n",
SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
pw.printf(" %s <manual suggestion opts>\n",

View File

@@ -28,6 +28,7 @@ import static com.android.server.location.timezone.TimeZoneProviderEvent.createU
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -923,6 +924,74 @@ public class ControllerImplTest {
assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
public void stateRecording() {
ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
{
LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
assertNull(state.getLastSuggestion());
assertTrue(state.getPrimaryProviderStates().isEmpty());
assertTrue(state.getSecondaryProviderStates().isEmpty());
}
// State recording and simulate some provider behavior that will show up in the state
// recording.
controllerImpl.setProviderStateRecordingEnabled(true);
// Simulate an uncertain event from the primary. This will start the secondary.
mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
{
LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
assertNull(state.getLastSuggestion());
List<LocationTimeZoneProvider.ProviderState> primaryProviderStates =
state.getPrimaryProviderStates();
assertEquals(1, primaryProviderStates.size());
assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN,
primaryProviderStates.get(0).stateEnum);
List<LocationTimeZoneProvider.ProviderState> secondaryProviderStates =
state.getSecondaryProviderStates();
assertEquals(1, secondaryProviderStates.size());
assertEquals(PROVIDER_STATE_STARTED_INITIALIZING,
secondaryProviderStates.get(0).stateEnum);
}
// Simulate an uncertain event from the primary. This will start the secondary.
mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
{
LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
state.getLastSuggestion().getZoneIds());
List<LocationTimeZoneProvider.ProviderState> primaryProviderStates =
state.getPrimaryProviderStates();
assertEquals(1, primaryProviderStates.size());
assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN, primaryProviderStates.get(0).stateEnum);
List<LocationTimeZoneProvider.ProviderState> secondaryProviderStates =
state.getSecondaryProviderStates();
assertEquals(2, secondaryProviderStates.size());
assertEquals(PROVIDER_STATE_STARTED_CERTAIN, secondaryProviderStates.get(1).stateEnum);
}
controllerImpl.setProviderStateRecordingEnabled(false);
{
LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
state.getLastSuggestion().getZoneIds());
assertTrue(state.getPrimaryProviderStates().isEmpty());
assertTrue(state.getSecondaryProviderStates().isEmpty());
}
}
private static void assertUncertaintyTimeoutSet(
LocationTimeZoneProviderController.Environment environment,
LocationTimeZoneProviderController controller) {

View File

@@ -18,6 +18,7 @@ package com.android.server.location.timezone;
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.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
@@ -49,6 +50,7 @@ import org.junit.Test;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -174,6 +176,48 @@ public class LocationTimeZoneProviderTest {
assertNotNull(result.getString(TEST_COMMAND_RESULT_ERROR_KEY));
}
@Test
public void stateRecording() {
String providerName = "primary";
TestLocationTimeZoneProvider provider =
new TestLocationTimeZoneProvider(mTestThreadingDomain, providerName);
provider.setStateChangeRecordingEnabled(true);
// initialize()
provider.initialize(mProviderListener);
provider.assertLatestRecordedState(PROVIDER_STATE_STOPPED);
// startUpdates()
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
provider.startUpdates(config, arbitraryInitializationTimeout,
arbitraryInitializationTimeoutFuzz);
provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_INITIALIZING);
// Simulate a suggestion event being received.
TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
.setTimeZoneIds(Arrays.asList("Europe/London"))
.build();
TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion);
provider.simulateProviderEventReceived(event);
provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_CERTAIN);
// Simulate an uncertain event being received.
event = TimeZoneProviderEvent.createUncertainEvent();
provider.simulateProviderEventReceived(event);
provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN);
// stopUpdates()
provider.stopUpdates();
provider.assertLatestRecordedState(PROVIDER_STATE_STOPPED);
// destroy()
provider.destroy();
provider.assertLatestRecordedState(PROVIDER_STATE_DESTROYED);
}
/** A test stand-in for the real {@link LocationTimeZoneProviderController}'s listener. */
private static class TestProviderListener implements ProviderListener {
@@ -257,5 +301,11 @@ public class LocationTimeZoneProviderTest {
void assertOnDestroyCalled() {
assertTrue(mOnDestroyCalled);
}
void assertLatestRecordedState(@ProviderState.ProviderStateEnum int expectedStateEnum) {
List<ProviderState> recordedStates = getRecordedStates();
assertEquals(expectedStateEnum,
recordedStates.get(recordedStates.size() - 1).stateEnum);
}
}
}