Home | History | Annotate | Download | only in telephony
      1 /*
      2  * Copyright 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.telephony;
     18 
     19 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
     20 
     21 import android.telephony.Rlog;
     22 
     23 import com.android.internal.annotations.VisibleForTesting;
     24 
     25 import java.util.Calendar;
     26 import java.util.TimeZone;
     27 
     28 /**
     29  * Represents NITZ data. Various static methods are provided to help with parsing and intepretation
     30  * of NITZ data.
     31  *
     32  * {@hide}
     33  */
     34 @VisibleForTesting(visibility = PACKAGE)
     35 public final class NitzData {
     36     private static final String LOG_TAG = ServiceStateTracker.LOG_TAG;
     37     private static final int MS_PER_QUARTER_HOUR = 15 * 60 * 1000;
     38 
     39     /* Time stamp after 19 January 2038 is not supported under 32 bit */
     40     private static final int MAX_NITZ_YEAR = 2037;
     41 
     42     // Stored For logging / debugging only.
     43     private final String mOriginalString;
     44 
     45     private final int mZoneOffset;
     46 
     47     private final Integer mDstOffset;
     48 
     49     private final long mCurrentTimeMillis;
     50 
     51     private final TimeZone mEmulatorHostTimeZone;
     52 
     53     private NitzData(String originalString, int zoneOffsetMillis, Integer dstOffsetMillis,
     54             long utcTimeMillis, TimeZone emulatorHostTimeZone) {
     55         if (originalString == null) {
     56             throw new NullPointerException("originalString==null");
     57         }
     58         this.mOriginalString = originalString;
     59         this.mZoneOffset = zoneOffsetMillis;
     60         this.mDstOffset = dstOffsetMillis;
     61         this.mCurrentTimeMillis = utcTimeMillis;
     62         this.mEmulatorHostTimeZone = emulatorHostTimeZone;
     63     }
     64 
     65     /**
     66      * Parses the supplied NITZ string, returning the encoded data.
     67      */
     68     public static NitzData parse(String nitz) {
     69         // "yy/mm/dd,hh:mm:ss(+/-)tz[,dt[,tzid]]"
     70         // tz, dt are in number of quarter-hours
     71 
     72         try {
     73             /* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
     74              * offset as well (which we won't worry about until later) */
     75             Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
     76             c.clear();
     77             c.set(Calendar.DST_OFFSET, 0);
     78 
     79             String[] nitzSubs = nitz.split("[/:,+-]");
     80 
     81             int year = 2000 + Integer.parseInt(nitzSubs[0]);
     82             if (year > MAX_NITZ_YEAR) {
     83                 if (ServiceStateTracker.DBG) {
     84                     Rlog.e(LOG_TAG, "NITZ year: " + year + " exceeds limit, skip NITZ time update");
     85                 }
     86                 return null;
     87             }
     88             c.set(Calendar.YEAR, year);
     89 
     90             // month is 0 based!
     91             int month = Integer.parseInt(nitzSubs[1]) - 1;
     92             c.set(Calendar.MONTH, month);
     93 
     94             int date = Integer.parseInt(nitzSubs[2]);
     95             c.set(Calendar.DATE, date);
     96 
     97             int hour = Integer.parseInt(nitzSubs[3]);
     98             c.set(Calendar.HOUR, hour);
     99 
    100             int minute = Integer.parseInt(nitzSubs[4]);
    101             c.set(Calendar.MINUTE, minute);
    102 
    103             int second = Integer.parseInt(nitzSubs[5]);
    104             c.set(Calendar.SECOND, second);
    105 
    106             // The offset received from NITZ is the offset to add to get current local time.
    107             boolean sign = (nitz.indexOf('-') == -1);
    108             int totalUtcOffsetQuarterHours = Integer.parseInt(nitzSubs[6]);
    109             int totalUtcOffsetMillis =
    110                     (sign ? 1 : -1) * totalUtcOffsetQuarterHours * MS_PER_QUARTER_HOUR;
    111 
    112             // DST correction is already applied to the UTC offset. We could subtract it if we
    113             // wanted the raw offset.
    114             Integer dstAdjustmentQuarterHours =
    115                     (nitzSubs.length >= 8) ? Integer.parseInt(nitzSubs[7]) : null;
    116             Integer dstAdjustmentMillis = null;
    117             if (dstAdjustmentQuarterHours != null) {
    118                 dstAdjustmentMillis = dstAdjustmentQuarterHours * MS_PER_QUARTER_HOUR;
    119             }
    120 
    121             // As a special extension, the Android emulator appends the name of
    122             // the host computer's timezone to the nitz string. this is zoneinfo
    123             // timezone name of the form Area!Location or Area!Location!SubLocation
    124             // so we need to convert the ! into /
    125             TimeZone zone = null;
    126             if (nitzSubs.length >= 9) {
    127                 String tzname = nitzSubs[8].replace('!', '/');
    128                 zone = TimeZone.getTimeZone(tzname);
    129             }
    130             return new NitzData(nitz, totalUtcOffsetMillis, dstAdjustmentMillis,
    131                     c.getTimeInMillis(), zone);
    132         } catch (RuntimeException ex) {
    133             Rlog.e(LOG_TAG, "NITZ: Parsing NITZ time " + nitz + " ex=" + ex);
    134             return null;
    135         }
    136     }
    137 
    138     /** A method for use in tests to create NitzData instances. */
    139     public static NitzData createForTests(int zoneOffsetMillis, Integer dstOffsetMillis,
    140             long utcTimeMillis, TimeZone emulatorHostTimeZone) {
    141         return new NitzData("Test data", zoneOffsetMillis, dstOffsetMillis, utcTimeMillis,
    142                 emulatorHostTimeZone);
    143     }
    144 
    145     /**
    146      * Returns the current time as the number of milliseconds since the beginning of the Unix epoch
    147      * (1/1/1970 00:00:00 UTC).
    148      */
    149     public long getCurrentTimeInMillis() {
    150         return mCurrentTimeMillis;
    151     }
    152 
    153     /**
    154      * Returns the total offset to apply to the {@link #getCurrentTimeInMillis()} to arrive at a
    155      * local time.
    156      */
    157     public int getLocalOffsetMillis() {
    158         return mZoneOffset;
    159     }
    160 
    161     /**
    162      * Returns the offset (already included in {@link #getLocalOffsetMillis()}) associated with
    163      * Daylight Savings Time (DST). This field is optional: {@code null} means the DST offset is
    164      * unknown.
    165      */
    166     public Integer getDstAdjustmentMillis() {
    167         return mDstOffset;
    168     }
    169 
    170     /**
    171      * Returns {@link true} if the time is in Daylight Savings Time (DST), {@link false} if it is
    172      * unknown or not in DST. See {@link #getDstAdjustmentMillis()}.
    173      */
    174     public boolean isDst() {
    175         return mDstOffset != null && mDstOffset != 0;
    176     }
    177 
    178 
    179     /**
    180      * Returns the time zone of the host computer when Android is running in an emulator. It is
    181      * {@code null} for real devices. This information is communicated via a non-standard Android
    182      * extension to NITZ.
    183      */
    184     public TimeZone getEmulatorHostTimeZone() {
    185         return mEmulatorHostTimeZone;
    186     }
    187 
    188     @Override
    189     public boolean equals(Object o) {
    190         if (this == o) {
    191             return true;
    192         }
    193         if (o == null || getClass() != o.getClass()) {
    194             return false;
    195         }
    196 
    197         NitzData nitzData = (NitzData) o;
    198 
    199         if (mZoneOffset != nitzData.mZoneOffset) {
    200             return false;
    201         }
    202         if (mCurrentTimeMillis != nitzData.mCurrentTimeMillis) {
    203             return false;
    204         }
    205         if (!mOriginalString.equals(nitzData.mOriginalString)) {
    206             return false;
    207         }
    208         if (mDstOffset != null ? !mDstOffset.equals(nitzData.mDstOffset)
    209                 : nitzData.mDstOffset != null) {
    210             return false;
    211         }
    212         return mEmulatorHostTimeZone != null ? mEmulatorHostTimeZone
    213                 .equals(nitzData.mEmulatorHostTimeZone) : nitzData.mEmulatorHostTimeZone == null;
    214     }
    215 
    216     @Override
    217     public int hashCode() {
    218         int result = mOriginalString.hashCode();
    219         result = 31 * result + mZoneOffset;
    220         result = 31 * result + (mDstOffset != null ? mDstOffset.hashCode() : 0);
    221         result = 31 * result + (int) (mCurrentTimeMillis ^ (mCurrentTimeMillis >>> 32));
    222         result = 31 * result + (mEmulatorHostTimeZone != null ? mEmulatorHostTimeZone.hashCode()
    223                 : 0);
    224         return result;
    225     }
    226 
    227     @Override
    228     public String toString() {
    229         return "NitzData{"
    230                 + "mOriginalString=" + mOriginalString
    231                 + ", mZoneOffset=" + mZoneOffset
    232                 + ", mDstOffset=" + mDstOffset
    233                 + ", mCurrentTimeMillis=" + mCurrentTimeMillis
    234                 + ", mEmulatorHostTimeZone=" + mEmulatorHostTimeZone
    235                 + '}';
    236     }
    237 }
    238