Merge "Extract an "ArrayMapWithHistory" support class"
am: 472f9b1540
Change-Id: Ie6d8ce05f008796463cfc0e636b8ea7846166f62
This commit is contained in:
@@ -24,7 +24,6 @@ import android.app.timedetector.ManualTimeSuggestion;
|
||||
import android.app.timedetector.PhoneTimeSuggestion;
|
||||
import android.content.Intent;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.LocalLog;
|
||||
import android.util.Slog;
|
||||
import android.util.TimestampedValue;
|
||||
@@ -32,12 +31,11 @@ import android.util.TimestampedValue;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.timezonedetector.ArrayMapWithHistory;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An implementation of TimeDetectorStrategy that passes phone and manual suggestions to
|
||||
@@ -99,14 +97,12 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
|
||||
private TimestampedValue<Long> mLastAutoSystemClockTimeSet;
|
||||
|
||||
/**
|
||||
* A mapping from phoneId to a linked list of time suggestions (the "first" 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.
|
||||
* A mapping from phoneId to a time suggestion. We typically expect one or two mappings: devices
|
||||
* will have a small number of telephony devices and phoneIds are assumed to be stable.
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private ArrayMap<Integer, LinkedList<PhoneTimeSuggestion>> mSuggestionByPhoneId =
|
||||
new ArrayMap<>();
|
||||
private ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionByPhoneId =
|
||||
new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
|
||||
|
||||
@Override
|
||||
public void initialize(@NonNull Callback callback) {
|
||||
@@ -179,16 +175,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
|
||||
|
||||
ipw.println("Phone suggestion history:");
|
||||
ipw.increaseIndent(); // level 2
|
||||
for (Map.Entry<Integer, LinkedList<PhoneTimeSuggestion>> entry
|
||||
: mSuggestionByPhoneId.entrySet()) {
|
||||
ipw.println("Phone " + entry.getKey());
|
||||
|
||||
ipw.increaseIndent(); // level 3
|
||||
for (PhoneTimeSuggestion suggestion : entry.getValue()) {
|
||||
ipw.println(suggestion);
|
||||
}
|
||||
ipw.decreaseIndent(); // level 3
|
||||
}
|
||||
mSuggestionByPhoneId.dump(ipw);
|
||||
ipw.decreaseIndent(); // level 2
|
||||
|
||||
ipw.decreaseIndent(); // level 1
|
||||
@@ -205,20 +192,10 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
|
||||
}
|
||||
|
||||
int phoneId = suggestion.getPhoneId();
|
||||
LinkedList<PhoneTimeSuggestion> phoneSuggestions = mSuggestionByPhoneId.get(phoneId);
|
||||
if (phoneSuggestions == null) {
|
||||
// The first time we've seen this phoneId.
|
||||
phoneSuggestions = new LinkedList<>();
|
||||
mSuggestionByPhoneId.put(phoneId, phoneSuggestions);
|
||||
} else if (phoneSuggestions.isEmpty()) {
|
||||
Slog.w(LOG_TAG, "Suggestions unexpectedly empty when adding suggestion=" + suggestion);
|
||||
}
|
||||
|
||||
if (!phoneSuggestions.isEmpty()) {
|
||||
PhoneTimeSuggestion previousSuggestion = mSuggestionByPhoneId.get(phoneId);
|
||||
if (previousSuggestion != null) {
|
||||
// We can log / discard suggestions with obvious issues with the reference time clock.
|
||||
PhoneTimeSuggestion previousSuggestion = phoneSuggestions.getFirst();
|
||||
if (previousSuggestion == null
|
||||
|| previousSuggestion.getUtcTime() == null
|
||||
if (previousSuggestion.getUtcTime() == null
|
||||
|| previousSuggestion.getUtcTime().getValue() == null) {
|
||||
// This should be impossible given we only store validated suggestions.
|
||||
Slog.w(LOG_TAG, "Previous suggestion is null or has a null time."
|
||||
@@ -240,10 +217,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
|
||||
}
|
||||
|
||||
// Store the latest suggestion.
|
||||
phoneSuggestions.addFirst(suggestion);
|
||||
if (phoneSuggestions.size() > KEEP_SUGGESTION_HISTORY_SIZE) {
|
||||
phoneSuggestions.removeLast();
|
||||
}
|
||||
mSuggestionByPhoneId.put(phoneId, suggestion);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -331,15 +305,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
|
||||
int bestScore = PHONE_INVALID_SCORE;
|
||||
for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
|
||||
Integer phoneId = mSuggestionByPhoneId.keyAt(i);
|
||||
LinkedList<PhoneTimeSuggestion> phoneSuggestions = mSuggestionByPhoneId.valueAt(i);
|
||||
if (phoneSuggestions == null) {
|
||||
// Unexpected - map is missing a value.
|
||||
Slog.w(LOG_TAG, "Suggestions unexpectedly missing for phoneId."
|
||||
+ " phoneId=" + phoneId);
|
||||
continue;
|
||||
}
|
||||
|
||||
PhoneTimeSuggestion candidateSuggestion = phoneSuggestions.getFirst();
|
||||
PhoneTimeSuggestion candidateSuggestion = mSuggestionByPhoneId.valueAt(i);
|
||||
if (candidateSuggestion == null) {
|
||||
// Unexpected - null suggestions should never be stored.
|
||||
Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for phoneId."
|
||||
@@ -540,10 +506,6 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) {
|
||||
LinkedList<PhoneTimeSuggestion> suggestions = mSuggestionByPhoneId.get(phoneId);
|
||||
if (suggestions == null) {
|
||||
return null;
|
||||
}
|
||||
return suggestions.getFirst();
|
||||
return mSuggestionByPhoneId.get(phoneId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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 com.android.server.timezonedetector;
|
||||
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
|
||||
/**
|
||||
* A partial decorator for {@link ArrayMap} that records historic values for each mapping for
|
||||
* debugging later with {@link #dump(IndentingPrintWriter)}.
|
||||
*
|
||||
* <p>This class is only intended for use in {@link TimeZoneDetectorStrategy} and
|
||||
* {@link com.android.server.timedetector.TimeDetectorStrategy} so only provides the parts of the
|
||||
* {@link ArrayMap} API needed. If it is ever extended to include deletion methods like
|
||||
* {@link ArrayMap#remove(Object)} some thought would need to be given to the correct
|
||||
* {@link ArrayMap#containsKey(Object)} behavior for the history. Like {@link ArrayMap}, it is not
|
||||
* thread-safe.
|
||||
*
|
||||
* @param <K> the type of the key
|
||||
* @param <V> the type of the value
|
||||
*/
|
||||
public final class ArrayMapWithHistory<K, V> {
|
||||
private static final String TAG = "ArrayMapWithHistory";
|
||||
|
||||
/** The size the linked list against each value is allowed to grow to. */
|
||||
private final int mMaxHistorySize;
|
||||
|
||||
@Nullable
|
||||
private ArrayMap<K, ReferenceWithHistory<V>> mMap;
|
||||
|
||||
/**
|
||||
* Creates an instance that records, at most, the specified number of values against each key.
|
||||
*/
|
||||
public ArrayMapWithHistory(@IntRange(from = 1) int maxHistorySize) {
|
||||
if (maxHistorySize < 1) {
|
||||
throw new IllegalArgumentException("maxHistorySize < 1: " + maxHistorySize);
|
||||
}
|
||||
mMaxHistorySize = maxHistorySize;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link ArrayMap#put(K, V)}.
|
||||
*/
|
||||
@Nullable
|
||||
public V put(@Nullable K key, @Nullable V value) {
|
||||
if (mMap == null) {
|
||||
mMap = new ArrayMap<>();
|
||||
}
|
||||
|
||||
ReferenceWithHistory<V> valueHolder = mMap.get(key);
|
||||
if (valueHolder == null) {
|
||||
valueHolder = new ReferenceWithHistory<>(mMaxHistorySize);
|
||||
mMap.put(key, valueHolder);
|
||||
} else if (valueHolder.getHistoryCount() == 0) {
|
||||
Log.w(TAG, "History for \"" + key + "\" was unexpectedly empty");
|
||||
}
|
||||
|
||||
return valueHolder.set(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link ArrayMap#get(Object)}.
|
||||
*/
|
||||
@Nullable
|
||||
public V get(@Nullable Object key) {
|
||||
if (mMap == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ReferenceWithHistory<V> valueHolder = mMap.get(key);
|
||||
if (valueHolder == null) {
|
||||
return null;
|
||||
} else if (valueHolder.getHistoryCount() == 0) {
|
||||
Log.w(TAG, "History for \"" + key + "\" was unexpectedly empty");
|
||||
}
|
||||
return valueHolder.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link ArrayMap#size()}.
|
||||
*/
|
||||
public int size() {
|
||||
return mMap == null ? 0 : mMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link ArrayMap#keyAt(int)}.
|
||||
*/
|
||||
@Nullable
|
||||
public K keyAt(int index) {
|
||||
if (mMap == null) {
|
||||
throw new ArrayIndexOutOfBoundsException(index);
|
||||
}
|
||||
return mMap.keyAt(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link ArrayMap#valueAt(int)}.
|
||||
*/
|
||||
@Nullable
|
||||
public V valueAt(int index) {
|
||||
if (mMap == null) {
|
||||
throw new ArrayIndexOutOfBoundsException(index);
|
||||
}
|
||||
|
||||
ReferenceWithHistory<V> valueHolder = mMap.valueAt(index);
|
||||
if (valueHolder == null || valueHolder.getHistoryCount() == 0) {
|
||||
Log.w(TAG, "valueAt(" + index + ") was unexpectedly null or empty");
|
||||
return null;
|
||||
}
|
||||
return valueHolder.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the content of the map, including historic values, using the supplied writer.
|
||||
*/
|
||||
public void dump(@NonNull IndentingPrintWriter ipw) {
|
||||
if (mMap == null) {
|
||||
ipw.println("{Empty}");
|
||||
} else {
|
||||
for (int i = 0; i < mMap.size(); i++) {
|
||||
ipw.println("key idx: " + i + "=" + mMap.keyAt(i));
|
||||
ReferenceWithHistory<V> value = mMap.valueAt(i);
|
||||
ipw.println("val idx: " + i + "=" + value);
|
||||
ipw.increaseIndent();
|
||||
|
||||
ipw.println("Historic values=[");
|
||||
ipw.increaseIndent();
|
||||
value.dump(ipw);
|
||||
ipw.decreaseIndent();
|
||||
ipw.println("]");
|
||||
|
||||
ipw.decreaseIndent();
|
||||
}
|
||||
}
|
||||
ipw.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method intended for tests that returns the number of historic values associated with
|
||||
* the supplied key currently. If there is no mapping for the key then {@code 0} is returned.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public int getHistoryCountForKeyForTests(@Nullable K key) {
|
||||
if (mMap == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ReferenceWithHistory<V> valueHolder = mMap.get(key);
|
||||
if (valueHolder == null) {
|
||||
return 0;
|
||||
} else if (valueHolder.getHistoryCount() == 0) {
|
||||
Log.w(TAG, "getValuesSizeForKeyForTests(\"" + key + "\") was unexpectedly empty");
|
||||
return 0;
|
||||
} else {
|
||||
return valueHolder.getHistoryCount();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ArrayMapWithHistory{"
|
||||
+ "mHistorySize=" + mMaxHistorySize
|
||||
+ ", mMap=" + mMap
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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 com.android.server.timezonedetector;
|
||||
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* A class that behaves like the following definition, except it stores the history of values set
|
||||
* that can be dumped for debugging with {@link #dump(IndentingPrintWriter)}.
|
||||
*
|
||||
* <pre>{@code
|
||||
* private static class Ref<V> {
|
||||
* private V mValue;
|
||||
*
|
||||
* public V get() {
|
||||
* return mValue;
|
||||
* }
|
||||
*
|
||||
* public V set(V value) {
|
||||
* V previous = mValue;
|
||||
* mValue = value;
|
||||
* return previous;
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p>This class is not thread-safe.
|
||||
*
|
||||
* @param <V> the type of the value
|
||||
*/
|
||||
public final class ReferenceWithHistory<V> {
|
||||
|
||||
/** The size the history linked list is allowed to grow to. */
|
||||
private final int mMaxHistorySize;
|
||||
|
||||
@Nullable
|
||||
private LinkedList<V> mValues;
|
||||
|
||||
/**
|
||||
* Creates an instance that records, at most, the specified number of values.
|
||||
*/
|
||||
public ReferenceWithHistory(@IntRange(from = 1) int maxHistorySize) {
|
||||
if (maxHistorySize < 1) {
|
||||
throw new IllegalArgumentException("maxHistorySize < 1: " + maxHistorySize);
|
||||
}
|
||||
this.mMaxHistorySize = maxHistorySize;
|
||||
}
|
||||
|
||||
/** Returns the current value, or {@code null} if it has never been set. */
|
||||
@Nullable
|
||||
public V get() {
|
||||
return (mValues == null || mValues.isEmpty()) ? null : mValues.getFirst();
|
||||
}
|
||||
|
||||
/** Sets the current value. Returns the previous value, or {@code null}. */
|
||||
@Nullable
|
||||
public V set(@Nullable V newValue) {
|
||||
if (mValues == null) {
|
||||
mValues = new LinkedList<>();
|
||||
}
|
||||
|
||||
V previous = get();
|
||||
|
||||
mValues.addFirst(newValue);
|
||||
if (mValues.size() > mMaxHistorySize) {
|
||||
mValues.removeLast();
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the content of the reference, including historic values, using the supplied writer.
|
||||
*/
|
||||
public void dump(@NonNull IndentingPrintWriter ipw) {
|
||||
if (mValues == null) {
|
||||
ipw.println("{Empty}");
|
||||
} else {
|
||||
int i = 0;
|
||||
for (V value : mValues) {
|
||||
ipw.println(i + ": " + value);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
ipw.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of historic entries stored currently.
|
||||
*/
|
||||
public int getHistoryCount() {
|
||||
return mValues == null ? 0 : mValues.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(get());
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ import android.annotation.Nullable;
|
||||
import android.app.timezonedetector.ManualTimeZoneSuggestion;
|
||||
import android.app.timezonedetector.PhoneTimeZoneSuggestion;
|
||||
import android.content.Context;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.LocalLog;
|
||||
import android.util.Slog;
|
||||
|
||||
@@ -38,8 +37,6 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -175,14 +172,13 @@ public class TimeZoneDetectorStrategy {
|
||||
private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
|
||||
|
||||
/**
|
||||
* 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_PHONE_SUGGESTION_HISTORY_SIZE} in size.
|
||||
* A mapping from phoneId to a phone time zone suggestion. We typically expect one or two
|
||||
* mappings: devices will have a small number of telephony devices and phoneIds are assumed to
|
||||
* be stable.
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private ArrayMap<Integer, LinkedList<QualifiedPhoneTimeZoneSuggestion>> mSuggestionByPhoneId =
|
||||
new ArrayMap<>();
|
||||
private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionByPhoneId =
|
||||
new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE);
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link TimeZoneDetectorStrategy}.
|
||||
@@ -226,16 +222,7 @@ public class TimeZoneDetectorStrategy {
|
||||
new QualifiedPhoneTimeZoneSuggestion(suggestion, score);
|
||||
|
||||
// Store the suggestion against the correct phoneId.
|
||||
LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
|
||||
mSuggestionByPhoneId.get(suggestion.getPhoneId());
|
||||
if (suggestions == null) {
|
||||
suggestions = new LinkedList<>();
|
||||
mSuggestionByPhoneId.put(suggestion.getPhoneId(), suggestions);
|
||||
}
|
||||
suggestions.addFirst(scoredSuggestion);
|
||||
if (suggestions.size() > KEEP_PHONE_SUGGESTION_HISTORY_SIZE) {
|
||||
suggestions.removeLast();
|
||||
}
|
||||
mSuggestionByPhoneId.put(suggestion.getPhoneId(), scoredSuggestion);
|
||||
|
||||
// Now perform auto time zone detection. The new suggestion may be used to modify the time
|
||||
// zone setting.
|
||||
@@ -398,13 +385,7 @@ public class TimeZoneDetectorStrategy {
|
||||
// rate-limit so age is not a strong indicator of confidence. Instead, the callers are
|
||||
// expected to withdraw suggestions they no longer have confidence in.
|
||||
for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
|
||||
LinkedList<QualifiedPhoneTimeZoneSuggestion> phoneSuggestions =
|
||||
mSuggestionByPhoneId.valueAt(i);
|
||||
if (phoneSuggestions == null) {
|
||||
// Unexpected
|
||||
continue;
|
||||
}
|
||||
QualifiedPhoneTimeZoneSuggestion candidateSuggestion = phoneSuggestions.getFirst();
|
||||
QualifiedPhoneTimeZoneSuggestion candidateSuggestion = mSuggestionByPhoneId.valueAt(i);
|
||||
if (candidateSuggestion == null) {
|
||||
// Unexpected
|
||||
continue;
|
||||
@@ -474,16 +455,7 @@ public class TimeZoneDetectorStrategy {
|
||||
|
||||
ipw.println("Phone suggestion history:");
|
||||
ipw.increaseIndent(); // level 2
|
||||
for (Map.Entry<Integer, LinkedList<QualifiedPhoneTimeZoneSuggestion>> entry
|
||||
: mSuggestionByPhoneId.entrySet()) {
|
||||
ipw.println("Phone " + entry.getKey());
|
||||
|
||||
ipw.increaseIndent(); // level 3
|
||||
for (QualifiedPhoneTimeZoneSuggestion suggestion : entry.getValue()) {
|
||||
ipw.println(suggestion);
|
||||
}
|
||||
ipw.decreaseIndent(); // level 3
|
||||
}
|
||||
mSuggestionByPhoneId.dump(ipw);
|
||||
ipw.decreaseIndent(); // level 2
|
||||
ipw.decreaseIndent(); // level 1
|
||||
ipw.flush();
|
||||
@@ -494,12 +466,7 @@ public class TimeZoneDetectorStrategy {
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int phoneId) {
|
||||
LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
|
||||
mSuggestionByPhoneId.get(phoneId);
|
||||
if (suggestions == null) {
|
||||
return null;
|
||||
}
|
||||
return suggestions.getFirst();
|
||||
return mSuggestionByPhoneId.get(phoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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 com.android.server.timedetector;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.timezonedetector.ArrayMapWithHistory;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ArrayMapWithHistoryTest {
|
||||
|
||||
@Test
|
||||
public void testValueHistoryBehavior() {
|
||||
// Create a map that will retain 2 values per key.
|
||||
ArrayMapWithHistory<String, String> historyMap = new ArrayMapWithHistory<>(2 /* history */);
|
||||
ArrayMap<String, String> arrayMap = new ArrayMap<>();
|
||||
|
||||
compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", null));
|
||||
|
||||
assertEquals(0, historyMap.getHistoryCountForKeyForTests("K1"));
|
||||
assertToStringAndDumpNotNull(historyMap);
|
||||
|
||||
putAndCompareReturnValue(historyMap, arrayMap, "K1", "V1");
|
||||
compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", "V1"));
|
||||
compareKeyAtAndValueAtForIndex(0, historyMap, arrayMap);
|
||||
|
||||
assertEquals(1, historyMap.getHistoryCountForKeyForTests("K1"));
|
||||
assertToStringAndDumpNotNull(historyMap);
|
||||
|
||||
// put() a new value for the same key.
|
||||
putAndCompareReturnValue(historyMap, arrayMap, "K1", "V2");
|
||||
compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", "V2"));
|
||||
compareKeyAtAndValueAtForIndex(0, historyMap, arrayMap);
|
||||
|
||||
assertEquals(2, historyMap.getHistoryCountForKeyForTests("K1"));
|
||||
assertToStringAndDumpNotNull(historyMap);
|
||||
|
||||
// put() a new value for the same key. We should have hit the limit of "2 values retained
|
||||
// per key".
|
||||
putAndCompareReturnValue(historyMap, arrayMap, "K1", "V3");
|
||||
compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", "V3"));
|
||||
compareKeyAtAndValueAtForIndex(0, historyMap, arrayMap);
|
||||
|
||||
assertEquals(2, historyMap.getHistoryCountForKeyForTests("K1"));
|
||||
assertToStringAndDumpNotNull(historyMap);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapBehavior() throws Exception {
|
||||
ArrayMapWithHistory<String, String> historyMap = new ArrayMapWithHistory<>(2);
|
||||
ArrayMap<String, String> arrayMap = new ArrayMap<>();
|
||||
|
||||
compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", null), entry("K2", null));
|
||||
assertIndexAccessThrowsException(0, historyMap, arrayMap);
|
||||
|
||||
assertEquals(0, historyMap.getHistoryCountForKeyForTests("K1"));
|
||||
assertEquals(0, historyMap.getHistoryCountForKeyForTests("K2"));
|
||||
|
||||
putAndCompareReturnValue(historyMap, arrayMap, "K1", "V1");
|
||||
compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", "V1"), entry("K2", null));
|
||||
compareKeyAtAndValueAtForIndex(0, historyMap, arrayMap);
|
||||
// TODO Restore after http://b/146563025 is fixed and ArrayMap behaves properly in tests.
|
||||
// assertIndexAccessThrowsException(1, historyMap, arrayMap);
|
||||
|
||||
assertEquals(1, historyMap.getHistoryCountForKeyForTests("K1"));
|
||||
assertToStringAndDumpNotNull(historyMap);
|
||||
|
||||
putAndCompareReturnValue(historyMap, arrayMap, "K2", "V2");
|
||||
compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", "V1"), entry("K2", "V2"));
|
||||
compareKeyAtAndValueAtForIndex(0, historyMap, arrayMap);
|
||||
compareKeyAtAndValueAtForIndex(1, historyMap, arrayMap);
|
||||
// TODO Restore after http://b/146563025 is fixed and ArrayMap behaves properly in tests.
|
||||
// assertIndexAccessThrowsException(2, historyMap, arrayMap);
|
||||
|
||||
assertEquals(1, historyMap.getHistoryCountForKeyForTests("K1"));
|
||||
assertEquals(1, historyMap.getHistoryCountForKeyForTests("K2"));
|
||||
assertToStringAndDumpNotNull(historyMap);
|
||||
}
|
||||
|
||||
private static String dumpHistoryMap(ArrayMapWithHistory<?, ?> historyMap) {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
try (IndentingPrintWriter ipw = new IndentingPrintWriter(stringWriter, " ")) {
|
||||
historyMap.dump(ipw);
|
||||
return stringWriter.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static <K, V> void putAndCompareReturnValue(ArrayMapWithHistory<K, V> historyMap,
|
||||
ArrayMap<K, V> arrayMap, K key, V value) {
|
||||
assertEquals(arrayMap.put(key, value), historyMap.put(key, value));
|
||||
}
|
||||
|
||||
private static class Entry<K, V> {
|
||||
public final K key;
|
||||
public final V value;
|
||||
|
||||
Entry(K key, V value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static <K, V> Entry<K, V> entry(K key, V value) {
|
||||
return new Entry<>(key, value);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static <K, V> void compareGetAndSizeForKeys(ArrayMapWithHistory<K, V> historyMap,
|
||||
ArrayMap<K, V> arrayMap, Entry<K, V>... expectedEntries) {
|
||||
for (Entry<K, V> expectedEntry : expectedEntries) {
|
||||
assertEquals(arrayMap.get(expectedEntry.key), historyMap.get(expectedEntry.key));
|
||||
assertEquals(expectedEntry.value, historyMap.get(expectedEntry.key));
|
||||
}
|
||||
assertEquals(arrayMap.size(), historyMap.size());
|
||||
}
|
||||
|
||||
private static void compareKeyAtAndValueAtForIndex(
|
||||
int index, ArrayMapWithHistory<?, ?> historyMap, ArrayMap<?, ?> arrayMap) {
|
||||
assertEquals(arrayMap.keyAt(index), historyMap.keyAt(index));
|
||||
assertEquals(arrayMap.valueAt(index), historyMap.valueAt(index));
|
||||
}
|
||||
|
||||
private static void assertIndexAccessThrowsException(
|
||||
int index, ArrayMapWithHistory<?, ?> historyMap, ArrayMap<?, ?> arrayMap)
|
||||
throws Exception {
|
||||
assertThrowsArrayIndexOutOfBoundsException(
|
||||
"ArrayMap.keyAt(" + index + ")", () -> arrayMap.keyAt(index));
|
||||
assertThrowsArrayIndexOutOfBoundsException(
|
||||
"ArrayMapWithHistory.keyAt(" + index + ")", () -> historyMap.keyAt(index));
|
||||
assertThrowsArrayIndexOutOfBoundsException(
|
||||
"ArrayMap.keyAt(" + index + ")", () -> arrayMap.valueAt(index));
|
||||
assertThrowsArrayIndexOutOfBoundsException(
|
||||
"ArrayMapWithHistory.keyAt(" + index + ")", () -> historyMap.valueAt(index));
|
||||
}
|
||||
|
||||
private static void assertThrowsArrayIndexOutOfBoundsException(
|
||||
String description, Callable<?> callable) throws Exception {
|
||||
try {
|
||||
callable.call();
|
||||
fail("Expected exception for " + description);
|
||||
} catch (ArrayIndexOutOfBoundsException expected) {
|
||||
// This is fine.
|
||||
} catch (Exception e) {
|
||||
// Any other exception is just rethrown.
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertToStringAndDumpNotNull(ArrayMapWithHistory<?, ?> historyMap) {
|
||||
assertNotNull(historyMap.toString());
|
||||
assertNotNull(dumpHistoryMap(historyMap));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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 com.android.server.timedetector;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.timezonedetector.ReferenceWithHistory;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.StringWriter;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ReferenceWithHistoryTest {
|
||||
|
||||
@Test
|
||||
public void testBasicReferenceBehavior() {
|
||||
// Create a reference that will retain 2 history values.
|
||||
ReferenceWithHistory<String> referenceWithHistory =
|
||||
new ReferenceWithHistory<>(2 /* history */);
|
||||
TestRef<String> reference = new TestRef<>();
|
||||
|
||||
// Check unset behavior.
|
||||
compareGet(referenceWithHistory, reference, null);
|
||||
assertNotNull(dumpReferenceWithHistory(referenceWithHistory));
|
||||
compareToString(referenceWithHistory, reference, "null");
|
||||
|
||||
// Try setting null.
|
||||
setAndCompareReturnValue(referenceWithHistory, reference, null);
|
||||
compareGet(referenceWithHistory, reference, null);
|
||||
assertNotNull(dumpReferenceWithHistory(referenceWithHistory));
|
||||
compareToString(referenceWithHistory, reference, "null");
|
||||
|
||||
// Try setting a non-null value.
|
||||
setAndCompareReturnValue(referenceWithHistory, reference, "Foo");
|
||||
compareGet(referenceWithHistory, reference, "Foo");
|
||||
assertNotNull(dumpReferenceWithHistory(referenceWithHistory));
|
||||
compareToString(referenceWithHistory, reference, "Foo");
|
||||
|
||||
// Try setting null again.
|
||||
setAndCompareReturnValue(referenceWithHistory, reference, "Foo");
|
||||
compareGet(referenceWithHistory, reference, "Foo");
|
||||
assertNotNull(dumpReferenceWithHistory(referenceWithHistory));
|
||||
compareToString(referenceWithHistory, reference, "Foo");
|
||||
|
||||
// Try a non-null value again.
|
||||
setAndCompareReturnValue(referenceWithHistory, reference, "Bar");
|
||||
compareGet(referenceWithHistory, reference, "Bar");
|
||||
assertNotNull(dumpReferenceWithHistory(referenceWithHistory));
|
||||
compareToString(referenceWithHistory, reference, "Bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValueHistoryBehavior() {
|
||||
// Create a reference that will retain 2 history values.
|
||||
ReferenceWithHistory<String> referenceWithHistory =
|
||||
new ReferenceWithHistory<>(2 /* history */);
|
||||
TestRef<String> reference = new TestRef<>();
|
||||
|
||||
// Assert behavior before anything is set.
|
||||
assertEquals(0, referenceWithHistory.getHistoryCount());
|
||||
|
||||
// Set a value (1).
|
||||
setAndCompareReturnValue(referenceWithHistory, reference, "V1");
|
||||
assertEquals(1, referenceWithHistory.getHistoryCount());
|
||||
|
||||
// Set a value (2).
|
||||
setAndCompareReturnValue(referenceWithHistory, reference, "V2");
|
||||
assertEquals(2, referenceWithHistory.getHistoryCount());
|
||||
|
||||
// Set a value (3).
|
||||
// We should have hit the limit of "2 history values retained per key".
|
||||
setAndCompareReturnValue(referenceWithHistory, reference, "V3");
|
||||
assertEquals(2, referenceWithHistory.getHistoryCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple class that has the same behavior as ReferenceWithHistory without the history. Used
|
||||
* in tests for comparison.
|
||||
*/
|
||||
private static class TestRef<V> {
|
||||
private V mValue;
|
||||
|
||||
public V get() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public V set(V value) {
|
||||
V previous = mValue;
|
||||
mValue = value;
|
||||
return previous;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.valueOf(mValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static void compareGet(
|
||||
ReferenceWithHistory<?> referenceWithHistory, TestRef<?> reference, Object value) {
|
||||
assertEquals(reference.get(), referenceWithHistory.get());
|
||||
assertEquals(value, reference.get());
|
||||
}
|
||||
|
||||
private static <T> void setAndCompareReturnValue(
|
||||
ReferenceWithHistory<T> referenceWithHistory, TestRef<T> reference, T newValue) {
|
||||
assertEquals(reference.set(newValue), referenceWithHistory.set(newValue));
|
||||
}
|
||||
|
||||
private static void compareToString(
|
||||
ReferenceWithHistory<?> referenceWithHistory, TestRef<?> reference, String expected) {
|
||||
assertEquals(reference.toString(), referenceWithHistory.toString());
|
||||
assertEquals(expected, referenceWithHistory.toString());
|
||||
}
|
||||
|
||||
private static String dumpReferenceWithHistory(ReferenceWithHistory<?> referenceWithHistory) {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
try (IndentingPrintWriter ipw = new IndentingPrintWriter(stringWriter, " ")) {
|
||||
referenceWithHistory.dump(ipw);
|
||||
return stringWriter.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user