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

Commit ec299a67 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "Extract an "ArrayMapWithHistory" support class" am: 472f9b15 am: 7546e0c5

Change-Id: Ib24daca60e5b5118f6d3728362b5ce9ed9b9878b
parents 36208225 7546e0c5
Loading
Loading
Loading
Loading
+12 −50
Original line number Original line Diff line number Diff line
@@ -24,7 +24,6 @@ import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
import android.content.Intent;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.LocalLog;
import android.util.LocalLog;
import android.util.Slog;
import android.util.Slog;
import android.util.TimestampedValue;
import android.util.TimestampedValue;
@@ -32,12 +31,11 @@ import android.util.TimestampedValue;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.timezonedetector.ArrayMapWithHistory;


import java.io.PrintWriter;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.RetentionPolicy;
import java.util.LinkedList;
import java.util.Map;


/**
/**
 * An implementation of TimeDetectorStrategy that passes phone and manual suggestions to
 * 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;
    private TimestampedValue<Long> mLastAutoSystemClockTimeSet;


    /**
    /**
     * A mapping from phoneId to a linked list of time suggestions (the "first" being the latest).
     * A mapping from phoneId to a time suggestion. We typically expect one or two mappings: devices
     * We typically expect one or two entries in this Map: devices will have a small number
     * will have a small number of telephony devices and phoneIds are assumed to be stable.
     * 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.
     */
     */
    @GuardedBy("this")
    @GuardedBy("this")
    private ArrayMap<Integer, LinkedList<PhoneTimeSuggestion>> mSuggestionByPhoneId =
    private ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionByPhoneId =
            new ArrayMap<>();
            new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);


    @Override
    @Override
    public void initialize(@NonNull Callback callback) {
    public void initialize(@NonNull Callback callback) {
@@ -179,16 +175,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {


        ipw.println("Phone suggestion history:");
        ipw.println("Phone suggestion history:");
        ipw.increaseIndent(); // level 2
        ipw.increaseIndent(); // level 2
        for (Map.Entry<Integer, LinkedList<PhoneTimeSuggestion>> entry
        mSuggestionByPhoneId.dump(ipw);
                : mSuggestionByPhoneId.entrySet()) {
            ipw.println("Phone " + entry.getKey());

            ipw.increaseIndent(); // level 3
            for (PhoneTimeSuggestion suggestion : entry.getValue()) {
                ipw.println(suggestion);
            }
            ipw.decreaseIndent(); // level 3
        }
        ipw.decreaseIndent(); // level 2
        ipw.decreaseIndent(); // level 2


        ipw.decreaseIndent(); // level 1
        ipw.decreaseIndent(); // level 1
@@ -205,20 +192,10 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
        }
        }


        int phoneId = suggestion.getPhoneId();
        int phoneId = suggestion.getPhoneId();
        LinkedList<PhoneTimeSuggestion> phoneSuggestions = mSuggestionByPhoneId.get(phoneId);
        PhoneTimeSuggestion previousSuggestion = mSuggestionByPhoneId.get(phoneId);
        if (phoneSuggestions == null) {
        if (previousSuggestion != 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()) {
            // We can log / discard suggestions with obvious issues with the reference time clock.
            // We can log / discard suggestions with obvious issues with the reference time clock.
            PhoneTimeSuggestion previousSuggestion = phoneSuggestions.getFirst();
            if (previousSuggestion.getUtcTime() == null
            if (previousSuggestion == null
                    || previousSuggestion.getUtcTime() == null
                    || previousSuggestion.getUtcTime().getValue() == null) {
                    || previousSuggestion.getUtcTime().getValue() == null) {
                // This should be impossible given we only store validated suggestions.
                // This should be impossible given we only store validated suggestions.
                Slog.w(LOG_TAG, "Previous suggestion is null or has a null time."
                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.
        // Store the latest suggestion.
        phoneSuggestions.addFirst(suggestion);
        mSuggestionByPhoneId.put(phoneId, suggestion);
        if (phoneSuggestions.size() > KEEP_SUGGESTION_HISTORY_SIZE) {
            phoneSuggestions.removeLast();
        }
        return true;
        return true;
    }
    }


@@ -331,15 +305,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
        int bestScore = PHONE_INVALID_SCORE;
        int bestScore = PHONE_INVALID_SCORE;
        for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
        for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
            Integer phoneId = mSuggestionByPhoneId.keyAt(i);
            Integer phoneId = mSuggestionByPhoneId.keyAt(i);
            LinkedList<PhoneTimeSuggestion> phoneSuggestions = mSuggestionByPhoneId.valueAt(i);
            PhoneTimeSuggestion candidateSuggestion = 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();
            if (candidateSuggestion == null) {
            if (candidateSuggestion == null) {
                // Unexpected - null suggestions should never be stored.
                // Unexpected - null suggestions should never be stored.
                Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for phoneId."
                Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for phoneId."
@@ -540,10 +506,6 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
    @VisibleForTesting
    @VisibleForTesting
    @Nullable
    @Nullable
    public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) {
    public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) {
        LinkedList<PhoneTimeSuggestion> suggestions = mSuggestionByPhoneId.get(phoneId);
        return mSuggestionByPhoneId.get(phoneId);
        if (suggestions == null) {
            return null;
        }
        return suggestions.getFirst();
    }
    }
}
}
+187 −0
Original line number Original line 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 Original line 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 Original line Diff line number Diff line
@@ -27,7 +27,6 @@ import android.annotation.Nullable;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.PhoneTimeZoneSuggestion;
import android.app.timezonedetector.PhoneTimeZoneSuggestion;
import android.content.Context;
import android.content.Context;
import android.util.ArrayMap;
import android.util.LocalLog;
import android.util.LocalLog;
import android.util.Slog;
import android.util.Slog;


@@ -38,8 +37,6 @@ import com.android.internal.util.IndentingPrintWriter;
import java.io.PrintWriter;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.RetentionPolicy;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Objects;


/**
/**
@@ -175,14 +172,13 @@ public class TimeZoneDetectorStrategy {
    private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
    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
     * A mapping from phoneId to a phone time zone suggestion. We typically expect one or two
     * latest). We typically expect one or two entries in this Map: devices will have a small number
     * mappings: devices will have a small number of telephony devices and phoneIds are assumed to
     * of telephony devices and phoneIds are assumed to be stable. The LinkedList associated with
     * be stable.
     * the ID will not exceed {@link #KEEP_PHONE_SUGGESTION_HISTORY_SIZE} in size.
     */
     */
    @GuardedBy("this")
    @GuardedBy("this")
    private ArrayMap<Integer, LinkedList<QualifiedPhoneTimeZoneSuggestion>> mSuggestionByPhoneId =
    private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionByPhoneId =
            new ArrayMap<>();
            new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE);


    /**
    /**
     * Creates a new instance of {@link TimeZoneDetectorStrategy}.
     * Creates a new instance of {@link TimeZoneDetectorStrategy}.
@@ -226,16 +222,7 @@ public class TimeZoneDetectorStrategy {
                new QualifiedPhoneTimeZoneSuggestion(suggestion, score);
                new QualifiedPhoneTimeZoneSuggestion(suggestion, score);


        // Store the suggestion against the correct phoneId.
        // Store the suggestion against the correct phoneId.
        LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
        mSuggestionByPhoneId.put(suggestion.getPhoneId(), scoredSuggestion);
                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();
        }


        // Now perform auto time zone detection. The new suggestion may be used to modify the time
        // Now perform auto time zone detection. The new suggestion may be used to modify the time
        // zone setting.
        // zone setting.
@@ -398,13 +385,7 @@ public class TimeZoneDetectorStrategy {
        // rate-limit so age is not a strong indicator of confidence. Instead, the callers are
        // 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.
        // expected to withdraw suggestions they no longer have confidence in.
        for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
        for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
            LinkedList<QualifiedPhoneTimeZoneSuggestion> phoneSuggestions =
            QualifiedPhoneTimeZoneSuggestion candidateSuggestion = mSuggestionByPhoneId.valueAt(i);
                    mSuggestionByPhoneId.valueAt(i);
            if (phoneSuggestions == null) {
                // Unexpected
                continue;
            }
            QualifiedPhoneTimeZoneSuggestion candidateSuggestion = phoneSuggestions.getFirst();
            if (candidateSuggestion == null) {
            if (candidateSuggestion == null) {
                // Unexpected
                // Unexpected
                continue;
                continue;
@@ -474,16 +455,7 @@ public class TimeZoneDetectorStrategy {


        ipw.println("Phone suggestion history:");
        ipw.println("Phone suggestion history:");
        ipw.increaseIndent(); // level 2
        ipw.increaseIndent(); // level 2
        for (Map.Entry<Integer, LinkedList<QualifiedPhoneTimeZoneSuggestion>> entry
        mSuggestionByPhoneId.dump(ipw);
                : mSuggestionByPhoneId.entrySet()) {
            ipw.println("Phone " + entry.getKey());

            ipw.increaseIndent(); // level 3
            for (QualifiedPhoneTimeZoneSuggestion suggestion : entry.getValue()) {
                ipw.println(suggestion);
            }
            ipw.decreaseIndent(); // level 3
        }
        ipw.decreaseIndent(); // level 2
        ipw.decreaseIndent(); // level 2
        ipw.decreaseIndent(); // level 1
        ipw.decreaseIndent(); // level 1
        ipw.flush();
        ipw.flush();
@@ -494,12 +466,7 @@ public class TimeZoneDetectorStrategy {
     */
     */
    @VisibleForTesting
    @VisibleForTesting
    public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int phoneId) {
    public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int phoneId) {
        LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
        return mSuggestionByPhoneId.get(phoneId);
                mSuggestionByPhoneId.get(phoneId);
        if (suggestions == null) {
            return null;
        }
        return suggestions.getFirst();
    }
    }


    /**
    /**
+180 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading