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