Merge "Add support for tz detection telephony fallback"
This commit is contained in:
@@ -95,6 +95,13 @@ public interface TimeZoneDetector {
|
||||
*/
|
||||
String SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE = "suggest_telephony_time_zone";
|
||||
|
||||
/**
|
||||
* A shell command that enables telephony time zone fallback. See {@link
|
||||
* com.android.server.timezonedetector.TimeZoneDetectorStrategy} for details.
|
||||
* @hide
|
||||
*/
|
||||
String SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK = "enable_telephony_fallback";
|
||||
|
||||
/**
|
||||
* A shared utility method to create a {@link ManualTimeZoneSuggestion}.
|
||||
*
|
||||
|
||||
@@ -1802,6 +1802,13 @@
|
||||
provider services. -->
|
||||
<string name="config_secondaryLocationTimeZoneProviderPackageName" translatable="false"></string>
|
||||
|
||||
<!-- Whether the time zone detection logic supports fall back from geolocation suggestions to
|
||||
telephony suggestions temporarily in certain circumstances. Reduces time zone detection
|
||||
latency during some scenarios like air travel. Only useful when both geolocation and
|
||||
telephony time zone detection are supported on a device.
|
||||
See com.android.server.timezonedetector.TimeZoneDetectorStrategy for more information. -->
|
||||
<bool name="config_supportTelephonyTimeZoneFallback" translatable="false">false</bool>
|
||||
|
||||
<!-- Whether to enable network location overlay which allows network location provider to be
|
||||
replaced by an app at run-time. When disabled, only the
|
||||
config_networkLocationProviderPackageName package will be searched for network location
|
||||
|
||||
@@ -2238,6 +2238,7 @@
|
||||
<java-symbol type="string" name="config_primaryLocationTimeZoneProviderPackageName" />
|
||||
<java-symbol type="bool" name="config_enableSecondaryLocationTimeZoneProvider" />
|
||||
<java-symbol type="string" name="config_secondaryLocationTimeZoneProviderPackageName" />
|
||||
<java-symbol type="bool" name="config_supportTelephonyTimeZoneFallback" />
|
||||
<java-symbol type="bool" name="config_autoResetAirplaneMode" />
|
||||
<java-symbol type="string" name="config_notificationAccessConfirmationActivity" />
|
||||
<java-symbol type="bool" name="config_killableInputMethods" />
|
||||
|
||||
@@ -65,6 +65,7 @@ public final class ServerFlags {
|
||||
KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
|
||||
KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE,
|
||||
KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE,
|
||||
KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
|
||||
})
|
||||
@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@@ -138,6 +139,14 @@ public final class ServerFlags {
|
||||
KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT =
|
||||
"location_time_zone_detection_setting_enabled_default";
|
||||
|
||||
/**
|
||||
* The key to control support for time zone detection falling back to telephony detection under
|
||||
* certain circumstances.
|
||||
*/
|
||||
public static final @DeviceConfigKey String
|
||||
KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED =
|
||||
"time_zone_detector_telephony_fallback_supported";
|
||||
|
||||
/**
|
||||
* The key to override the time detector origin priorities configuration. A comma-separated list
|
||||
* of strings that will be passed to {@link TimeDetectorStrategy#stringToOrigin(String)}.
|
||||
|
||||
@@ -39,6 +39,7 @@ public final class ConfigurationInternal {
|
||||
|
||||
private final boolean mTelephonyDetectionSupported;
|
||||
private final boolean mGeoDetectionSupported;
|
||||
private final boolean mTelephonyFallbackSupported;
|
||||
private final boolean mAutoDetectionEnabledSetting;
|
||||
private final @UserIdInt int mUserId;
|
||||
private final boolean mUserConfigAllowed;
|
||||
@@ -48,6 +49,7 @@ public final class ConfigurationInternal {
|
||||
private ConfigurationInternal(Builder builder) {
|
||||
mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported;
|
||||
mGeoDetectionSupported = builder.mGeoDetectionSupported;
|
||||
mTelephonyFallbackSupported = builder.mTelephonyFallbackSupported;
|
||||
mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
|
||||
|
||||
mUserId = builder.mUserId;
|
||||
@@ -71,6 +73,14 @@ public final class ConfigurationInternal {
|
||||
return mGeoDetectionSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the device supports time zone detection falling back to telephony detection
|
||||
* under certain circumstances.
|
||||
*/
|
||||
public boolean isTelephonyFallbackSupported() {
|
||||
return mTelephonyFallbackSupported;
|
||||
}
|
||||
|
||||
/** Returns the value of the auto time zone detection enabled setting. */
|
||||
public boolean getAutoDetectionEnabledSetting() {
|
||||
return mAutoDetectionEnabledSetting;
|
||||
@@ -216,6 +226,7 @@ public final class ConfigurationInternal {
|
||||
&& mUserConfigAllowed == that.mUserConfigAllowed
|
||||
&& mTelephonyDetectionSupported == that.mTelephonyDetectionSupported
|
||||
&& mGeoDetectionSupported == that.mGeoDetectionSupported
|
||||
&& mTelephonyFallbackSupported == that.mTelephonyFallbackSupported
|
||||
&& mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
|
||||
&& mLocationEnabledSetting == that.mLocationEnabledSetting
|
||||
&& mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting;
|
||||
@@ -224,8 +235,8 @@ public final class ConfigurationInternal {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported,
|
||||
mGeoDetectionSupported, mAutoDetectionEnabledSetting, mLocationEnabledSetting,
|
||||
mGeoDetectionEnabledSetting);
|
||||
mGeoDetectionSupported, mTelephonyFallbackSupported, mAutoDetectionEnabledSetting,
|
||||
mLocationEnabledSetting, mGeoDetectionEnabledSetting);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -235,6 +246,7 @@ public final class ConfigurationInternal {
|
||||
+ ", mUserConfigAllowed=" + mUserConfigAllowed
|
||||
+ ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported
|
||||
+ ", mGeoDetectionSupported=" + mGeoDetectionSupported
|
||||
+ ", mTelephonyFallbackSupported=" + mTelephonyFallbackSupported
|
||||
+ ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting
|
||||
+ ", mLocationEnabledSetting=" + mLocationEnabledSetting
|
||||
+ ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting
|
||||
@@ -251,6 +263,7 @@ public final class ConfigurationInternal {
|
||||
private boolean mUserConfigAllowed;
|
||||
private boolean mTelephonyDetectionSupported;
|
||||
private boolean mGeoDetectionSupported;
|
||||
private boolean mTelephonyFallbackSupported;
|
||||
private boolean mAutoDetectionEnabledSetting;
|
||||
private boolean mLocationEnabledSetting;
|
||||
private boolean mGeoDetectionEnabledSetting;
|
||||
@@ -269,6 +282,7 @@ public final class ConfigurationInternal {
|
||||
this.mUserId = toCopy.mUserId;
|
||||
this.mUserConfigAllowed = toCopy.mUserConfigAllowed;
|
||||
this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported;
|
||||
this.mTelephonyFallbackSupported = toCopy.mTelephonyFallbackSupported;
|
||||
this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported;
|
||||
this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting;
|
||||
this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting;
|
||||
@@ -299,6 +313,15 @@ public final class ConfigurationInternal {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether time zone detection supports falling back to telephony detection under
|
||||
* certain circumstances.
|
||||
*/
|
||||
public Builder setTelephonyFallbackSupported(boolean supported) {
|
||||
mTelephonyFallbackSupported = supported;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the automatic time zone detection enabled setting for this device.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.timezonedetector;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* The interface for the class that is responsible for detecting device activities relevant to
|
||||
* time zone detection. This interface exists to decouple parts of the time zone detector from each
|
||||
* other and to enable easier testing.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
interface DeviceActivityMonitor extends Dumpable {
|
||||
|
||||
/** Adds a listener. */
|
||||
void addListener(@NonNull Listener listener);
|
||||
|
||||
/**
|
||||
* A listener for device activities. See {@link DeviceActivityMonitor#addListener(Listener)}.
|
||||
*/
|
||||
interface Listener {
|
||||
/** A flight has completed. */
|
||||
void onFlightComplete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.timezonedetector;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.os.Handler;
|
||||
import android.provider.Settings;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The real implementation of {@link DeviceActivityMonitor}.
|
||||
*/
|
||||
class DeviceActivityMonitorImpl implements DeviceActivityMonitor {
|
||||
|
||||
private static final String LOG_TAG = TimeZoneDetectorService.TAG;
|
||||
private static final boolean DBG = TimeZoneDetectorService.DBG;
|
||||
|
||||
static DeviceActivityMonitor create(@NonNull Context context, @NonNull Handler handler) {
|
||||
return new DeviceActivityMonitorImpl(context, handler);
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
@NonNull
|
||||
private final List<Listener> mListeners = new ArrayList<>();
|
||||
|
||||
private DeviceActivityMonitorImpl(@NonNull Context context, @NonNull Handler handler) {
|
||||
// The way this "detects" a flight concluding is by the user explicitly turning off airplane
|
||||
// mode. Smarter heuristics would be nice.
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
ContentObserver airplaneModeObserver = new ContentObserver(handler) {
|
||||
@Override
|
||||
public void onChange(boolean unused) {
|
||||
try {
|
||||
int state = Settings.Global.getInt(
|
||||
contentResolver, Settings.Global.AIRPLANE_MODE_ON);
|
||||
if (state == 0) {
|
||||
notifyFlightComplete();
|
||||
}
|
||||
} catch (Settings.SettingNotFoundException e) {
|
||||
Slog.e(LOG_TAG, "Unable to read airplane mode state", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
contentResolver.registerContentObserver(
|
||||
Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON),
|
||||
true /* notifyForDescendants */,
|
||||
airplaneModeObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void addListener(Listener listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
private synchronized void notifyFlightComplete() {
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "notifyFlightComplete");
|
||||
}
|
||||
|
||||
for (Listener listener : mListeners) {
|
||||
listener.onFlightComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(IndentingPrintWriter pw, String[] args) {
|
||||
// No-op right now: no state to dump.
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
package com.android.server.timezonedetector;
|
||||
|
||||
import android.annotation.ElapsedRealtimeLong;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.AlarmManager;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.os.SystemProperties;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -79,4 +81,9 @@ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment
|
||||
AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
|
||||
alarmManager.setTimeZone(zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @ElapsedRealtimeLong long elapsedRealtimeMillis() {
|
||||
return SystemClock.elapsedRealtime();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,11 @@ public final class MetricsTimeZoneDetectorState {
|
||||
return mConfigurationInternal.isGeoDetectionSupported();
|
||||
}
|
||||
|
||||
/** Returns true if the device supports telephony time zone detection fallback. */
|
||||
public boolean isTelephonyTimeZoneFallbackSupported() {
|
||||
return mConfigurationInternal.isTelephonyFallbackSupported();
|
||||
}
|
||||
|
||||
/** Returns true if user's location can be used generally. */
|
||||
public boolean getUserLocationEnabledSetting() {
|
||||
return mConfigurationInternal.getLocationEnabledSetting();
|
||||
|
||||
@@ -64,6 +64,7 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
|
||||
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
|
||||
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
|
||||
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
|
||||
ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
|
||||
}));
|
||||
|
||||
/**
|
||||
@@ -294,6 +295,7 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
|
||||
.setTelephonyDetectionFeatureSupported(
|
||||
isTelephonyTimeZoneDetectionFeatureSupported())
|
||||
.setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
|
||||
.setTelephonyFallbackSupported(isTelephonyFallbackSupported())
|
||||
.setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting())
|
||||
.setUserConfigAllowed(isUserConfigAllowed(userId))
|
||||
.setLocationEnabledSetting(getLocationEnabledSetting(userId))
|
||||
@@ -549,6 +551,13 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
|
||||
mRecordProviderStateChanges = false;
|
||||
}
|
||||
|
||||
private boolean isTelephonyFallbackSupported() {
|
||||
return mServerFlags.getBoolean(
|
||||
ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
|
||||
getConfigBoolean(
|
||||
com.android.internal.R.bool.config_supportTelephonyTimeZoneFallback));
|
||||
}
|
||||
|
||||
private boolean getConfigBoolean(int providerEnabledConfigId) {
|
||||
Resources resources = mContext.getResources();
|
||||
return resources.getBoolean(providerEnabledConfigId);
|
||||
|
||||
@@ -83,6 +83,16 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
ServiceConfigAccessorImpl.getInstance(context);
|
||||
TimeZoneDetectorStrategy timeZoneDetectorStrategy =
|
||||
TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
|
||||
DeviceActivityMonitor deviceActivityMonitor =
|
||||
DeviceActivityMonitorImpl.create(context, handler);
|
||||
|
||||
// Wire up the telephony fallback behavior to activity detection.
|
||||
deviceActivityMonitor.addListener(new DeviceActivityMonitor.Listener() {
|
||||
@Override
|
||||
public void onFlightComplete() {
|
||||
timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
|
||||
}
|
||||
});
|
||||
|
||||
// Create and publish the local service for use by internal callers.
|
||||
TimeZoneDetectorInternal internal =
|
||||
@@ -93,6 +103,10 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
// permissioned) processes.
|
||||
TimeZoneDetectorService service = TimeZoneDetectorService.create(
|
||||
context, handler, serviceConfigAccessor, timeZoneDetectorStrategy);
|
||||
|
||||
// Dump the device activity monitor when the service is dumped.
|
||||
service.addDumpable(deviceActivityMonitor);
|
||||
|
||||
publishBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, service);
|
||||
}
|
||||
}
|
||||
@@ -331,6 +345,15 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
return mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a signal to enable telephony fallback. Provided for command-line access for use
|
||||
* during tests. This is not exposed as a binder API.
|
||||
*/
|
||||
void enableTelephonyFallback() {
|
||||
enforceManageTimeZoneDetectorPermission();
|
||||
mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the supplied {@link Dumpable} for dumping. When the service is dumped
|
||||
* {@link Dumpable#dump(IndentingPrintWriter, String[])} will be called on the {@code dumpable}.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.server.timezonedetector;
|
||||
|
||||
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK;
|
||||
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;
|
||||
@@ -30,6 +31,7 @@ import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
|
||||
import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED;
|
||||
import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT;
|
||||
import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE;
|
||||
import static com.android.server.timedetector.ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED;
|
||||
|
||||
import android.app.time.LocationTimeZoneManager;
|
||||
import android.app.time.TimeZoneConfiguration;
|
||||
@@ -76,6 +78,8 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
|
||||
return runSuggestManualTimeZone();
|
||||
case SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE:
|
||||
return runSuggestTelephonyTimeZone();
|
||||
case SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK:
|
||||
return runEnableTelephonyFallback();
|
||||
default: {
|
||||
return handleDefaultCommands(cmd);
|
||||
}
|
||||
@@ -169,6 +173,11 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private int runEnableTelephonyFallback() {
|
||||
mInterface.enableTelephonyFallback();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHelp() {
|
||||
final PrintWriter pw = getOutPrintWriter();
|
||||
@@ -190,6 +199,13 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
|
||||
+ "\n");
|
||||
pw.printf(" %s true|false\n", SHELL_COMMAND_SET_GEO_DETECTION_ENABLED);
|
||||
pw.printf(" Sets the geolocation time zone detection enabled setting.\n");
|
||||
pw.printf(" %s\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK);
|
||||
pw.printf(" Signals that telephony time zone detection fall back can be used if"
|
||||
+ " geolocation detection is supported and enabled. This is a temporary state until"
|
||||
+ " geolocation detection becomes \"certain\". To have an effect this requires that"
|
||||
+ " the telephony fallback feature is supported on the device, see below for"
|
||||
+ " for device_config flags.\n");
|
||||
pw.println();
|
||||
pw.printf(" %s <geolocation suggestion opts>\n",
|
||||
SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
|
||||
pw.printf(" %s <manual suggestion opts>\n",
|
||||
@@ -216,6 +232,9 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
|
||||
pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE);
|
||||
pw.printf(" Used to override the device's 'geolocation time zone detection enabled'"
|
||||
+ " setting [*].\n");
|
||||
pw.printf(" %s\n", KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED);
|
||||
pw.printf(" Used to enable / disable support for telephony detection fallback. Also see"
|
||||
+ " the %s command.\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK);
|
||||
pw.println();
|
||||
pw.printf("[*] To be enabled, the user must still have location = on / auto time zone"
|
||||
+ " detection = on.\n");
|
||||
|
||||
@@ -69,6 +69,20 @@ import android.util.IndentingPrintWriter;
|
||||
* users enter areas without the necessary signals. Ultimately, with no perfect algorithm available,
|
||||
* the user is left to choose which algorithm works best for their circumstances.
|
||||
*
|
||||
* <p>When geolocation detection is supported and enabled, in certain circumstances, such as during
|
||||
* international travel, it makes sense to prioritize speed of detection via telephony (when
|
||||
* available) Vs waiting for the geolocation algorithm to reach certainty. Geolocation detection can
|
||||
* sometimes be slow to get a location fix and can require network connectivity (which cannot be
|
||||
* assumed when users are travelling) for server-assisted location detection or time zone lookup.
|
||||
* Therefore, as a restricted form of prioritization between geolocation and telephony algorithms,
|
||||
* the strategy provides "telephony fallback" behavior, which can be set to "supported" via device
|
||||
* config. Fallback mode is toggled on at runtime via {@link #enableTelephonyTimeZoneFallback()} in
|
||||
* response to signals outside of the scope of this class. Telephony fallback allows the use of
|
||||
* telephony suggestions to help with faster detection but only until geolocation detection
|
||||
* provides a concrete, "certain" suggestion. After geolocation has made the first certain
|
||||
* suggestion, telephony fallback is disabled until the next call to {@link
|
||||
* #enableTelephonyTimeZoneFallback()}.
|
||||
*
|
||||
* <p>Threading:
|
||||
*
|
||||
* <p>Implementations of this class must be thread-safe as calls calls like {@link
|
||||
@@ -100,6 +114,13 @@ public interface TimeZoneDetectorStrategy extends Dumpable {
|
||||
*/
|
||||
void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion);
|
||||
|
||||
/**
|
||||
* Tells the strategy that it can fall back to telephony detection while geolocation detection
|
||||
* remains uncertain. {@link #suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion)} can
|
||||
* disable it again. See {@link TimeZoneDetectorStrategy} for details.
|
||||
*/
|
||||
void enableTelephonyTimeZoneFallback();
|
||||
|
||||
/** Generates a state snapshot for metrics. */
|
||||
@NonNull
|
||||
MetricsTimeZoneDetectorState generateMetricsState();
|
||||
|
||||
@@ -22,6 +22,7 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_M
|
||||
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
|
||||
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
|
||||
|
||||
import android.annotation.ElapsedRealtimeLong;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
@@ -31,6 +32,7 @@ import android.app.timezonedetector.ManualTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.TimestampedValue;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.LocalLog;
|
||||
import android.util.Slog;
|
||||
@@ -38,6 +40,7 @@ import android.util.Slog;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -83,6 +86,13 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
* Sets the device's time zone.
|
||||
*/
|
||||
void setDeviceTimeZone(@NonNull String zoneId);
|
||||
|
||||
/**
|
||||
* Returns the time according to the elapsed realtime clock, the same as {@link
|
||||
* android.os.SystemClock#elapsedRealtime()}.
|
||||
*/
|
||||
@ElapsedRealtimeLong
|
||||
long elapsedRealtimeMillis();
|
||||
}
|
||||
|
||||
private static final String LOG_TAG = TimeZoneDetectorService.TAG;
|
||||
@@ -190,6 +200,21 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
@NonNull
|
||||
private ConfigurationInternal mCurrentConfigurationInternal;
|
||||
|
||||
/**
|
||||
* Whether telephony time zone detection fallback is currently enabled (when device config also
|
||||
* allows).
|
||||
*
|
||||
* <p>This field is only actually used when telephony time zone fallback is supported, but the
|
||||
* value is maintained even when it isn't supported as it can be turned on at any time via
|
||||
* server flags. The reference time is the elapsed realtime when the mode last changed to help
|
||||
* ordering between fallback mode switches and suggestions.
|
||||
*
|
||||
* <p>See {@link TimeZoneDetectorStrategy} for more information.
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
@NonNull
|
||||
private TimestampedValue<Boolean> mTelephonyTimeZoneFallbackEnabled;
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
|
||||
*/
|
||||
@@ -205,6 +230,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
public TimeZoneDetectorStrategyImpl(@NonNull Environment environment) {
|
||||
mEnvironment = Objects.requireNonNull(environment);
|
||||
|
||||
// Start with telephony fallback enabled.
|
||||
mTelephonyTimeZoneFallbackEnabled =
|
||||
new TimestampedValue<>(mEnvironment.elapsedRealtimeMillis(), true);
|
||||
|
||||
synchronized (this) {
|
||||
mEnvironment.setConfigurationInternalChangeListener(
|
||||
this::handleConfigurationInternalChanged);
|
||||
@@ -233,6 +262,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
// are made in a sensible order and the most recent is always the best one to use.
|
||||
mLatestGeoLocationSuggestion.set(suggestion);
|
||||
|
||||
// Update the mTelephonyTimeZoneFallbackEnabled state if needed: a certain suggestion
|
||||
// will usually disable telephony fallback mode if it is currently enabled.
|
||||
disableTelephonyFallbackIfNeeded();
|
||||
|
||||
// Now perform auto time zone detection. The new suggestion may be used to modify the
|
||||
// time zone setting.
|
||||
String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
|
||||
@@ -303,6 +336,43 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
doAutoTimeZoneDetection(currentUserConfig, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void enableTelephonyTimeZoneFallback() {
|
||||
// Only do any work if fallback is currently not enabled.
|
||||
if (!mTelephonyTimeZoneFallbackEnabled.getValue()) {
|
||||
ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal;
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "enableTelephonyTimeZoneFallbackMode"
|
||||
+ ": currentUserConfig=" + currentUserConfig);
|
||||
}
|
||||
|
||||
final boolean fallbackEnabled = true;
|
||||
mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>(
|
||||
mEnvironment.elapsedRealtimeMillis(), fallbackEnabled);
|
||||
|
||||
// mTelephonyTimeZoneFallbackEnabled and mLatestGeoLocationSuggestion interact.
|
||||
// If there is currently a certain geolocation suggestion, then the telephony fallback
|
||||
// value needs to be considered after changing it.
|
||||
// With the way that the mTelephonyTimeZoneFallbackEnabled time is currently chosen
|
||||
// above, and the fact that geolocation suggestions should never have a time in the
|
||||
// future, the following call will be a no-op, and telephony fallback will remain
|
||||
// enabled. This comment / call is left as a reminder that it is possible for there to
|
||||
// be a current, "certain" geolocation suggestion when this signal arrives and it is
|
||||
// intentional that fallback stays enabled in this case. The choice to do this
|
||||
// is mostly for symmetry WRT the case where fallback is enabled and an old "certain"
|
||||
// geolocation is received; that would also leave telephony fallback enabled.
|
||||
// This choice means that telephony fallback will remain enabled until a new "certain"
|
||||
// geolocation suggestion is received. If, instead, the next geolocation is "uncertain",
|
||||
// then telephony fallback will occur.
|
||||
disableTelephonyFallbackIfNeeded();
|
||||
|
||||
if (currentUserConfig.isTelephonyFallbackSupported()) {
|
||||
String reason = "enableTelephonyTimeZoneFallbackMode";
|
||||
doAutoTimeZoneDetection(currentUserConfig, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public synchronized MetricsTimeZoneDetectorState generateMetricsState() {
|
||||
@@ -361,7 +431,32 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
// Use the correct algorithm based on the user's current configuration. If it changes, then
|
||||
// detection will be re-run.
|
||||
if (currentUserConfig.getGeoDetectionEnabledBehavior()) {
|
||||
doGeolocationTimeZoneDetection(detectionReason);
|
||||
boolean isGeoDetectionCertain = doGeolocationTimeZoneDetection(detectionReason);
|
||||
|
||||
// When geolocation detection is uncertain of the time zone, telephony detection
|
||||
// can be used if telephony fallback is enabled and supported.
|
||||
if (!isGeoDetectionCertain
|
||||
&& mTelephonyTimeZoneFallbackEnabled.getValue()
|
||||
&& currentUserConfig.isTelephonyFallbackSupported()) {
|
||||
|
||||
// This "only look at telephony if geolocation is uncertain" approach is
|
||||
// deliberate to try to keep the logic simple and keep telephony and geolocation
|
||||
// detection decoupled: when geolocation detection is in use, it is fully
|
||||
// trusted and the most recent "certain" geolocation suggestion available will
|
||||
// be used, even if the information it is based on is quite old.
|
||||
// There could be newer telephony suggestions available, but telephony
|
||||
// suggestions tend not to be withdrawn when they should be, and are based on
|
||||
// combining information like MCC and NITZ signals, which could have been
|
||||
// received at different times; thus it is hard to say what time the suggestion
|
||||
// is actually "for" and reason clearly about ordering between telephony and
|
||||
// geolocation suggestions.
|
||||
//
|
||||
// This approach is reliant on the location_time_zone_manager (and the location
|
||||
// time zone providers it manages) correctly sending "uncertain" suggestions
|
||||
// when the current location is unknown so that telephony fallback will actually be
|
||||
// used.
|
||||
doTelephonyTimeZoneDetection(detectionReason + ", telephony fallback mode");
|
||||
}
|
||||
} else {
|
||||
doTelephonyTimeZoneDetection(detectionReason);
|
||||
}
|
||||
@@ -371,21 +466,29 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
* Detects the time zone using the latest available geolocation time zone suggestion, if one is
|
||||
* available. The outcome can be that this strategy becomes / remains un-opinionated and nothing
|
||||
* is set.
|
||||
*
|
||||
* @return true if geolocation time zone detection was certain of the time zone, false if it is
|
||||
* uncertain
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private void doGeolocationTimeZoneDetection(@NonNull String detectionReason) {
|
||||
private boolean doGeolocationTimeZoneDetection(@NonNull String detectionReason) {
|
||||
GeolocationTimeZoneSuggestion latestGeolocationSuggestion =
|
||||
mLatestGeoLocationSuggestion.get();
|
||||
if (latestGeolocationSuggestion == null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> zoneIds = latestGeolocationSuggestion.getZoneIds();
|
||||
if (zoneIds == null || zoneIds.isEmpty()) {
|
||||
// This means the client has become uncertain about the time zone or it is certain there
|
||||
// is no known zone. In either case we must leave the existing time zone setting as it
|
||||
// is.
|
||||
return;
|
||||
if (zoneIds == null) {
|
||||
// This means the originator of the suggestion is uncertain about the time zone. The
|
||||
// existing time zone setting must be left as it is but detection can go on looking for
|
||||
// a different answer elsewhere.
|
||||
return false;
|
||||
} else if (zoneIds.isEmpty()) {
|
||||
// This means the originator is certain there is no time zone. The existing time zone
|
||||
// setting must be left as it is and detection must not go looking for a different
|
||||
// answer elsewhere.
|
||||
return true;
|
||||
}
|
||||
|
||||
// GeolocationTimeZoneSuggestion has no measure of quality. We assume all suggestions are
|
||||
@@ -404,6 +507,34 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
zoneId = zoneIds.get(0);
|
||||
}
|
||||
setDeviceTimeZoneIfRequired(zoneId, detectionReason);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest geo
|
||||
* suggestion is a "certain" suggestion that comes after the time when telephony fallback was
|
||||
* enabled.
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private void disableTelephonyFallbackIfNeeded() {
|
||||
GeolocationTimeZoneSuggestion suggestion = mLatestGeoLocationSuggestion.get();
|
||||
boolean isLatestSuggestionCertain = suggestion != null && suggestion.getZoneIds() != null;
|
||||
if (isLatestSuggestionCertain && mTelephonyTimeZoneFallbackEnabled.getValue()) {
|
||||
// This transition ONLY changes mTelephonyTimeZoneFallbackEnabled from
|
||||
// true -> false. See mTelephonyTimeZoneFallbackEnabled javadocs for details.
|
||||
|
||||
// Telephony fallback will be disabled after a "certain" suggestion is processed
|
||||
// if and only if the location information it is based on is from after telephony
|
||||
// fallback was enabled.
|
||||
boolean latestSuggestionIsNewerThanFallbackEnabled =
|
||||
suggestion.getEffectiveFromElapsedMillis()
|
||||
> mTelephonyTimeZoneFallbackEnabled.getReferenceTimeMillis();
|
||||
if (latestSuggestionIsNewerThanFallbackEnabled) {
|
||||
final boolean fallbackEnabled = false;
|
||||
mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>(
|
||||
mEnvironment.elapsedRealtimeMillis(), fallbackEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -556,6 +687,12 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
+ mEnvironment.isDeviceTimeZoneInitialized());
|
||||
ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone());
|
||||
|
||||
ipw.println("Misc state:");
|
||||
ipw.increaseIndent(); // level 2
|
||||
ipw.println("mTelephonyTimeZoneFallbackEnabled="
|
||||
+ formatDebugString(mTelephonyTimeZoneFallbackEnabled));
|
||||
ipw.decreaseIndent(); // level 2
|
||||
|
||||
ipw.println("Time zone change log:");
|
||||
ipw.increaseIndent(); // level 2
|
||||
mTimeZoneChangesLog.dump(ipw);
|
||||
@@ -603,6 +740,11 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
return mLatestGeoLocationSuggestion.get();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public synchronized boolean isTelephonyFallbackEnabledForTests() {
|
||||
return mTelephonyTimeZoneFallbackEnabled.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
|
||||
*/
|
||||
@@ -652,4 +794,8 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatDebugString(TimestampedValue<?> value) {
|
||||
return value.getValue() + " @ " + Duration.ofMillis(value.getReferenceTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ public class ConfigurationInternalTest {
|
||||
.setUserConfigAllowed(true)
|
||||
.setTelephonyDetectionFeatureSupported(true)
|
||||
.setGeoDetectionFeatureSupported(true)
|
||||
.setTelephonyFallbackSupported(false)
|
||||
.setAutoDetectionEnabledSetting(true)
|
||||
.setLocationEnabledSetting(true)
|
||||
.setGeoDetectionEnabledSetting(true)
|
||||
@@ -110,6 +111,7 @@ public class ConfigurationInternalTest {
|
||||
.setUserConfigAllowed(false)
|
||||
.setTelephonyDetectionFeatureSupported(true)
|
||||
.setGeoDetectionFeatureSupported(true)
|
||||
.setTelephonyFallbackSupported(false)
|
||||
.setAutoDetectionEnabledSetting(true)
|
||||
.setLocationEnabledSetting(true)
|
||||
.setGeoDetectionEnabledSetting(true)
|
||||
@@ -174,6 +176,7 @@ public class ConfigurationInternalTest {
|
||||
.setUserConfigAllowed(true)
|
||||
.setTelephonyDetectionFeatureSupported(false)
|
||||
.setGeoDetectionFeatureSupported(false)
|
||||
.setTelephonyFallbackSupported(false)
|
||||
.setAutoDetectionEnabledSetting(true)
|
||||
.setLocationEnabledSetting(true)
|
||||
.setGeoDetectionEnabledSetting(true)
|
||||
@@ -236,6 +239,7 @@ public class ConfigurationInternalTest {
|
||||
.setUserConfigAllowed(true)
|
||||
.setTelephonyDetectionFeatureSupported(true)
|
||||
.setGeoDetectionFeatureSupported(false)
|
||||
.setTelephonyFallbackSupported(false)
|
||||
.setAutoDetectionEnabledSetting(true)
|
||||
.setLocationEnabledSetting(true)
|
||||
.setGeoDetectionEnabledSetting(true)
|
||||
@@ -288,4 +292,18 @@ public class ConfigurationInternalTest {
|
||||
assertTrue(configuration.isGeoDetectionEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_telephonyFallbackSupported() {
|
||||
ConfigurationInternal config = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
|
||||
.setUserConfigAllowed(true)
|
||||
.setTelephonyDetectionFeatureSupported(true)
|
||||
.setGeoDetectionFeatureSupported(false)
|
||||
.setTelephonyFallbackSupported(true)
|
||||
.setAutoDetectionEnabledSetting(true)
|
||||
.setLocationEnabledSetting(true)
|
||||
.setGeoDetectionEnabledSetting(true)
|
||||
.build();
|
||||
assertTrue(config.isTelephonyFallbackSupported());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,11 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
|
||||
mLastTelephonySuggestion = timeZoneSuggestion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableTelephonyTimeZoneFallback() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricsTimeZoneDetectorState generateMetricsState() {
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
@@ -378,6 +378,7 @@ public class TimeZoneDetectorServiceTest {
|
||||
return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
|
||||
.setTelephonyDetectionFeatureSupported(true)
|
||||
.setGeoDetectionFeatureSupported(true)
|
||||
.setTelephonyFallbackSupported(false)
|
||||
.setUserConfigAllowed(true)
|
||||
.setAutoDetectionEnabledSetting(autoDetectionEnabled)
|
||||
.setLocationEnabledSetting(geoDetectionEnabled)
|
||||
|
||||
@@ -60,6 +60,7 @@ import java.util.function.Function;
|
||||
public class TimeZoneDetectorStrategyImplTest {
|
||||
|
||||
private static final @UserIdInt int USER_ID = 9876;
|
||||
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234;
|
||||
/** A time zone used for initialization that does not occur elsewhere in tests. */
|
||||
private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC";
|
||||
private static final int SLOT_INDEX1 = 10000;
|
||||
@@ -89,6 +90,7 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
.setUserConfigAllowed(false)
|
||||
.setTelephonyDetectionFeatureSupported(true)
|
||||
.setGeoDetectionFeatureSupported(true)
|
||||
.setTelephonyFallbackSupported(false)
|
||||
.setAutoDetectionEnabledSetting(false)
|
||||
.setLocationEnabledSetting(true)
|
||||
.setGeoDetectionEnabledSetting(false)
|
||||
@@ -99,6 +101,7 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
.setUserConfigAllowed(false)
|
||||
.setTelephonyDetectionFeatureSupported(true)
|
||||
.setGeoDetectionFeatureSupported(true)
|
||||
.setTelephonyFallbackSupported(false)
|
||||
.setAutoDetectionEnabledSetting(true)
|
||||
.setLocationEnabledSetting(true)
|
||||
.setGeoDetectionEnabledSetting(true)
|
||||
@@ -109,6 +112,7 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
.setUserConfigAllowed(true)
|
||||
.setTelephonyDetectionFeatureSupported(false)
|
||||
.setGeoDetectionFeatureSupported(false)
|
||||
.setTelephonyFallbackSupported(false)
|
||||
.setAutoDetectionEnabledSetting(false)
|
||||
.setLocationEnabledSetting(true)
|
||||
.setGeoDetectionEnabledSetting(false)
|
||||
@@ -119,6 +123,7 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
.setUserConfigAllowed(true)
|
||||
.setTelephonyDetectionFeatureSupported(true)
|
||||
.setGeoDetectionFeatureSupported(true)
|
||||
.setTelephonyFallbackSupported(false)
|
||||
.setAutoDetectionEnabledSetting(false)
|
||||
.setLocationEnabledSetting(true)
|
||||
.setGeoDetectionEnabledSetting(false)
|
||||
@@ -128,6 +133,7 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
new ConfigurationInternal.Builder(USER_ID)
|
||||
.setTelephonyDetectionFeatureSupported(true)
|
||||
.setGeoDetectionFeatureSupported(true)
|
||||
.setTelephonyFallbackSupported(false)
|
||||
.setUserConfigAllowed(true)
|
||||
.setAutoDetectionEnabledSetting(true)
|
||||
.setLocationEnabledSetting(true)
|
||||
@@ -138,6 +144,7 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
new ConfigurationInternal.Builder(USER_ID)
|
||||
.setTelephonyDetectionFeatureSupported(true)
|
||||
.setGeoDetectionFeatureSupported(true)
|
||||
.setTelephonyFallbackSupported(false)
|
||||
.setUserConfigAllowed(true)
|
||||
.setAutoDetectionEnabledSetting(true)
|
||||
.setLocationEnabledSetting(true)
|
||||
@@ -147,9 +154,6 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
|
||||
private FakeEnvironment mFakeEnvironment;
|
||||
|
||||
// A fake source of time for suggestions. This will typically be incremented after every use.
|
||||
@ElapsedRealtimeLong private long mElapsedRealtimeMillis;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mFakeEnvironment = new FakeEnvironment();
|
||||
@@ -752,6 +756,204 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTelephonyFallback() {
|
||||
ConfigurationInternal config = new ConfigurationInternal.Builder(
|
||||
CONFIG_AUTO_ENABLED_GEO_ENABLED)
|
||||
.setTelephonyFallbackSupported(true)
|
||||
.build();
|
||||
|
||||
Script script = new Script()
|
||||
.initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
|
||||
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
|
||||
.simulateConfigurationInternalChange(config)
|
||||
.resetConfigurationTracking();
|
||||
|
||||
// Confirm initial state is as expected.
|
||||
script.verifyTelephonyFallbackIsEnabled(true)
|
||||
.verifyTimeZoneNotChanged();
|
||||
|
||||
// Although geolocation detection is enabled, telephony fallback should be used initially
|
||||
// and until a suitable "certain" geolocation suggestion is received.
|
||||
{
|
||||
TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
|
||||
SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
|
||||
"Europe/Paris");
|
||||
script.simulateIncrementClock()
|
||||
.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
|
||||
.verifyTimeZoneChangedAndReset(telephonySuggestion)
|
||||
.verifyTelephonyFallbackIsEnabled(true);
|
||||
}
|
||||
|
||||
// Receiving an "uncertain" geolocation suggestion should have no effect.
|
||||
{
|
||||
GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
|
||||
createUncertainGeolocationSuggestion();
|
||||
script.simulateIncrementClock()
|
||||
.simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
|
||||
.verifyTimeZoneNotChanged()
|
||||
.verifyTelephonyFallbackIsEnabled(true);
|
||||
}
|
||||
|
||||
// Receiving a "certain" geolocation suggestion should disable telephony fallback mode.
|
||||
{
|
||||
GeolocationTimeZoneSuggestion geolocationSuggestion =
|
||||
createCertainGeolocationSuggestion("Europe/London");
|
||||
script.simulateIncrementClock()
|
||||
.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
|
||||
.verifyTimeZoneChangedAndReset(geolocationSuggestion)
|
||||
.verifyTelephonyFallbackIsEnabled(false);
|
||||
}
|
||||
|
||||
// Used to record the last telephony suggestion received, which will be used when fallback
|
||||
// takes place.
|
||||
TelephonyTimeZoneSuggestion lastTelephonySuggestion;
|
||||
|
||||
// Telephony suggestions should now be ignored and geolocation detection is "in control".
|
||||
{
|
||||
TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
|
||||
SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
|
||||
"Europe/Berlin");
|
||||
script.simulateIncrementClock()
|
||||
.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
|
||||
.verifyTimeZoneNotChanged()
|
||||
.verifyTelephonyFallbackIsEnabled(false);
|
||||
lastTelephonySuggestion = telephonySuggestion;
|
||||
}
|
||||
|
||||
// Geolocation suggestions should continue to be used as normal (previous telephony
|
||||
// suggestions are not used, even when the geolocation suggestion is uncertain).
|
||||
{
|
||||
GeolocationTimeZoneSuggestion geolocationSuggestion =
|
||||
createCertainGeolocationSuggestion("Europe/Rome");
|
||||
script.simulateIncrementClock()
|
||||
.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
|
||||
.verifyTimeZoneChangedAndReset(geolocationSuggestion)
|
||||
.verifyTelephonyFallbackIsEnabled(false);
|
||||
|
||||
GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
|
||||
createUncertainGeolocationSuggestion();
|
||||
script.simulateIncrementClock()
|
||||
.simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
|
||||
.verifyTimeZoneNotChanged()
|
||||
.verifyTelephonyFallbackIsEnabled(false);
|
||||
|
||||
script.simulateIncrementClock()
|
||||
.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
|
||||
// No change needed, device will already be set to Europe/Rome.
|
||||
.verifyTimeZoneNotChanged()
|
||||
.verifyTelephonyFallbackIsEnabled(false);
|
||||
}
|
||||
|
||||
// Enable telephony fallback. Nothing will change, because the geolocation is still certain,
|
||||
// but fallback will remain enabled.
|
||||
{
|
||||
script.simulateIncrementClock()
|
||||
.simulateEnableTelephonyFallback()
|
||||
.verifyTimeZoneNotChanged()
|
||||
.verifyTelephonyFallbackIsEnabled(true);
|
||||
}
|
||||
|
||||
// Make the geolocation algorithm uncertain.
|
||||
{
|
||||
GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
|
||||
createUncertainGeolocationSuggestion();
|
||||
script.simulateIncrementClock()
|
||||
.simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
|
||||
.verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
|
||||
.verifyTelephonyFallbackIsEnabled(true);
|
||||
}
|
||||
|
||||
// Make the geolocation algorithm certain, disabling telephony fallback.
|
||||
{
|
||||
GeolocationTimeZoneSuggestion geolocationSuggestion =
|
||||
createCertainGeolocationSuggestion("Europe/Lisbon");
|
||||
script.simulateIncrementClock()
|
||||
.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
|
||||
.verifyTimeZoneChangedAndReset(geolocationSuggestion)
|
||||
.verifyTelephonyFallbackIsEnabled(false);
|
||||
|
||||
}
|
||||
|
||||
// Demonstrate what happens when geolocation is uncertain when telephony fallback is
|
||||
// enabled.
|
||||
{
|
||||
GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
|
||||
createUncertainGeolocationSuggestion();
|
||||
script.simulateIncrementClock()
|
||||
.simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
|
||||
.verifyTimeZoneNotChanged()
|
||||
.verifyTelephonyFallbackIsEnabled(false)
|
||||
.simulateEnableTelephonyFallback()
|
||||
.verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
|
||||
.verifyTelephonyFallbackIsEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTelephonyFallback_noTelephonySuggestionToFallBackTo() {
|
||||
ConfigurationInternal config = new ConfigurationInternal.Builder(
|
||||
CONFIG_AUTO_ENABLED_GEO_ENABLED)
|
||||
.setTelephonyFallbackSupported(true)
|
||||
.build();
|
||||
|
||||
Script script = new Script()
|
||||
.initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
|
||||
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
|
||||
.simulateConfigurationInternalChange(config)
|
||||
.resetConfigurationTracking();
|
||||
|
||||
// Confirm initial state is as expected.
|
||||
script.verifyTelephonyFallbackIsEnabled(true)
|
||||
.verifyTimeZoneNotChanged();
|
||||
|
||||
// Receiving an "uncertain" geolocation suggestion should have no effect.
|
||||
{
|
||||
GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
|
||||
createUncertainGeolocationSuggestion();
|
||||
script.simulateIncrementClock()
|
||||
.simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
|
||||
.verifyTimeZoneNotChanged()
|
||||
.verifyTelephonyFallbackIsEnabled(true);
|
||||
}
|
||||
|
||||
// Make an uncertain geolocation suggestion, there is no telephony suggestion to fall back
|
||||
// to
|
||||
{
|
||||
GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
|
||||
createUncertainGeolocationSuggestion();
|
||||
script.simulateIncrementClock()
|
||||
.simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
|
||||
.verifyTimeZoneNotChanged()
|
||||
.verifyTelephonyFallbackIsEnabled(true);
|
||||
}
|
||||
|
||||
// Similar to the case above, but force a fallback attempt after making a "certain"
|
||||
// geolocation suggestion.
|
||||
// Geolocation suggestions should continue to be used as normal (previous telephony
|
||||
// suggestions are not used, even when the geolocation suggestion is uncertain).
|
||||
{
|
||||
GeolocationTimeZoneSuggestion geolocationSuggestion =
|
||||
createCertainGeolocationSuggestion("Europe/Rome");
|
||||
script.simulateIncrementClock()
|
||||
.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
|
||||
.verifyTimeZoneChangedAndReset(geolocationSuggestion)
|
||||
.verifyTelephonyFallbackIsEnabled(false);
|
||||
|
||||
GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
|
||||
createUncertainGeolocationSuggestion();
|
||||
script.simulateIncrementClock()
|
||||
.simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
|
||||
.verifyTimeZoneNotChanged()
|
||||
.verifyTelephonyFallbackIsEnabled(false);
|
||||
|
||||
script.simulateIncrementClock()
|
||||
.simulateEnableTelephonyFallback()
|
||||
.verifyTimeZoneNotChanged()
|
||||
.verifyTelephonyFallbackIsEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateMetricsState() {
|
||||
ConfigurationInternal expectedInternalConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
|
||||
@@ -835,6 +1037,8 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
assertEquals(config.isTelephonyDetectionSupported(),
|
||||
actualState.isTelephonyDetectionSupported());
|
||||
assertEquals(config.isGeoDetectionSupported(), actualState.isGeoDetectionSupported());
|
||||
assertEquals(config.isTelephonyFallbackSupported(),
|
||||
actualState.isTelephonyTimeZoneFallbackSupported());
|
||||
assertEquals(config.getAutoDetectionEnabledSetting(),
|
||||
actualState.getAutoDetectionEnabledSetting());
|
||||
assertEquals(config.getGeoDetectionEnabledSetting(),
|
||||
@@ -865,7 +1069,7 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
|
||||
private GeolocationTimeZoneSuggestion createUncertainGeolocationSuggestion() {
|
||||
return GeolocationTimeZoneSuggestion.createCertainSuggestion(
|
||||
mElapsedRealtimeMillis++, null);
|
||||
mFakeEnvironment.elapsedRealtimeMillis(), null);
|
||||
}
|
||||
|
||||
private GeolocationTimeZoneSuggestion createCertainGeolocationSuggestion(
|
||||
@@ -874,7 +1078,7 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
|
||||
GeolocationTimeZoneSuggestion suggestion =
|
||||
GeolocationTimeZoneSuggestion.createCertainSuggestion(
|
||||
mElapsedRealtimeMillis++, Arrays.asList(zoneIds));
|
||||
mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds));
|
||||
suggestion.addDebugInfo("Test suggestion");
|
||||
return suggestion;
|
||||
}
|
||||
@@ -883,16 +1087,25 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
|
||||
private final TestState<String> mTimeZoneId = new TestState<>();
|
||||
private ConfigurationInternal mConfigurationInternal;
|
||||
private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
|
||||
private ConfigurationChangeListener mConfigurationInternalChangeListener;
|
||||
|
||||
void initializeConfig(ConfigurationInternal configurationInternal) {
|
||||
mConfigurationInternal = configurationInternal;
|
||||
}
|
||||
|
||||
void initializeClock(@ElapsedRealtimeLong long elapsedRealtimeMillis) {
|
||||
mElapsedRealtimeMillis = elapsedRealtimeMillis;
|
||||
}
|
||||
|
||||
void initializeTimeZoneSetting(String zoneId) {
|
||||
mTimeZoneId.init(zoneId);
|
||||
}
|
||||
|
||||
void incrementClock() {
|
||||
mElapsedRealtimeMillis++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
|
||||
mConfigurationInternalChangeListener = listener;
|
||||
@@ -936,6 +1149,12 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
void commitAllChanges() {
|
||||
mTimeZoneId.commitLatest();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ElapsedRealtimeLong
|
||||
public long elapsedRealtimeMillis() {
|
||||
return mElapsedRealtimeMillis;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -949,6 +1168,16 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
return this;
|
||||
}
|
||||
|
||||
Script initializeClock(long elapsedRealtimeMillis) {
|
||||
mFakeEnvironment.initializeClock(elapsedRealtimeMillis);
|
||||
return this;
|
||||
}
|
||||
|
||||
Script simulateIncrementClock() {
|
||||
mFakeEnvironment.incrementClock();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the user / user's configuration changing.
|
||||
*/
|
||||
@@ -1008,6 +1237,15 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the time zone detection strategty receiving a signal that allows it to do
|
||||
* telephony fallback.
|
||||
*/
|
||||
Script simulateEnableTelephonyFallback() {
|
||||
mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that the device's time zone has not been set by previous actions since the test
|
||||
* state was last reset.
|
||||
@@ -1044,6 +1282,13 @@ public class TimeZoneDetectorStrategyImplTest {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Verifies the state for telephony fallback. */
|
||||
Script verifyTelephonyFallbackIsEnabled(boolean expectedEnabled) {
|
||||
assertEquals(expectedEnabled,
|
||||
mTimeZoneDetectorStrategy.isTelephonyFallbackEnabledForTests());
|
||||
return this;
|
||||
}
|
||||
|
||||
Script resetConfigurationTracking() {
|
||||
mFakeEnvironment.commitAllChanges();
|
||||
return this;
|
||||
|
||||
@@ -46,6 +46,7 @@ final class TestSupport {
|
||||
.setUserConfigAllowed(true)
|
||||
.setTelephonyDetectionFeatureSupported(true)
|
||||
.setGeoDetectionFeatureSupported(true)
|
||||
.setTelephonyFallbackSupported(false)
|
||||
.setAutoDetectionEnabledSetting(true)
|
||||
.setLocationEnabledSetting(true)
|
||||
.setGeoDetectionEnabledSetting(geoDetectionEnabledSetting)
|
||||
|
||||
Reference in New Issue
Block a user