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