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

Commit 472f9b15 authored by Neil Fuller's avatar Neil Fuller Committed by Gerrit Code Review
Browse files

Merge "Extract an "ArrayMapWithHistory" support class"

parents 7be5365d 0de8726e
Loading
Loading
Loading
Loading
+12 −50
Original line number Diff line number Diff line
@@ -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);
    }
}
+187 −0
Original line number Diff line number Diff line
/*
 * 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
                + '}';
    }
}
+118 −0
Original line number Diff line number Diff line
/*
 * 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());
    }
}
+9 −42
Original line number Diff line number Diff line
@@ -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);
    }

    /**
+180 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading