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

Commit b4db55a1 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Move time zone lookup logic"

parents 33d94ef1 a0f09cee
Loading
Loading
Loading
Loading
+4 −44
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import android.telephony.Rlog;
import com.android.internal.annotations.VisibleForTesting;

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
@@ -35,7 +34,6 @@ import java.util.TimeZone;
@VisibleForTesting(visibility = PACKAGE)
public final class NitzData {
    private static final String LOG_TAG = ServiceStateTracker.LOG_TAG;
    private static final int MS_PER_HOUR = 60 * 60 * 1000;
    private static final int MS_PER_QUARTER_HOUR = 15 * 60 * 1000;

    /* Time stamp after 19 January 2038 is not supported under 32 bit */
@@ -53,7 +51,7 @@ public final class NitzData {
    private final TimeZone mEmulatorHostTimeZone;

    private NitzData(String originalString, int zoneOffsetMillis, Integer dstOffsetMillis,
            long utcTimeMillis, TimeZone timeZone) {
            long utcTimeMillis, TimeZone emulatorHostTimeZone) {
        if (originalString == null) {
            throw new NullPointerException("originalString==null");
        }
@@ -61,7 +59,7 @@ public final class NitzData {
        this.mZoneOffset = zoneOffsetMillis;
        this.mDstOffset = dstOffsetMillis;
        this.mCurrentTimeMillis = utcTimeMillis;
        this.mEmulatorHostTimeZone = timeZone;
        this.mEmulatorHostTimeZone = emulatorHostTimeZone;
    }

    /**
@@ -139,9 +137,9 @@ public final class NitzData {

    /** A method for use in tests to create NitzData instances. */
    public static NitzData createForTests(int zoneOffsetMillis, Integer dstOffsetMillis,
            long utcTimeMillis, TimeZone timeZone) {
            long utcTimeMillis, TimeZone emulatorHostTimeZone) {
        return new NitzData("Test data", zoneOffsetMillis, dstOffsetMillis, utcTimeMillis,
                timeZone);
                emulatorHostTimeZone);
    }

    /**
@@ -187,44 +185,6 @@ public final class NitzData {
        return mEmulatorHostTimeZone;
    }

    /**
     * Using information present in the supplied {@link NitzData} object, guess the time zone.
     * Because multiple time zones can have the same offset / DST state at a given time this process
     * is error prone; an arbitrary match is returned when there are multiple candidates. The
     * algorithm can also return a non-exact match by assuming that the DST information provided by
     * NITZ is incorrect. This method can return {@code null} if no time zones are found.
     */
    public static TimeZone guessTimeZone(NitzData nitzData) {
        int offset = nitzData.getLocalOffsetMillis();
        boolean dst = nitzData.isDst();
        long when = nitzData.getCurrentTimeInMillis();
        TimeZone guess = findTimeZone(offset, dst, when);
        if (guess == null) {
            // Couldn't find a proper timezone.  Perhaps the DST data is wrong.
            guess = findTimeZone(offset, !dst, when);
        }
        return guess;
    }

    private static TimeZone findTimeZone(int offset, boolean dst, long when) {
        int rawOffset = offset;
        if (dst) {
            rawOffset -= MS_PER_HOUR;
        }
        String[] zones = TimeZone.getAvailableIDs(rawOffset);
        TimeZone guess = null;
        Date d = new Date(when);
        for (String zone : zones) {
            TimeZone tz = TimeZone.getTimeZone(zone);
            if (tz.getOffset(when) == offset && tz.inDaylightTime(d) == dst) {
                guess = tz;
                break;
            }
        }

        return guess;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
+42 −100
Original line number Diff line number Diff line
@@ -30,12 +30,11 @@ import android.util.TimeUtils;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.util.TimeStampedValue;
import com.android.internal.util.IndentingPrintWriter;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import java.util.TimeZone;

/**
@@ -117,63 +116,9 @@ public class NitzStateMachine {
        }
    }

    /** A pair containing a value and an associated time stamp. */
    private static class TimeStampedValue<T> {
        /** The value. */
        final T mValue;

        /**
         * The value of {@link SystemClock#elapsedRealtime} or equivalent when value was
         * determined.
         */
        final long mElapsedRealtime;

        TimeStampedValue(T value, long elapsedRealtime) {
            this.mValue = value;
            this.mElapsedRealtime = elapsedRealtime;
        }

        @Override
        public String toString() {
            return "TimeStampedValue{"
                    + "mValue=" + mValue
                    + ", mElapsedRealtime=" + mElapsedRealtime
                    + '}';
        }
    }

    private static final String LOG_TAG = ServiceStateTracker.LOG_TAG;
    private static final boolean DBG = ServiceStateTracker.DBG;

    /**
     * List of ISO codes for countries that can have an offset of
     * GMT+0 when not in daylight savings time.  This ignores some
     * small places such as the Canary Islands (Spain) and
     * Danmarkshavn (Denmark).  The list must be sorted by code.
     */
    private static final String[] GMT_COUNTRY_CODES = {
            "bf", // Burkina Faso
            "ci", // Cote d'Ivoire
            "eh", // Western Sahara
            "fo", // Faroe Islands, Denmark
            "gb", // United Kingdom of Great Britain and Northern Ireland
            "gh", // Ghana
            "gm", // Gambia
            "gn", // Guinea
            "gw", // Guinea Bissau
            "ie", // Ireland
            "lr", // Liberia
            "is", // Iceland
            "ma", // Morocco
            "ml", // Mali
            "mr", // Mauritania
            "pt", // Portugal
            "sl", // Sierra Leone
            "sn", // Senegal
            "st", // Sao Tome and Principe
            "tg", // Togo
    };

    // Time detection state.

    /**
@@ -212,6 +157,7 @@ public class NitzStateMachine {
    private final GsmCdmaPhone mPhone;
    private final DeviceState mDeviceState;
    private final TimeServiceHelper mTimeServiceHelper;
    private final TimeZoneLookupHelper mTimeZoneLookupHelper;
    /** Wake lock used while setting time of day. */
    private final PowerManager.WakeLock mWakeLock;
    private static final String WAKELOCK_TAG = "NitzStateMachine";
@@ -219,12 +165,13 @@ public class NitzStateMachine {
    public NitzStateMachine(GsmCdmaPhone phone) {
        this(phone,
                TelephonyComponentFactory.getInstance().makeTimeServiceHelper(phone.getContext()),
                new DeviceState(phone));
                new DeviceState(phone),
                new TimeZoneLookupHelper());
    }

    @VisibleForTesting
    public NitzStateMachine(GsmCdmaPhone phone, TimeServiceHelper timeServiceHelper,
            DeviceState deviceState) {
            DeviceState deviceState, TimeZoneLookupHelper timeZoneLookupHelper) {
        mPhone = phone;

        Context context = phone.getContext();
@@ -233,6 +180,7 @@ public class NitzStateMachine {
        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);

        mDeviceState = deviceState;
        mTimeZoneLookupHelper = timeZoneLookupHelper;
        mTimeServiceHelper = timeServiceHelper;
        mTimeServiceHelper.setListener(new TimeServiceHelper.Listener() {
            @Override
@@ -266,23 +214,22 @@ public class NitzStateMachine {
                    + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
                    + " mNitzData=" + mNitzData
                    + " iso-cc='" + isoCountryCode
                    + "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode));
                    + "' countryUsesUtc=" + mTimeZoneLookupHelper.countryUsesUtc(isoCountryCode));
        }
        TimeZone zone;
        String zoneId;
        if ("".equals(isoCountryCode) && mNeedFixZoneAfterNitz) {
            // Country code not found.  This is likely a test network.
            // Get a TimeZone based only on the NITZ parameters (best guess).

            // mNeedFixZoneAfterNitz is only set to true when mNitzData is set so there's no need to
            // check mNitzData == null.
            zone = NitzData.guessTimeZone(mNitzData);
            zoneId = mTimeZoneLookupHelper.guessZoneIdByNitz(mNitzData);
            if (DBG) {
                Rlog.d(LOG_TAG, "fixTimeZone(): guessNitzTimeZone returned "
                        + (zone == null ? zone : zone.getID()));
                Rlog.d(LOG_TAG, "fixTimeZone(): guessNitzTimeZone returned " + zoneId);
            }
        } else if ((mNitzData == null || nitzOffsetMightBeBogus(mNitzData))
                && isTimeZoneSettingInitialized
                && (Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) < 0)) {
                && !mTimeZoneLookupHelper.countryUsesUtc(isoCountryCode)) {

            // This case means that (1) the device received no NITZ signal yet or received an NITZ
            // signal that looks bogus due to having a zero offset from UTC, (2) the device has a
@@ -290,7 +237,8 @@ public class NitzStateMachine {
            // zero offset. This is interpreted as being NITZ incorrectly reporting a local time and
            // not a UTC time. The zone is left as the current device's zone setting, and the time
            // may be adjusted by assuming the current zone setting is correct.
            zone = TimeZone.getDefault();
            TimeZone zone = TimeZone.getDefault();
            zoneId = zone.getID();

            // Note that mNeedFixZoneAfterNitz => (implies) { mNitzData != null }. Therefore, if
            // mNitzData == null, mNeedFixZoneAfterNitz cannot be true. The code in this section
@@ -330,15 +278,14 @@ public class NitzStateMachine {
        } else if (mNitzData == null) {
            // The use of 1/1/1970 UTC is unusual but consistent with historical behavior when
            // it wasn't possible to detect whether a previous NITZ signal had been saved.
            zone = TimeUtils.getTimeZone(0 /* offset */, false /* dst */, 0 /* when */,
                    isoCountryCode);
            zoneId = mTimeZoneLookupHelper.guessZoneIdByInstantOffsetDstCountry(
                    0 /* when */, 0 /* offset */, false /* dst */, isoCountryCode);
            if (DBG) {
                Rlog.d(LOG_TAG, "fixTimeZone: No cached NITZ data available, using only country"
                        + " code. zone=" + zone);
                        + " code. zone=" + zoneId);
            }
        } else {
            zone = TimeUtils.getTimeZone(mNitzData.getLocalOffsetMillis(), mNitzData.isDst(),
                    mNitzData.getCurrentTimeInMillis(), isoCountryCode);
            zoneId = mTimeZoneLookupHelper.guessZoneIdByNitzCountry(mNitzData, isoCountryCode);
            if (DBG) {
                Rlog.d(LOG_TAG, "fixTimeZone: using getTimeZone(off, dst, time, iso)");
            }
@@ -348,18 +295,18 @@ public class NitzStateMachine {
                + " mNitzData=" + mNitzData
                + " iso-cc=" + isoCountryCode
                + " mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz
                + " zone=" + (zone != null ? zone.getID() : "NULL");
                + " zoneId=" + zoneId;
        mTimeZoneLog.log(tmpLog);

        if (zone != null) {
            Rlog.d(LOG_TAG, "fixTimeZone: zone != null zone.getID=" + zone.getID());
        if (zoneId != null) {
            Rlog.d(LOG_TAG, "fixTimeZone: zone != null zoneId=" + zoneId);
            if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
                setAndBroadcastNetworkSetTimeZone(zone.getID());
                setAndBroadcastNetworkSetTimeZone(zoneId);
            } else {
                Rlog.d(LOG_TAG, "fixTimeZone: skip changing zone as getAutoTimeZone was false");
            }
            if (mNeedFixZoneAfterNitz) {
                saveNitzTimeZone(zone.getID());
                saveNitzTimeZone(zoneId);
            }
        } else {
            Rlog.d(LOG_TAG, "fixTimeZone: zone == null, do nothing for zone");
@@ -389,27 +336,22 @@ public class NitzStateMachine {
    private void setTimeZoneFromNitz(NitzData newNitzData, long nitzReceiveTime) {
        try {
            String iso = mDeviceState.getNetworkCountryIsoForPhone();
            TimeZone zone;
            String zoneId;
            if (newNitzData.getEmulatorHostTimeZone() != null) {
                zone = newNitzData.getEmulatorHostTimeZone();
                zoneId = newNitzData.getEmulatorHostTimeZone().getID();
            } else {
                if (!mGotCountryCode) {
                    zone = null;
                    zoneId = null;
                } else if (iso != null && iso.length() > 0) {
                    zone = TimeUtils.getTimeZone(
                            newNitzData.getLocalOffsetMillis(),
                            newNitzData.isDst(),
                            newNitzData.getCurrentTimeInMillis(),
                            iso);
                    zoneId = mTimeZoneLookupHelper.guessZoneIdByNitzCountry(newNitzData, iso);
                } else {
                    // We don't have a valid iso country code.  This is
                    // most likely because we're on a test network that's
                    // using a bogus MCC (eg, "001"), so get a TimeZone
                    // based only on the NITZ parameters.
                    zone = NitzData.guessTimeZone(newNitzData);
                    zoneId = mTimeZoneLookupHelper.guessZoneIdByNitz(newNitzData);
                    if (DBG) {
                        Rlog.d(LOG_TAG, "setTimeFromNITZ(): guessNitzTimeZone returned "
                                + (zone == null ? zone : zone.getID()));
                        Rlog.d(LOG_TAG, "setTimeZoneFromNitz(): findByNitz returned " + zoneId);
                    }
                }
            }
@@ -425,7 +367,7 @@ public class NitzStateMachine {
                previousUtcOffset = mNitzData.getLocalOffsetMillis();
                previousIsDst = mNitzData.isDst();
            }
            if ((zone == null)
            if ((zoneId == null)
                    || (newNitzData.getLocalOffsetMillis() != previousUtcOffset)
                    || (newNitzData.isDst() != previousIsDst)) {
                // We got the time before the country or the zone has changed
@@ -437,7 +379,7 @@ public class NitzStateMachine {

            String tmpLog = "NITZ: newNitzData=" + newNitzData
                    + " nitzReceiveTime=" + nitzReceiveTime
                    + " zone=" + (zone != null ? zone.getID() : "NULL")
                    + " zoneId=" + zoneId
                    + " iso=" + iso + " mGotCountryCode=" + mGotCountryCode
                    + " mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz
                    + " isTimeZoneDetectionEnabled()="
@@ -447,12 +389,12 @@ public class NitzStateMachine {
            }
            mTimeZoneLog.log(tmpLog);

            if (zone != null) {
            if (zoneId != null) {
                if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
                    setAndBroadcastNetworkSetTimeZone(zone.getID());
                    setAndBroadcastNetworkSetTimeZone(zoneId);
                }
                mNitzTimeZoneDetectionSuccessful = true;
                saveNitzTimeZone(zone.getID());
                saveNitzTimeZone(zoneId);
            }
        } catch (RuntimeException ex) {
            Rlog.e(LOG_TAG, "NITZ: Processing NITZ data " + newNitzData + " ex=" + ex);
@@ -642,27 +584,27 @@ public class NitzStateMachine {
    }

    /**
     * Update time zone by network country code, works on countries which only have one time zone.
     * Update time zone by network country code, works well on countries which only have one time
     * zone or multiple zones with the same offset.
     *
     * @param iso Country code from network MCC
     */
    public void updateTimeZoneByNetworkCountryCode(String iso) {
        List<String> uniqueZoneIds = TimeUtils.getTimeZoneIdsWithUniqueOffsets(iso);
        if (uniqueZoneIds.size() == 1) {
            String zoneId = uniqueZoneIds.get(0);
        String zoneId = mTimeZoneLookupHelper.guessZoneIdByCountry(
                iso, mDeviceState.currentTimeMillis());
        if (zoneId != null) {
            if (DBG) {
                Rlog.d(LOG_TAG, "updateTimeZoneByNetworkCountryCode: no nitz but one TZ for iso-cc="
                        + iso
                        + " with zone.getID=" + zoneId);
                Rlog.d(LOG_TAG, "updateTimeZoneByNetworkCountryCode: set time zone=" + zoneId
                        + " iso=" + iso);
            }

            mTimeZoneLog.log("updateTimeZoneByNetworkCountryCode: set time zone=" + zoneId
                    + " iso=" + iso);
            setAndBroadcastNetworkSetTimeZone(zoneId);
        } else {
            if (DBG) {
                Rlog.d(LOG_TAG,
                        "updateTimeZoneByNetworkCountryCode: there are " + uniqueZoneIds.size()
                                + " unique offsets for iso-cc='" + iso
                        "updateTimeZoneByNetworkCountryCode: no good zone for iso-cc='" + iso
                                + "', do nothing");
            }
        }
+1 −1
Original line number Diff line number Diff line
@@ -3092,7 +3092,7 @@ public class ServiceStateTracker extends Handler {
            if (lastNitzData == null) {
                tzone = null;
            } else {
                tzone = NitzData.guessTimeZone(lastNitzData);
                tzone = TimeZoneLookupHelper.guessZoneByNitzStatic(lastNitzData);
                if (ServiceStateTracker.DBG) {
                    log("fixUnknownMcc(): guessNitzTimeZone returned "
                            + (tzone == null ? tzone : tzone.getID()));
+173 −0
Original line number Diff line number Diff line
/*
 * Copyright 2017 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.internal.telephony;

import android.util.TimeUtils;

import libcore.util.CountryTimeZones;
import libcore.util.TimeZoneFinder;

import java.util.Arrays;
import java.util.Date;
import java.util.TimeZone;

/**
 * An interface to various time zone lookup behaviors.
 */
// Non-final to allow mocking.
public class TimeZoneLookupHelper {
    private static final int MS_PER_HOUR = 60 * 60 * 1000;

    /**
     * List of ISO codes for countries that can have an offset of
     * GMT+0 when not in daylight savings time.  This ignores some
     * small places such as the Canary Islands (Spain) and
     * Danmarkshavn (Denmark).  The list must be sorted by code.
     */
    private static final String[] GMT_COUNTRY_CODES = {
            "bf", // Burkina Faso
            "ci", // Cote d'Ivoire
            "eh", // Western Sahara
            "fo", // Faroe Islands, Denmark
            "gb", // United Kingdom of Great Britain and Northern Ireland
            "gh", // Ghana
            "gm", // Gambia
            "gn", // Guinea
            "gw", // Guinea Bissau
            "ie", // Ireland
            "lr", // Liberia
            "is", // Iceland
            "ma", // Morocco
            "ml", // Mali
            "mr", // Mauritania
            "pt", // Portugal
            "sl", // Sierra Leone
            "sn", // Senegal
            "st", // Sao Tome and Principe
            "tg", // Togo
    };

    public TimeZoneLookupHelper() {}

    /**
     * Finds a time zone ID that fits the supplied NITZ and country information.
     *
     * <p><em>Note:</em> When there are multiple matching zones then one of the matching candidates
     * will be returned. If the current device default zone matches it will be returned in
     * preference to other candidates. This method can return {@code null} if no matching time
     * zones are found.
     */
    public String guessZoneIdByNitzCountry(NitzData nitzData, String isoCountryCode) {
        return guessZoneIdByInstantOffsetDstCountry(
                nitzData.getCurrentTimeInMillis(),
                nitzData.getLocalOffsetMillis(),
                nitzData.isDst(),
                isoCountryCode);
    }

    /**
     * Finds a time zone ID that fits the supplied time / offset and country information.
     *
     * <p><em>Note:</em> When there are multiple matching zones then one of the matching candidates
     * will be returned. If the current device default zone matches it will be returned in
     * preference to other candidates. This method can return {@code null} if no matching time
     * zones are found.
     */
    public String guessZoneIdByInstantOffsetDstCountry(
            long timeMillis, int utcOffsetMillis, boolean isDst, String isoCountryCode) {
        TimeZone timeZone =
                TimeUtils.getTimeZone(utcOffsetMillis, isDst, timeMillis, isoCountryCode);
        return timeZone == null ? null : timeZone.getID();
    }

    /**
     * Finds a time zone ID using only information present in the supplied {@link NitzData} object.
     *
     * <p><em>Note:</em> Because multiple time zones can have the same offset / DST state at a given
     * time this process is error prone; an arbitrary match is returned when there are multiple
     * candidates. The algorithm can also return a non-exact match by assuming that the DST
     * information provided by NITZ is incorrect. This method can return {@code null} if no matching
     * time zones are found.
     */
    public String guessZoneIdByNitz(NitzData nitzData) {
        TimeZone zone = guessZoneByNitzStatic(nitzData);
        return zone == null ? null : zone.getID();
    }

    /**
     * Returns a time zone ID for the country if possible. For counties that use a single time zone
     * this will provide a good choice. For countries with multiple time zones, a time zone is
     * returned if all time zones used in the country currently have the same offset (currently ==
     * according to the device's current system clock time). If this is not the case then
     * {@code null} can be returned.
     */
    public String guessZoneIdByCountry(String isoCountryCode, long whenMillis) {
        CountryTimeZones countryTimeZones =
                TimeZoneFinder.getInstance().lookupCountryTimeZones(isoCountryCode);
        if (countryTimeZones == null) {
            // Unknown country code.
            return null;
        }

        if (countryTimeZones.isDefaultOkForCountryTimeZoneDetection(whenMillis)) {
            return countryTimeZones.getDefaultTimeZoneId();
        }
        return null;
    }

    /** Static method for use by {@link ServiceStateTracker}. */
    static TimeZone guessZoneByNitzStatic(NitzData nitzData) {
        int utcOffsetMillis = nitzData.getLocalOffsetMillis();
        boolean isDst = nitzData.isDst();
        long timeMillis = nitzData.getCurrentTimeInMillis();

        TimeZone guess = guessByInstantOffsetDst(timeMillis, utcOffsetMillis, isDst);
        if (guess == null) {
            // Couldn't find a proper timezone.  Perhaps the DST data is wrong.
            guess = guessByInstantOffsetDst(timeMillis, utcOffsetMillis, !isDst);
        }
        return guess;
    }

    private static TimeZone guessByInstantOffsetDst(long timeMillis, int utcOffsetMillis,
            boolean isDst) {
        int rawOffset = utcOffsetMillis;
        if (isDst) {
            rawOffset -= MS_PER_HOUR;
        }
        String[] zones = TimeZone.getAvailableIDs(rawOffset);
        TimeZone guess = null;
        Date d = new Date(timeMillis);
        for (String zone : zones) {
            TimeZone tz = TimeZone.getTimeZone(zone);
            if (tz.getOffset(timeMillis) == utcOffsetMillis && tz.inDaylightTime(d) == isDst) {
                guess = tz;
                break;
            }
        }

        return guess;
    }

    /**
     * Returns {@code true} if the supplied (lower-case) ISO country code is for a country known to
     * use a raw offset of zero from UTC.
     */
    public boolean countryUsesUtc(String isoCountryCode) {
        return Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) >= 0;
    }
}
+73 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading