Merge "Add a manual path for setting time zone"
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.app.timezonedetector;
|
||||
|
||||
import android.app.timezonedetector.ManualTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.PhoneTimeZoneSuggestion;
|
||||
|
||||
/**
|
||||
@@ -32,5 +33,6 @@ import android.app.timezonedetector.PhoneTimeZoneSuggestion;
|
||||
* {@hide}
|
||||
*/
|
||||
interface ITimeZoneDetectorService {
|
||||
void suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
|
||||
void suggestPhoneTimeZone(in PhoneTimeZoneSuggestion timeZoneSuggestion);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 android.app.timezonedetector;
|
||||
|
||||
parcelable ManualTimeZoneSuggestion;
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 android.app.timezonedetector;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A time signal from a manual (user provided) source. The value consists of the number of
|
||||
* milliseconds elapsed since 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime
|
||||
* clock when that number was established. The elapsed realtime clock is considered accurate but
|
||||
* volatile, so time signals must not be persisted across device resets.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class ManualTimeZoneSuggestion implements Parcelable {
|
||||
|
||||
public static final @NonNull Creator<ManualTimeZoneSuggestion> CREATOR =
|
||||
new Creator<ManualTimeZoneSuggestion>() {
|
||||
public ManualTimeZoneSuggestion createFromParcel(Parcel in) {
|
||||
return ManualTimeZoneSuggestion.createFromParcel(in);
|
||||
}
|
||||
|
||||
public ManualTimeZoneSuggestion[] newArray(int size) {
|
||||
return new ManualTimeZoneSuggestion[size];
|
||||
}
|
||||
};
|
||||
|
||||
@NonNull
|
||||
private final String mZoneId;
|
||||
@Nullable
|
||||
private ArrayList<String> mDebugInfo;
|
||||
|
||||
public ManualTimeZoneSuggestion(@NonNull String zoneId) {
|
||||
mZoneId = Objects.requireNonNull(zoneId);
|
||||
}
|
||||
|
||||
private static ManualTimeZoneSuggestion createFromParcel(Parcel in) {
|
||||
String zoneId = in.readString();
|
||||
ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(zoneId);
|
||||
@SuppressWarnings("unchecked")
|
||||
ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
|
||||
suggestion.mDebugInfo = debugInfo;
|
||||
return suggestion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeString(mZoneId);
|
||||
dest.writeList(mDebugInfo);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getZoneId() {
|
||||
return mZoneId;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<String> getDebugInfo() {
|
||||
return mDebugInfo == null
|
||||
? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates information with the instance that can be useful for debugging / logging. The
|
||||
* information is present in {@link #toString()} but is not considered for
|
||||
* {@link #equals(Object)} and {@link #hashCode()}.
|
||||
*/
|
||||
public void addDebugInfo(String... debugInfos) {
|
||||
if (mDebugInfo == null) {
|
||||
mDebugInfo = new ArrayList<>();
|
||||
}
|
||||
mDebugInfo.addAll(Arrays.asList(debugInfos));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ManualTimeZoneSuggestion
|
||||
that = (ManualTimeZoneSuggestion) o;
|
||||
return Objects.equals(mZoneId, that.mZoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mZoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ManualTimeSuggestion{"
|
||||
+ "mZoneId=" + mZoneId
|
||||
+ ", mDebugInfo=" + mDebugInfo
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2019 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 android.app.timezonedetector;
|
||||
|
||||
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
|
||||
import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ManualTimeZoneSuggestionTest {
|
||||
|
||||
private static final String ARBITRARY_ZONE_ID1 = "Europe/London";
|
||||
private static final String ARBITRARY_ZONE_ID2 = "Europe/Paris";
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
ManualTimeZoneSuggestion one = new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID1);
|
||||
assertEquals(one, one);
|
||||
|
||||
ManualTimeZoneSuggestion two = new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID1);
|
||||
assertEquals(one, two);
|
||||
assertEquals(two, one);
|
||||
|
||||
ManualTimeZoneSuggestion three = new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID2);
|
||||
assertNotEquals(one, three);
|
||||
assertNotEquals(three, one);
|
||||
|
||||
// DebugInfo must not be considered in equals().
|
||||
one.addDebugInfo("Debug info 1");
|
||||
two.addDebugInfo("Debug info 2");
|
||||
assertEquals(one, two);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParcelable() {
|
||||
ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID1);
|
||||
assertRoundTripParcelable(suggestion);
|
||||
|
||||
// DebugInfo should also be stored (but is not checked by equals()
|
||||
suggestion.addDebugInfo("This is debug info");
|
||||
ManualTimeZoneSuggestion rtSuggestion = roundTripParcelable(suggestion);
|
||||
assertEquals(suggestion.getDebugInfo(), rtSuggestion.getDebugInfo());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2019 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 android.app.timezonedetector;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/** Utility methods related to {@link Parcelable} objects used in several tests. */
|
||||
public final class ParcelableTestSupport {
|
||||
|
||||
private ParcelableTestSupport() {}
|
||||
|
||||
/** Returns the result of parceling and unparceling the argument. */
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Parcelable> T roundTripParcelable(T parcelable) {
|
||||
Parcel parcel = Parcel.obtain();
|
||||
parcel.writeTypedObject(parcelable, 0);
|
||||
parcel.setDataPosition(0);
|
||||
|
||||
Parcelable.Creator<T> creator;
|
||||
try {
|
||||
Field creatorField = parcelable.getClass().getField("CREATOR");
|
||||
creator = (Parcelable.Creator<T>) creatorField.get(null);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
T toReturn = parcel.readTypedObject(creator);
|
||||
parcel.recycle();
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
public static <T extends Parcelable> void assertRoundTripParcelable(T instance) {
|
||||
assertEquals(instance, roundTripParcelable(instance));
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
package android.app.timezonedetector;
|
||||
|
||||
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
|
||||
import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class PhoneTimeZoneSuggestionTest {
|
||||
@@ -152,19 +152,4 @@ public class PhoneTimeZoneSuggestionTest {
|
||||
assertEquals(suggestion1, suggestion1_2);
|
||||
assertTrue(suggestion1_2.getDebugInfo().contains(debugString));
|
||||
}
|
||||
|
||||
private static void assertRoundTripParcelable(PhoneTimeZoneSuggestion instance) {
|
||||
assertEquals(instance, roundTripParcelable(instance));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends Parcelable> T roundTripParcelable(T one) {
|
||||
Parcel parcel = Parcel.obtain();
|
||||
parcel.writeTypedObject(one, 0);
|
||||
parcel.setDataPosition(0);
|
||||
|
||||
T toReturn = (T) parcel.readTypedObject(PhoneTimeZoneSuggestion.CREATOR);
|
||||
parcel.recycle();
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTimeZoneDetectionEnabled() {
|
||||
public boolean isAutoTimeZoneDetectionEnabled() {
|
||||
return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
|
||||
}
|
||||
|
||||
@@ -66,14 +66,16 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDeviceTimeZone(String zoneId) {
|
||||
public void setDeviceTimeZone(String zoneId, boolean sendNetworkBroadcast) {
|
||||
AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
|
||||
alarmManager.setTimeZone(zoneId);
|
||||
|
||||
// TODO Nothing in the platform appears to listen for this. Remove it.
|
||||
Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
|
||||
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
|
||||
intent.putExtra("time-zone", zoneId);
|
||||
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
|
||||
if (sendNetworkBroadcast) {
|
||||
// TODO Nothing in the platform appears to listen for this. Remove it.
|
||||
Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
|
||||
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
|
||||
intent.putExtra("time-zone", zoneId);
|
||||
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.server.timezonedetector;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.timezonedetector.ITimeZoneDetectorService;
|
||||
import android.app.timezonedetector.ManualTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.PhoneTimeZoneSuggestion;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
@@ -28,7 +29,6 @@ import android.provider.Settings;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.FgThread;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
@@ -75,7 +75,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
|
||||
new ContentObserver(handler) {
|
||||
public void onChange(boolean selfChange) {
|
||||
timeZoneDetectorStrategy.handleTimeZoneDetectionChange();
|
||||
timeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -90,9 +90,17 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
|
||||
enforceSuggestManualTimeZonePermission();
|
||||
Objects.requireNonNull(timeZoneSuggestion);
|
||||
|
||||
mHandler.post(() -> mTimeZoneDetectorStrategy.suggestManualTimeZone(timeZoneSuggestion));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) {
|
||||
enforceSetTimeZonePermission();
|
||||
enforceSuggestPhoneTimeZonePermission();
|
||||
Objects.requireNonNull(timeZoneSuggestion);
|
||||
|
||||
mHandler.post(() -> mTimeZoneDetectorStrategy.suggestPhoneTimeZone(timeZoneSuggestion));
|
||||
@@ -103,13 +111,17 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
|
||||
@Nullable String[] args) {
|
||||
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
|
||||
|
||||
mTimeZoneDetectorStrategy.dumpState(pw);
|
||||
mTimeZoneDetectorStrategy.dumpLogs(new IndentingPrintWriter(pw, " "));
|
||||
mTimeZoneDetectorStrategy.dumpState(pw, args);
|
||||
}
|
||||
|
||||
private void enforceSetTimeZonePermission() {
|
||||
private void enforceSuggestPhoneTimeZonePermission() {
|
||||
mContext.enforceCallingPermission(
|
||||
android.Manifest.permission.SET_TIME_ZONE, "set time zone");
|
||||
}
|
||||
|
||||
private void enforceSuggestManualTimeZonePermission() {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.SET_TIME_ZONE, "set time zone");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,10 @@ import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTI
|
||||
import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
|
||||
import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.timezonedetector.ManualTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.PhoneTimeZoneSuggestion;
|
||||
import android.content.Context;
|
||||
import android.util.ArrayMap;
|
||||
@@ -34,22 +36,34 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A singleton, stateful time zone detection strategy that is aware of multiple phone devices. It
|
||||
* keeps track of the most recent suggestion from each phone and it uses the best based on a scoring
|
||||
* algorithm. If several phones provide the same score then the phone with the lowest numeric ID
|
||||
* "wins". If the situation changes and it is no longer possible to be confident about the time
|
||||
* zone, phones must submit an empty suggestion in order to "withdraw" their previous suggestion.
|
||||
* A singleton, stateful time zone detection strategy that is aware of user (manual) suggestions and
|
||||
* suggestions from multiple phone devices. Suggestions are acted on or ignored as needed, dependent
|
||||
* on the current "auto time zone detection" setting.
|
||||
*
|
||||
* <p>For automatic detection it keeps track of the most recent suggestion from each phone it uses
|
||||
* the best suggestion based on a scoring algorithm. If several phones provide the same score then
|
||||
* the phone with the lowest numeric ID "wins". If the situation changes and it is no longer
|
||||
* possible to be confident about the time zone, phones must submit an empty suggestion in order to
|
||||
* "withdraw" their previous suggestion.
|
||||
*/
|
||||
public class TimeZoneDetectorStrategy {
|
||||
|
||||
/**
|
||||
* Used by {@link TimeZoneDetectorStrategy} to interact with the surrounding service. It can be
|
||||
* faked for tests.
|
||||
*
|
||||
* <p>Note: Because the system properties-derived values like
|
||||
* {@link #isAutoTimeZoneDetectionEnabled()}, {@link #isAutoTimeZoneDetectionEnabled()},
|
||||
* {@link #getDeviceTimeZone()} can be modified independently and from different threads (and
|
||||
* processes!), their use are prone to race conditions. That will be true until the
|
||||
* responsibility for setting their values is moved to {@link TimeZoneDetectorStrategy}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public interface Callback {
|
||||
@@ -57,7 +71,7 @@ public class TimeZoneDetectorStrategy {
|
||||
/**
|
||||
* Returns true if automatic time zone detection is enabled in settings.
|
||||
*/
|
||||
boolean isTimeZoneDetectionEnabled();
|
||||
boolean isAutoTimeZoneDetectionEnabled();
|
||||
|
||||
/**
|
||||
* Returns true if the device has had an explicit time zone set.
|
||||
@@ -72,22 +86,34 @@ public class TimeZoneDetectorStrategy {
|
||||
/**
|
||||
* Sets the device's time zone.
|
||||
*/
|
||||
void setDeviceTimeZone(@NonNull String zoneId);
|
||||
void setDeviceTimeZone(@NonNull String zoneId, boolean sendNetworkBroadcast);
|
||||
}
|
||||
|
||||
static final String LOG_TAG = "TimeZoneDetectorStrategy";
|
||||
static final boolean DBG = false;
|
||||
private static final String LOG_TAG = "TimeZoneDetectorStrategy";
|
||||
private static final boolean DBG = false;
|
||||
|
||||
@IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Origin {}
|
||||
|
||||
/** Used when a time value originated from a telephony signal. */
|
||||
@Origin
|
||||
private static final int ORIGIN_PHONE = 1;
|
||||
|
||||
/** Used when a time value originated from a user / manual settings. */
|
||||
@Origin
|
||||
private static final int ORIGIN_MANUAL = 2;
|
||||
|
||||
/**
|
||||
* The abstract score for an empty or invalid suggestion.
|
||||
* The abstract score for an empty or invalid phone suggestion.
|
||||
*
|
||||
* Used to score suggestions where there is no zone.
|
||||
* Used to score phone suggestions where there is no zone.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static final int SCORE_NONE = 0;
|
||||
public static final int PHONE_SCORE_NONE = 0;
|
||||
|
||||
/**
|
||||
* The abstract score for a low quality suggestion.
|
||||
* The abstract score for a low quality phone suggestion.
|
||||
*
|
||||
* Used to score suggestions where:
|
||||
* The suggested zone ID is one of several possibilities, and the possibilities have different
|
||||
@@ -96,10 +122,10 @@ public class TimeZoneDetectorStrategy {
|
||||
* You would have to be quite desperate to want to use this choice.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static final int SCORE_LOW = 1;
|
||||
public static final int PHONE_SCORE_LOW = 1;
|
||||
|
||||
/**
|
||||
* The abstract score for a medium quality suggestion.
|
||||
* The abstract score for a medium quality phone suggestion.
|
||||
*
|
||||
* Used for:
|
||||
* The suggested zone ID is one of several possibilities but at least the possibilities have the
|
||||
@@ -107,33 +133,36 @@ public class TimeZoneDetectorStrategy {
|
||||
* switch to DST at the wrong time and (for example) their calendar events.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static final int SCORE_MEDIUM = 2;
|
||||
public static final int PHONE_SCORE_MEDIUM = 2;
|
||||
|
||||
/**
|
||||
* The abstract score for a high quality suggestion.
|
||||
* The abstract score for a high quality phone suggestion.
|
||||
*
|
||||
* Used for:
|
||||
* The suggestion was for one zone ID and the answer was unambiguous and likely correct given
|
||||
* the info available.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static final int SCORE_HIGH = 3;
|
||||
public static final int PHONE_SCORE_HIGH = 3;
|
||||
|
||||
/**
|
||||
* The abstract score for a highest quality suggestion.
|
||||
* The abstract score for a highest quality phone suggestion.
|
||||
*
|
||||
* Used for:
|
||||
* Suggestions that must "win" because they constitute test or emulator zone ID.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static final int SCORE_HIGHEST = 4;
|
||||
public static final int PHONE_SCORE_HIGHEST = 4;
|
||||
|
||||
/** The threshold at which suggestions are good enough to use to set the device's time zone. */
|
||||
/**
|
||||
* The threshold at which phone suggestions are good enough to use to set the device's time
|
||||
* zone.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static final int SCORE_USAGE_THRESHOLD = SCORE_MEDIUM;
|
||||
public static final int PHONE_SCORE_USAGE_THRESHOLD = PHONE_SCORE_MEDIUM;
|
||||
|
||||
/** The number of previous phone suggestions to keep for each ID (for use during debugging). */
|
||||
private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30;
|
||||
private static final int KEEP_PHONE_SUGGESTION_HISTORY_SIZE = 30;
|
||||
|
||||
@NonNull
|
||||
private final Callback mCallback;
|
||||
@@ -146,23 +175,15 @@ public class TimeZoneDetectorStrategy {
|
||||
private final LocalLog mTimeZoneChangesLog = new LocalLog(30);
|
||||
|
||||
/**
|
||||
* A mapping from phoneId to a linked list of time zone suggestions (the head being the latest).
|
||||
* We typically expect one or two entries in this Map: devices will have a small number
|
||||
* A mapping from phoneId to a linked list of phone time zone suggestions (the head being the
|
||||
* latest). We typically expect one or two entries in this Map: devices will have a small number
|
||||
* of telephony devices and phoneIds are assumed to be stable. The LinkedList associated with
|
||||
* the ID will not exceed {@link #KEEP_SUGGESTION_HISTORY_SIZE} in size.
|
||||
* the ID will not exceed {@link #KEEP_PHONE_SUGGESTION_HISTORY_SIZE} in size.
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private ArrayMap<Integer, LinkedList<QualifiedPhoneTimeZoneSuggestion>> mSuggestionByPhoneId =
|
||||
new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* The most recent best guess of time zone from all phones. Can be {@code null} to indicate
|
||||
* there would be no current suggestion.
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
@Nullable
|
||||
private QualifiedPhoneTimeZoneSuggestion mCurrentSuggestion;
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link TimeZoneDetectorStrategy}.
|
||||
*/
|
||||
@@ -176,56 +197,68 @@ public class TimeZoneDetectorStrategy {
|
||||
mCallback = Objects.requireNonNull(callback);
|
||||
}
|
||||
|
||||
/** Process the suggested manually- / user-entered time zone. */
|
||||
public synchronized void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion) {
|
||||
Objects.requireNonNull(suggestion);
|
||||
|
||||
String timeZoneId = suggestion.getZoneId();
|
||||
String cause = "Manual time suggestion received: suggestion=" + suggestion;
|
||||
setDeviceTimeZoneIfRequired(ORIGIN_MANUAL, timeZoneId, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggests a time zone for the device, or withdraws a previous suggestion if
|
||||
* {@link PhoneTimeZoneSuggestion#getZoneId()} is {@code null}. The suggestion is scoped to a
|
||||
* specific {@link PhoneTimeZoneSuggestion#getPhoneId() phone}.
|
||||
* See {@link PhoneTimeZoneSuggestion} for an explanation of the metadata associated with a
|
||||
* suggestion. The service uses suggestions to decide whether to modify the device's time zone
|
||||
* suggestion. The strategy uses suggestions to decide whether to modify the device's time zone
|
||||
* setting and what to set it to.
|
||||
*/
|
||||
public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion newSuggestion) {
|
||||
public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion) {
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "suggestPhoneTimeZone: newSuggestion=" + newSuggestion);
|
||||
Slog.d(LOG_TAG, "Phone suggestion received. newSuggestion=" + suggestion);
|
||||
}
|
||||
Objects.requireNonNull(newSuggestion);
|
||||
Objects.requireNonNull(suggestion);
|
||||
|
||||
int score = scoreSuggestion(newSuggestion);
|
||||
// Score the suggestion.
|
||||
int score = scorePhoneSuggestion(suggestion);
|
||||
QualifiedPhoneTimeZoneSuggestion scoredSuggestion =
|
||||
new QualifiedPhoneTimeZoneSuggestion(newSuggestion, score);
|
||||
new QualifiedPhoneTimeZoneSuggestion(suggestion, score);
|
||||
|
||||
// Record the suggestion against the correct phoneId.
|
||||
// Store the suggestion against the correct phoneId.
|
||||
LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
|
||||
mSuggestionByPhoneId.get(newSuggestion.getPhoneId());
|
||||
mSuggestionByPhoneId.get(suggestion.getPhoneId());
|
||||
if (suggestions == null) {
|
||||
suggestions = new LinkedList<>();
|
||||
mSuggestionByPhoneId.put(newSuggestion.getPhoneId(), suggestions);
|
||||
mSuggestionByPhoneId.put(suggestion.getPhoneId(), suggestions);
|
||||
}
|
||||
suggestions.addFirst(scoredSuggestion);
|
||||
if (suggestions.size() > KEEP_SUGGESTION_HISTORY_SIZE) {
|
||||
if (suggestions.size() > KEEP_PHONE_SUGGESTION_HISTORY_SIZE) {
|
||||
suggestions.removeLast();
|
||||
}
|
||||
|
||||
// Now run the competition between the phones' suggestions.
|
||||
doTimeZoneDetection();
|
||||
// Now perform auto time zone detection. The new suggestion may be used to modify the time
|
||||
// zone setting.
|
||||
String reason = "New phone time suggested. suggestion=" + suggestion;
|
||||
doAutoTimeZoneDetection(reason);
|
||||
}
|
||||
|
||||
private static int scoreSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) {
|
||||
private static int scorePhoneSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) {
|
||||
int score;
|
||||
if (suggestion.getZoneId() == null) {
|
||||
score = SCORE_NONE;
|
||||
score = PHONE_SCORE_NONE;
|
||||
} else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY
|
||||
|| suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) {
|
||||
// Handle emulator / test cases : These suggestions should always just be used.
|
||||
score = SCORE_HIGHEST;
|
||||
score = PHONE_SCORE_HIGHEST;
|
||||
} else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) {
|
||||
score = SCORE_HIGH;
|
||||
score = PHONE_SCORE_HIGH;
|
||||
} else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) {
|
||||
// The suggestion may be wrong, but at least the offset should be correct.
|
||||
score = SCORE_MEDIUM;
|
||||
score = PHONE_SCORE_MEDIUM;
|
||||
} else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) {
|
||||
// The suggestion has a good chance of being wrong.
|
||||
score = SCORE_LOW;
|
||||
score = PHONE_SCORE_LOW;
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -235,47 +268,46 @@ public class TimeZoneDetectorStrategy {
|
||||
/**
|
||||
* Finds the best available time zone suggestion from all phones. If it is high-enough quality
|
||||
* and automatic time zone detection is enabled then it will be set on the device. The outcome
|
||||
* can be that this service becomes / remains un-opinionated and nothing is set.
|
||||
* can be that this strategy becomes / remains un-opinionated and nothing is set.
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private void doTimeZoneDetection() {
|
||||
QualifiedPhoneTimeZoneSuggestion bestSuggestion = findBestSuggestion();
|
||||
boolean timeZoneDetectionEnabled = mCallback.isTimeZoneDetectionEnabled();
|
||||
private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
|
||||
if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
|
||||
// Avoid doing unnecessary work with this (race-prone) check.
|
||||
return;
|
||||
}
|
||||
|
||||
QualifiedPhoneTimeZoneSuggestion bestPhoneSuggestion = findBestPhoneSuggestion();
|
||||
|
||||
// Work out what to do with the best suggestion.
|
||||
if (bestSuggestion == null) {
|
||||
// There is no suggestion. Become un-opinionated.
|
||||
if (bestPhoneSuggestion == null) {
|
||||
// There is no phone suggestion available at all. Become un-opinionated.
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "doTimeZoneDetection: No good suggestion."
|
||||
+ " bestSuggestion=null"
|
||||
+ ", timeZoneDetectionEnabled=" + timeZoneDetectionEnabled);
|
||||
Slog.d(LOG_TAG, "Could not determine time zone: No best phone suggestion."
|
||||
+ " detectionReason=" + detectionReason);
|
||||
}
|
||||
mCurrentSuggestion = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case handling for uninitialized devices. This should only happen once.
|
||||
String newZoneId = bestSuggestion.suggestion.getZoneId();
|
||||
String newZoneId = bestPhoneSuggestion.suggestion.getZoneId();
|
||||
if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) {
|
||||
Slog.i(LOG_TAG, "doTimeZoneDetection: Device has no time zone set so might set the"
|
||||
+ " device to the best available suggestion."
|
||||
+ " bestSuggestion=" + bestSuggestion
|
||||
+ ", timeZoneDetectionEnabled=" + timeZoneDetectionEnabled);
|
||||
|
||||
mCurrentSuggestion = bestSuggestion;
|
||||
if (timeZoneDetectionEnabled) {
|
||||
setDeviceTimeZone(bestSuggestion.suggestion);
|
||||
}
|
||||
String cause = "Device has no time zone set. Attempting to set the device to the best"
|
||||
+ " available suggestion."
|
||||
+ " bestPhoneSuggestion=" + bestPhoneSuggestion
|
||||
+ ", detectionReason=" + detectionReason;
|
||||
Slog.i(LOG_TAG, cause);
|
||||
setDeviceTimeZoneIfRequired(ORIGIN_PHONE, newZoneId, cause);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean suggestionGoodEnough = bestSuggestion.score >= SCORE_USAGE_THRESHOLD;
|
||||
boolean suggestionGoodEnough = bestPhoneSuggestion.score >= PHONE_SCORE_USAGE_THRESHOLD;
|
||||
if (!suggestionGoodEnough) {
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "doTimeZoneDetection: Suggestion not good enough."
|
||||
+ " bestSuggestion=" + bestSuggestion);
|
||||
Slog.d(LOG_TAG, "Best suggestion not good enough."
|
||||
+ " bestPhoneSuggestion=" + bestPhoneSuggestion
|
||||
+ ", detectionReason=" + detectionReason);
|
||||
}
|
||||
mCurrentSuggestion = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -283,63 +315,84 @@ public class TimeZoneDetectorStrategy {
|
||||
// zone ID.
|
||||
if (newZoneId == null) {
|
||||
Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
|
||||
+ " bestSuggestion=" + bestSuggestion);
|
||||
mCurrentSuggestion = null;
|
||||
+ " bestPhoneSuggestion=" + bestPhoneSuggestion
|
||||
+ " detectionReason=" + detectionReason);
|
||||
return;
|
||||
}
|
||||
|
||||
// There is a good suggestion. Store the suggestion and set the device time zone if
|
||||
// settings allow.
|
||||
mCurrentSuggestion = bestSuggestion;
|
||||
|
||||
// Only set the device time zone if time zone detection is enabled.
|
||||
if (!timeZoneDetectionEnabled) {
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "doTimeZoneDetection: Not setting the time zone because time zone"
|
||||
+ " detection is disabled."
|
||||
+ " bestSuggestion=" + bestSuggestion);
|
||||
}
|
||||
return;
|
||||
}
|
||||
PhoneTimeZoneSuggestion suggestion = bestSuggestion.suggestion;
|
||||
setDeviceTimeZone(suggestion);
|
||||
String zoneId = bestPhoneSuggestion.suggestion.getZoneId();
|
||||
String cause = "Found good suggestion."
|
||||
+ ", bestPhoneSuggestion=" + bestPhoneSuggestion
|
||||
+ ", detectionReason=" + detectionReason;
|
||||
setDeviceTimeZoneIfRequired(ORIGIN_PHONE, zoneId, cause);
|
||||
}
|
||||
|
||||
private void setDeviceTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion) {
|
||||
String currentZoneId = mCallback.getDeviceTimeZone();
|
||||
String newZoneId = suggestion.getZoneId();
|
||||
@GuardedBy("this")
|
||||
private void setDeviceTimeZoneIfRequired(
|
||||
@Origin int origin, @NonNull String newZoneId, @NonNull String cause) {
|
||||
Objects.requireNonNull(newZoneId);
|
||||
Objects.requireNonNull(cause);
|
||||
|
||||
// Paranoia: This should never happen.
|
||||
if (newZoneId == null) {
|
||||
Slog.w(LOG_TAG, "setDeviceTimeZone: Suggested zone is null."
|
||||
+ " timeZoneSuggestion=" + suggestion);
|
||||
return;
|
||||
boolean sendNetworkBroadcast = (origin == ORIGIN_PHONE);
|
||||
boolean isOriginAutomatic = isOriginAutomatic(origin);
|
||||
if (isOriginAutomatic) {
|
||||
if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "Auto time zone detection is not enabled."
|
||||
+ " origin=" + origin
|
||||
+ ", newZoneId=" + newZoneId
|
||||
+ ", cause=" + cause);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (mCallback.isAutoTimeZoneDetectionEnabled()) {
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "Auto time zone detection is enabled."
|
||||
+ " origin=" + origin
|
||||
+ ", newZoneId=" + newZoneId
|
||||
+ ", cause=" + cause);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String currentZoneId = mCallback.getDeviceTimeZone();
|
||||
|
||||
// Avoid unnecessary changes / intents.
|
||||
if (newZoneId.equals(currentZoneId)) {
|
||||
// No need to set the device time zone - the setting is already what we would be
|
||||
// suggesting.
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "setDeviceTimeZone: No need to change the time zone;"
|
||||
Slog.d(LOG_TAG, "No need to change the time zone;"
|
||||
+ " device is already set to the suggested zone."
|
||||
+ " timeZoneSuggestion=" + suggestion);
|
||||
+ " origin=" + origin
|
||||
+ ", newZoneId=" + newZoneId
|
||||
+ ", cause=" + cause);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String msg = "Changing device time zone. currentZoneId=" + currentZoneId
|
||||
+ ", timeZoneSuggestion=" + suggestion;
|
||||
mCallback.setDeviceTimeZone(newZoneId, sendNetworkBroadcast);
|
||||
String msg = "Set device time zone."
|
||||
+ " origin=" + origin
|
||||
+ ", currentZoneId=" + currentZoneId
|
||||
+ ", newZoneId=" + newZoneId
|
||||
+ ", sendNetworkBroadcast" + sendNetworkBroadcast
|
||||
+ ", cause=" + cause;
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, msg);
|
||||
}
|
||||
mTimeZoneChangesLog.log(msg);
|
||||
mCallback.setDeviceTimeZone(newZoneId);
|
||||
}
|
||||
|
||||
private static boolean isOriginAutomatic(@Origin int origin) {
|
||||
return origin == ORIGIN_PHONE;
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
@Nullable
|
||||
private QualifiedPhoneTimeZoneSuggestion findBestSuggestion() {
|
||||
private QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestion() {
|
||||
QualifiedPhoneTimeZoneSuggestion bestSuggestion = null;
|
||||
|
||||
// Iterate over the latest QualifiedPhoneTimeZoneSuggestion objects received for each phone
|
||||
@@ -376,38 +429,44 @@ public class TimeZoneDetectorStrategy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current best suggestion. Not intended for general use: it is used during tests
|
||||
* to check service behavior.
|
||||
* Returns the current best phone suggestion. Not intended for general use: it is used during
|
||||
* tests to check strategy behavior.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
public synchronized QualifiedPhoneTimeZoneSuggestion findBestSuggestionForTests() {
|
||||
return findBestSuggestion();
|
||||
public synchronized QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestionForTests() {
|
||||
return findBestPhoneSuggestion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the has been a change to the automatic time zone detection setting.
|
||||
* Called when there has been a change to the automatic time zone detection setting.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public synchronized void handleTimeZoneDetectionChange() {
|
||||
public synchronized void handleAutoTimeZoneDetectionChange() {
|
||||
if (DBG) {
|
||||
Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called");
|
||||
}
|
||||
if (mCallback.isTimeZoneDetectionEnabled()) {
|
||||
if (mCallback.isAutoTimeZoneDetectionEnabled()) {
|
||||
// When the user enabled time zone detection, run the time zone detection and change the
|
||||
// device time zone if possible.
|
||||
doTimeZoneDetection();
|
||||
String reason = "Auto time zone detection setting enabled.";
|
||||
doAutoTimeZoneDetection(reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps any logs held to the supplied writer.
|
||||
* Dumps internal state such as field values.
|
||||
*/
|
||||
public synchronized void dumpLogs(IndentingPrintWriter ipw) {
|
||||
ipw.println("TimeZoneDetectorStrategy:");
|
||||
|
||||
ipw.increaseIndent(); // level 1
|
||||
public synchronized void dumpState(PrintWriter pw, String[] args) {
|
||||
pw.println("TimeZoneDetectorStrategy:");
|
||||
pw.println("mCallback.isTimeZoneDetectionEnabled()="
|
||||
+ mCallback.isAutoTimeZoneDetectionEnabled());
|
||||
pw.println("mCallback.isDeviceTimeZoneInitialized()="
|
||||
+ mCallback.isDeviceTimeZoneInitialized());
|
||||
pw.println("mCallback.getDeviceTimeZone()="
|
||||
+ mCallback.getDeviceTimeZone());
|
||||
|
||||
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
|
||||
ipw.println("Time zone change log:");
|
||||
ipw.increaseIndent(); // level 2
|
||||
mTimeZoneChangesLog.dump(ipw);
|
||||
@@ -427,24 +486,13 @@ public class TimeZoneDetectorStrategy {
|
||||
}
|
||||
ipw.decreaseIndent(); // level 2
|
||||
ipw.decreaseIndent(); // level 1
|
||||
}
|
||||
ipw.flush();
|
||||
|
||||
/**
|
||||
* Dumps internal state such as field values.
|
||||
*/
|
||||
public synchronized void dumpState(PrintWriter pw) {
|
||||
pw.println("mCurrentSuggestion=" + mCurrentSuggestion);
|
||||
pw.println("mCallback.isTimeZoneDetectionEnabled()="
|
||||
+ mCallback.isTimeZoneDetectionEnabled());
|
||||
pw.println("mCallback.isDeviceTimeZoneInitialized()="
|
||||
+ mCallback.isDeviceTimeZoneInitialized());
|
||||
pw.println("mCallback.getDeviceTimeZone()="
|
||||
+ mCallback.getDeviceTimeZone());
|
||||
pw.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* A method used to inspect service state during tests. Not intended for general use.
|
||||
* A method used to inspect strategy state during tests. Not intended for general use.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int phoneId) {
|
||||
|
||||
@@ -24,18 +24,19 @@ import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTI
|
||||
import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
|
||||
import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
|
||||
|
||||
import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_HIGH;
|
||||
import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_HIGHEST;
|
||||
import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_LOW;
|
||||
import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_MEDIUM;
|
||||
import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_NONE;
|
||||
import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_USAGE_THRESHOLD;
|
||||
import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_HIGH;
|
||||
import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_HIGHEST;
|
||||
import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_LOW;
|
||||
import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_MEDIUM;
|
||||
import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_NONE;
|
||||
import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_USAGE_THRESHOLD;
|
||||
|
||||
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 android.app.timezonedetector.ManualTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.PhoneTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.PhoneTimeZoneSuggestion.MatchType;
|
||||
import android.app.timezonedetector.PhoneTimeZoneSuggestion.Quality;
|
||||
@@ -49,6 +50,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* White-box unit tests for {@link TimeZoneDetectorStrategy}.
|
||||
@@ -64,16 +66,17 @@ public class TimeZoneDetectorStrategyTest {
|
||||
// than the previous.
|
||||
private static final SuggestionTestCase[] TEST_CASES = new SuggestionTestCase[] {
|
||||
newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
|
||||
QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, SCORE_LOW),
|
||||
QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, PHONE_SCORE_LOW),
|
||||
newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET,
|
||||
SCORE_MEDIUM),
|
||||
PHONE_SCORE_MEDIUM),
|
||||
newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET,
|
||||
QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, SCORE_MEDIUM),
|
||||
newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_SINGLE_ZONE, SCORE_HIGH),
|
||||
newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, SCORE_HIGH),
|
||||
QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, PHONE_SCORE_MEDIUM),
|
||||
newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_SINGLE_ZONE, PHONE_SCORE_HIGH),
|
||||
newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
|
||||
PHONE_SCORE_HIGH),
|
||||
newTestCase(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY,
|
||||
QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, SCORE_HIGHEST),
|
||||
newTestCase(MATCH_TYPE_EMULATOR_ZONE_ID, QUALITY_SINGLE_ZONE, SCORE_HIGHEST),
|
||||
QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, PHONE_SCORE_HIGHEST),
|
||||
newTestCase(MATCH_TYPE_EMULATOR_ZONE_ID, QUALITY_SINGLE_ZONE, PHONE_SCORE_HIGHEST),
|
||||
};
|
||||
|
||||
private TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
|
||||
@@ -87,11 +90,11 @@ public class TimeZoneDetectorStrategyTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptySuggestions() {
|
||||
public void testEmptyPhoneSuggestions() {
|
||||
PhoneTimeZoneSuggestion phone1TimeZoneSuggestion = createEmptyPhone1Suggestion();
|
||||
PhoneTimeZoneSuggestion phone2TimeZoneSuggestion = createEmptyPhone2Suggestion();
|
||||
Script script = new Script()
|
||||
.initializeTimeZoneDetectionEnabled(true)
|
||||
.initializeAutoTimeZoneDetection(true)
|
||||
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
|
||||
|
||||
script.suggestPhoneTimeZone(phone1TimeZoneSuggestion)
|
||||
@@ -99,38 +102,38 @@ public class TimeZoneDetectorStrategyTest {
|
||||
|
||||
// Assert internal service state.
|
||||
QualifiedPhoneTimeZoneSuggestion expectedPhone1ScoredSuggestion =
|
||||
new QualifiedPhoneTimeZoneSuggestion(phone1TimeZoneSuggestion, SCORE_NONE);
|
||||
new QualifiedPhoneTimeZoneSuggestion(phone1TimeZoneSuggestion, PHONE_SCORE_NONE);
|
||||
assertEquals(expectedPhone1ScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
|
||||
assertNull(mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
|
||||
assertEquals(expectedPhone1ScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.findBestSuggestionForTests());
|
||||
mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
|
||||
|
||||
script.suggestPhoneTimeZone(phone2TimeZoneSuggestion)
|
||||
.verifyTimeZoneNotSet();
|
||||
|
||||
// Assert internal service state.
|
||||
QualifiedPhoneTimeZoneSuggestion expectedPhone2ScoredSuggestion =
|
||||
new QualifiedPhoneTimeZoneSuggestion(phone2TimeZoneSuggestion, SCORE_NONE);
|
||||
new QualifiedPhoneTimeZoneSuggestion(phone2TimeZoneSuggestion, PHONE_SCORE_NONE);
|
||||
assertEquals(expectedPhone1ScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
|
||||
assertEquals(expectedPhone2ScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
|
||||
// Phone 1 should always beat phone 2, all other things being equal.
|
||||
assertEquals(expectedPhone1ScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.findBestSuggestionForTests());
|
||||
mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFirstPlausibleSuggestionAcceptedWhenTimeZoneUninitialized() {
|
||||
public void testFirstPlausiblePhoneSuggestionAcceptedWhenTimeZoneUninitialized() {
|
||||
SuggestionTestCase testCase = newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
|
||||
QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, SCORE_LOW);
|
||||
QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, PHONE_SCORE_LOW);
|
||||
PhoneTimeZoneSuggestion lowQualitySuggestion =
|
||||
testCase.createSuggestion(PHONE1_ID, "America/New_York");
|
||||
|
||||
// The device time zone setting is left uninitialized.
|
||||
Script script = new Script()
|
||||
.initializeTimeZoneDetectionEnabled(true);
|
||||
.initializeAutoTimeZoneDetection(true);
|
||||
|
||||
// The very first suggestion will be taken.
|
||||
script.suggestPhoneTimeZone(lowQualitySuggestion)
|
||||
@@ -142,7 +145,7 @@ public class TimeZoneDetectorStrategyTest {
|
||||
assertEquals(expectedScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
|
||||
assertEquals(expectedScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.findBestSuggestionForTests());
|
||||
mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
|
||||
|
||||
// Another low quality suggestion will be ignored now that the setting is initialized.
|
||||
PhoneTimeZoneSuggestion lowQualitySuggestion2 =
|
||||
@@ -156,7 +159,7 @@ public class TimeZoneDetectorStrategyTest {
|
||||
assertEquals(expectedScoredSuggestion2,
|
||||
mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
|
||||
assertEquals(expectedScoredSuggestion2,
|
||||
mTimeZoneDetectorStrategy.findBestSuggestionForTests());
|
||||
mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,12 +167,12 @@ public class TimeZoneDetectorStrategyTest {
|
||||
* the strategy is "opinionated".
|
||||
*/
|
||||
@Test
|
||||
public void testTogglingTimeZoneDetection() {
|
||||
public void testTogglingAutoTimeZoneDetection() {
|
||||
Script script = new Script();
|
||||
|
||||
for (SuggestionTestCase testCase : TEST_CASES) {
|
||||
// Start with the device in a known state.
|
||||
script.initializeTimeZoneDetectionEnabled(false)
|
||||
script.initializeAutoTimeZoneDetection(false)
|
||||
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
|
||||
|
||||
PhoneTimeZoneSuggestion suggestion =
|
||||
@@ -186,14 +189,14 @@ public class TimeZoneDetectorStrategyTest {
|
||||
assertEquals(expectedScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
|
||||
assertEquals(expectedScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.findBestSuggestionForTests());
|
||||
mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
|
||||
|
||||
// Toggling the time zone setting on should cause the device setting to be set.
|
||||
script.timeZoneDetectionEnabled(true);
|
||||
script.autoTimeZoneDetectionEnabled(true);
|
||||
|
||||
// When time zone detection is already enabled the suggestion (if it scores highly
|
||||
// enough) should be set immediately.
|
||||
if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
|
||||
if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) {
|
||||
script.verifyTimeZoneSetAndReset(suggestion);
|
||||
} else {
|
||||
script.verifyTimeZoneNotSet();
|
||||
@@ -203,24 +206,24 @@ public class TimeZoneDetectorStrategyTest {
|
||||
assertEquals(expectedScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
|
||||
assertEquals(expectedScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.findBestSuggestionForTests());
|
||||
mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
|
||||
|
||||
// Toggling the time zone setting should off should do nothing.
|
||||
script.timeZoneDetectionEnabled(false)
|
||||
script.autoTimeZoneDetectionEnabled(false)
|
||||
.verifyTimeZoneNotSet();
|
||||
|
||||
// Assert internal service state.
|
||||
assertEquals(expectedScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
|
||||
assertEquals(expectedScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.findBestSuggestionForTests());
|
||||
mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuggestionsSinglePhone() {
|
||||
public void testPhoneSuggestionsSinglePhone() {
|
||||
Script script = new Script()
|
||||
.initializeTimeZoneDetectionEnabled(true)
|
||||
.initializeAutoTimeZoneDetection(true)
|
||||
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
|
||||
|
||||
for (SuggestionTestCase testCase : TEST_CASES) {
|
||||
@@ -254,7 +257,7 @@ public class TimeZoneDetectorStrategyTest {
|
||||
new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion, testCase.expectedScore);
|
||||
|
||||
script.suggestPhoneTimeZone(zonePhone1Suggestion);
|
||||
if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
|
||||
if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) {
|
||||
script.verifyTimeZoneSetAndReset(zonePhone1Suggestion);
|
||||
} else {
|
||||
script.verifyTimeZoneNotSet();
|
||||
@@ -264,7 +267,7 @@ public class TimeZoneDetectorStrategyTest {
|
||||
assertEquals(expectedZonePhone1ScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
|
||||
assertEquals(expectedZonePhone1ScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.findBestSuggestionForTests());
|
||||
mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,12 +281,12 @@ public class TimeZoneDetectorStrategyTest {
|
||||
PhoneTimeZoneSuggestion emptyPhone1Suggestion = createEmptyPhone1Suggestion();
|
||||
PhoneTimeZoneSuggestion emptyPhone2Suggestion = createEmptyPhone2Suggestion();
|
||||
QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone1ScoredSuggestion =
|
||||
new QualifiedPhoneTimeZoneSuggestion(emptyPhone1Suggestion, SCORE_NONE);
|
||||
new QualifiedPhoneTimeZoneSuggestion(emptyPhone1Suggestion, PHONE_SCORE_NONE);
|
||||
QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone2ScoredSuggestion =
|
||||
new QualifiedPhoneTimeZoneSuggestion(emptyPhone2Suggestion, SCORE_NONE);
|
||||
new QualifiedPhoneTimeZoneSuggestion(emptyPhone2Suggestion, PHONE_SCORE_NONE);
|
||||
|
||||
Script script = new Script()
|
||||
.initializeTimeZoneDetectionEnabled(true)
|
||||
.initializeAutoTimeZoneDetection(true)
|
||||
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
|
||||
// Initialize the latest suggestions as empty so we don't need to worry about nulls
|
||||
// below for the first loop.
|
||||
@@ -305,7 +308,7 @@ public class TimeZoneDetectorStrategyTest {
|
||||
|
||||
// Start the test by making a suggestion for phone 1.
|
||||
script.suggestPhoneTimeZone(zonePhone1Suggestion);
|
||||
if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
|
||||
if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) {
|
||||
script.verifyTimeZoneSetAndReset(zonePhone1Suggestion);
|
||||
} else {
|
||||
script.verifyTimeZoneNotSet();
|
||||
@@ -317,7 +320,7 @@ public class TimeZoneDetectorStrategyTest {
|
||||
assertEquals(expectedEmptyPhone2ScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
|
||||
assertEquals(expectedZonePhone1ScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.findBestSuggestionForTests());
|
||||
mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
|
||||
|
||||
// Phone 2 then makes an alternative suggestion with an identical score. Phone 1's
|
||||
// suggestion should still "win" if it is above the required threshold.
|
||||
@@ -331,13 +334,13 @@ public class TimeZoneDetectorStrategyTest {
|
||||
mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
|
||||
// Phone 1 should always beat phone 2, all other things being equal.
|
||||
assertEquals(expectedZonePhone1ScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.findBestSuggestionForTests());
|
||||
mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
|
||||
|
||||
// Withdrawing phone 1's suggestion should leave phone 2 as the new winner. Since the
|
||||
// zoneId is different, the time zone setting should be updated if the score is high
|
||||
// enough.
|
||||
script.suggestPhoneTimeZone(emptyPhone1Suggestion);
|
||||
if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
|
||||
if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) {
|
||||
script.verifyTimeZoneSetAndReset(zonePhone2Suggestion);
|
||||
} else {
|
||||
script.verifyTimeZoneNotSet();
|
||||
@@ -349,7 +352,7 @@ public class TimeZoneDetectorStrategyTest {
|
||||
assertEquals(expectedZonePhone2ScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
|
||||
assertEquals(expectedZonePhone2ScoredSuggestion,
|
||||
mTimeZoneDetectorStrategy.findBestSuggestionForTests());
|
||||
mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
|
||||
|
||||
// Reset the state for the next loop.
|
||||
script.suggestPhoneTimeZone(emptyPhone2Suggestion)
|
||||
@@ -369,10 +372,11 @@ public class TimeZoneDetectorStrategyTest {
|
||||
@Test
|
||||
public void testTimeZoneDetectorStrategyDoesNotAssumeCurrentSetting() {
|
||||
Script script = new Script()
|
||||
.initializeTimeZoneDetectionEnabled(true);
|
||||
.initializeAutoTimeZoneDetection(true);
|
||||
|
||||
SuggestionTestCase testCase =
|
||||
newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, SCORE_HIGH);
|
||||
newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
|
||||
PHONE_SCORE_HIGH);
|
||||
PhoneTimeZoneSuggestion losAngelesSuggestion =
|
||||
testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles");
|
||||
PhoneTimeZoneSuggestion newYorkSuggestion =
|
||||
@@ -387,21 +391,49 @@ public class TimeZoneDetectorStrategyTest {
|
||||
|
||||
// Toggling time zone detection should set the device time zone only if the current setting
|
||||
// value is different from the most recent phone suggestion.
|
||||
script.timeZoneDetectionEnabled(false)
|
||||
script.autoTimeZoneDetectionEnabled(false)
|
||||
.verifyTimeZoneNotSet()
|
||||
.timeZoneDetectionEnabled(true)
|
||||
.autoTimeZoneDetectionEnabled(true)
|
||||
.verifyTimeZoneNotSet();
|
||||
|
||||
// Simulate a user turning auto detection off, a new suggestion being made while auto
|
||||
// detection is off, and the user turning it on again.
|
||||
script.timeZoneDetectionEnabled(false)
|
||||
script.autoTimeZoneDetectionEnabled(false)
|
||||
.suggestPhoneTimeZone(newYorkSuggestion)
|
||||
.verifyTimeZoneNotSet();
|
||||
// Latest suggestion should be used.
|
||||
script.timeZoneDetectionEnabled(true)
|
||||
script.autoTimeZoneDetectionEnabled(true)
|
||||
.verifyTimeZoneSetAndReset(newYorkSuggestion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManualSuggestion_autoTimeZoneDetectionEnabled() {
|
||||
Script script = new Script()
|
||||
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
|
||||
.initializeAutoTimeZoneDetection(true);
|
||||
|
||||
// Auto time zone detection is enabled so the manual suggestion should be ignored.
|
||||
script.suggestManualTimeZone(createManualSuggestion("Europe/Paris"))
|
||||
.verifyTimeZoneNotSet();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testManualSuggestion_autoTimeZoneDetectionDisabled() {
|
||||
Script script = new Script()
|
||||
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
|
||||
.initializeAutoTimeZoneDetection(false);
|
||||
|
||||
// Auto time zone detection is disabled so the manual suggestion should be used.
|
||||
ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
|
||||
script.suggestManualTimeZone(manualSuggestion)
|
||||
.verifyTimeZoneSetAndReset(manualSuggestion);
|
||||
}
|
||||
|
||||
private ManualTimeZoneSuggestion createManualSuggestion(String zoneId) {
|
||||
return new ManualTimeZoneSuggestion(zoneId);
|
||||
}
|
||||
|
||||
private static PhoneTimeZoneSuggestion createEmptyPhone1Suggestion() {
|
||||
return new PhoneTimeZoneSuggestion.Builder(PHONE1_ID).build();
|
||||
}
|
||||
@@ -410,55 +442,86 @@ public class TimeZoneDetectorStrategyTest {
|
||||
return new PhoneTimeZoneSuggestion.Builder(PHONE2_ID).build();
|
||||
}
|
||||
|
||||
class FakeTimeZoneDetectorStrategyCallback implements TimeZoneDetectorStrategy.Callback {
|
||||
static class FakeTimeZoneDetectorStrategyCallback implements TimeZoneDetectorStrategy.Callback {
|
||||
|
||||
private boolean mTimeZoneDetectionEnabled;
|
||||
private TestState<String> mTimeZoneId = new TestState<>();
|
||||
private boolean mAutoTimeZoneDetectionEnabled;
|
||||
private TestState<TimeZoneChange> mTimeZoneChanges = new TestState<>();
|
||||
private String mTimeZoneId;
|
||||
|
||||
@Override
|
||||
public boolean isTimeZoneDetectionEnabled() {
|
||||
return mTimeZoneDetectionEnabled;
|
||||
public boolean isAutoTimeZoneDetectionEnabled() {
|
||||
return mAutoTimeZoneDetectionEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeviceTimeZoneInitialized() {
|
||||
return mTimeZoneId.getLatest() != null;
|
||||
return mTimeZoneId != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeviceTimeZone() {
|
||||
return mTimeZoneId.getLatest();
|
||||
return mTimeZoneId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDeviceTimeZone(String zoneId) {
|
||||
mTimeZoneId.set(zoneId);
|
||||
public void setDeviceTimeZone(String zoneId, boolean withNetworkBroadcast) {
|
||||
mTimeZoneId = zoneId;
|
||||
mTimeZoneChanges.set(new TimeZoneChange(zoneId, withNetworkBroadcast));
|
||||
}
|
||||
|
||||
void initializeTimeZoneDetectionEnabled(boolean enabled) {
|
||||
mTimeZoneDetectionEnabled = enabled;
|
||||
void initializeAutoTimeZoneDetection(boolean enabled) {
|
||||
mAutoTimeZoneDetectionEnabled = enabled;
|
||||
}
|
||||
|
||||
void initializeTimeZone(String zoneId) {
|
||||
mTimeZoneId.init(zoneId);
|
||||
mTimeZoneId = zoneId;
|
||||
}
|
||||
|
||||
void setTimeZoneDetectionEnabled(boolean enabled) {
|
||||
mTimeZoneDetectionEnabled = enabled;
|
||||
void setAutoTimeZoneDetectionEnabled(boolean enabled) {
|
||||
mAutoTimeZoneDetectionEnabled = enabled;
|
||||
}
|
||||
|
||||
void assertTimeZoneNotSet() {
|
||||
mTimeZoneId.assertHasNotBeenSet();
|
||||
mTimeZoneChanges.assertHasNotBeenSet();
|
||||
}
|
||||
|
||||
void assertTimeZoneSet(String timeZoneId) {
|
||||
mTimeZoneId.assertHasBeenSet();
|
||||
mTimeZoneId.assertChangeCount(1);
|
||||
mTimeZoneId.assertLatestEquals(timeZoneId);
|
||||
void assertTimeZoneSet(String timeZoneId, boolean withNetworkBroadcast) {
|
||||
mTimeZoneChanges.assertHasBeenSet();
|
||||
mTimeZoneChanges.assertChangeCount(1);
|
||||
TimeZoneChange expectedChange = new TimeZoneChange(timeZoneId, withNetworkBroadcast);
|
||||
mTimeZoneChanges.assertLatestEquals(expectedChange);
|
||||
}
|
||||
|
||||
void commitAllChanges() {
|
||||
mTimeZoneId.commitLatest();
|
||||
mTimeZoneChanges.commitLatest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TimeZoneChange {
|
||||
private final String mTimeZoneId;
|
||||
private final boolean mWithNetworkBroadcast;
|
||||
|
||||
private TimeZoneChange(String timeZoneId, boolean withNetworkBroadcast) {
|
||||
mTimeZoneId = timeZoneId;
|
||||
mWithNetworkBroadcast = withNetworkBroadcast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TimeZoneChange that = (TimeZoneChange) o;
|
||||
return mWithNetworkBroadcast == that.mWithNetworkBroadcast
|
||||
&& mTimeZoneId.equals(that.mTimeZoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mTimeZoneId, mWithNetworkBroadcast);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,8 +580,8 @@ public class TimeZoneDetectorStrategyTest {
|
||||
*/
|
||||
private class Script {
|
||||
|
||||
Script initializeTimeZoneDetectionEnabled(boolean enabled) {
|
||||
mFakeTimeZoneDetectorStrategyCallback.initializeTimeZoneDetectionEnabled(enabled);
|
||||
Script initializeAutoTimeZoneDetection(boolean enabled) {
|
||||
mFakeTimeZoneDetectorStrategyCallback.initializeAutoTimeZoneDetection(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -527,24 +590,21 @@ public class TimeZoneDetectorStrategyTest {
|
||||
return this;
|
||||
}
|
||||
|
||||
Script timeZoneDetectionEnabled(boolean enabled) {
|
||||
mFakeTimeZoneDetectorStrategyCallback.setTimeZoneDetectionEnabled(enabled);
|
||||
mTimeZoneDetectorStrategy.handleTimeZoneDetectionChange();
|
||||
Script autoTimeZoneDetectionEnabled(boolean enabled) {
|
||||
mFakeTimeZoneDetectorStrategyCallback.setAutoTimeZoneDetectionEnabled(enabled);
|
||||
mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange();
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Simulates the time zone detection service receiving a phone-originated suggestion. */
|
||||
/** Simulates the time zone detection strategy receiving a phone-originated suggestion. */
|
||||
Script suggestPhoneTimeZone(PhoneTimeZoneSuggestion phoneTimeZoneSuggestion) {
|
||||
mTimeZoneDetectorStrategy.suggestPhoneTimeZone(phoneTimeZoneSuggestion);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Simulates the user manually setting the time zone. */
|
||||
Script manuallySetTimeZone(String timeZoneId) {
|
||||
// Assert the test code is correct to call this method.
|
||||
assertFalse(mFakeTimeZoneDetectorStrategyCallback.isTimeZoneDetectionEnabled());
|
||||
|
||||
mFakeTimeZoneDetectorStrategyCallback.initializeTimeZone(timeZoneId);
|
||||
/** Simulates the time zone detection strategy receiving a user-originated suggestion. */
|
||||
Script suggestManualTimeZone(ManualTimeZoneSuggestion manualTimeZoneSuggestion) {
|
||||
mTimeZoneDetectorStrategy.suggestManualTimeZone(manualTimeZoneSuggestion);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -553,8 +613,22 @@ public class TimeZoneDetectorStrategyTest {
|
||||
return this;
|
||||
}
|
||||
|
||||
Script verifyTimeZoneSetAndReset(PhoneTimeZoneSuggestion timeZoneSuggestion) {
|
||||
mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(timeZoneSuggestion.getZoneId());
|
||||
Script verifyTimeZoneSetAndReset(PhoneTimeZoneSuggestion suggestion) {
|
||||
// Phone suggestions should cause a TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE
|
||||
// broadcast.
|
||||
boolean withNetworkBroadcast = true;
|
||||
mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(
|
||||
suggestion.getZoneId(), withNetworkBroadcast);
|
||||
mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
|
||||
return this;
|
||||
}
|
||||
|
||||
Script verifyTimeZoneSetAndReset(ManualTimeZoneSuggestion suggestion) {
|
||||
// Manual suggestions should not cause a TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE
|
||||
// broadcast.
|
||||
boolean withNetworkBroadcast = false;
|
||||
mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(
|
||||
suggestion.getZoneId(), withNetworkBroadcast);
|
||||
mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
|
||||
return this;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user