1 /* 2 * Copyright (C) 2019 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 android.app.timedetector; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.time.UnixEpochTime; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.os.ShellCommand; 25 26 import java.io.PrintWriter; 27 import java.util.ArrayList; 28 import java.util.Collections; 29 import java.util.List; 30 import java.util.Objects; 31 32 /** 33 * A time suggestion from an identified telephony source. e.g. from NITZ information from a specific 34 * radio. 35 * 36 * <p>{@code slotIndex} identifies the suggestion source. This enables detection logic to identify 37 * suggestions from the same source when there are several in use. 38 * 39 * <p>{@code unixEpochTime}. When not {@code null}, the {@code unixEpochTime.value} is the number of 40 * milliseconds elapsed since 1/1/1970 00:00:00 UTC. The {@code unixEpochTime.referenceTimeMillis} 41 * is the value of the elapsed realtime clock when the {@code unixEpochTime.value} was established. 42 * Note that the elapsed realtime clock is considered accurate but it is volatile, so time 43 * suggestions cannot be persisted across device resets. {@code unixEpochTime} can be {@code null} 44 * to indicate that the telephony source has entered an "un-opinionated" state and any previous 45 * suggestion from the source is being withdrawn. 46 * 47 * <p>{@code debugInfo} contains debugging metadata associated with the suggestion. This is used to 48 * record why the suggestion exists, e.g. what triggered it to be made and what heuristic was used 49 * to determine the time or its absence. This information exists only to aid in debugging and 50 * therefore is used by {@link #toString()}, but it is not for use in detection logic and is not 51 * considered in {@link #hashCode()} or {@link #equals(Object)}. 52 * 53 * @hide 54 */ 55 public final class TelephonyTimeSuggestion implements Parcelable { 56 57 /** @hide */ 58 public static final @NonNull Parcelable.Creator<TelephonyTimeSuggestion> CREATOR = 59 new Parcelable.Creator<TelephonyTimeSuggestion>() { 60 public TelephonyTimeSuggestion createFromParcel(Parcel in) { 61 return TelephonyTimeSuggestion.createFromParcel(in); 62 } 63 64 public TelephonyTimeSuggestion[] newArray(int size) { 65 return new TelephonyTimeSuggestion[size]; 66 } 67 }; 68 69 private final int mSlotIndex; 70 @Nullable private final UnixEpochTime mUnixEpochTime; 71 @Nullable private ArrayList<String> mDebugInfo; 72 TelephonyTimeSuggestion(Builder builder)73 private TelephonyTimeSuggestion(Builder builder) { 74 mSlotIndex = builder.mSlotIndex; 75 mUnixEpochTime = builder.mUnixEpochTime; 76 mDebugInfo = builder.mDebugInfo != null ? new ArrayList<>(builder.mDebugInfo) : null; 77 } 78 createFromParcel(Parcel in)79 private static TelephonyTimeSuggestion createFromParcel(Parcel in) { 80 int slotIndex = in.readInt(); 81 UnixEpochTime unixEpochTime = 82 in.readParcelable(null /* classLoader */, UnixEpochTime.class); 83 TelephonyTimeSuggestion suggestion = new TelephonyTimeSuggestion.Builder(slotIndex) 84 .setUnixEpochTime(unixEpochTime) 85 .build(); 86 @SuppressWarnings("unchecked") 87 ArrayList<String> debugInfo = in.readArrayList( 88 null /* classLoader */, java.lang.String.class); 89 if (debugInfo != null) { 90 suggestion.addDebugInfo(debugInfo); 91 } 92 return suggestion; 93 } 94 95 /** @hide */ parseCommandLineArg(@onNull ShellCommand cmd)96 public static TelephonyTimeSuggestion parseCommandLineArg(@NonNull ShellCommand cmd) 97 throws IllegalArgumentException { 98 Integer slotIndex = null; 99 Long elapsedRealtimeMillis = null; 100 Long unixEpochTimeMillis = null; 101 String opt; 102 while ((opt = cmd.getNextArg()) != null) { 103 switch (opt) { 104 case "--slot_index": { 105 slotIndex = Integer.parseInt(cmd.getNextArgRequired()); 106 break; 107 } 108 case "--reference_time": 109 case "--elapsed_realtime": { 110 elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired()); 111 break; 112 } 113 case "--unix_epoch_time": { 114 unixEpochTimeMillis = Long.parseLong(cmd.getNextArgRequired()); 115 break; 116 } 117 default: { 118 throw new IllegalArgumentException("Unknown option: " + opt); 119 } 120 } 121 } 122 123 if (slotIndex == null) { 124 throw new IllegalArgumentException("No slotIndex specified."); 125 } 126 if (elapsedRealtimeMillis == null) { 127 throw new IllegalArgumentException("No elapsedRealtimeMillis specified."); 128 } 129 if (unixEpochTimeMillis == null) { 130 throw new IllegalArgumentException("No unixEpochTimeMillis specified."); 131 } 132 133 UnixEpochTime timeSignal = new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis); 134 Builder builder = new Builder(slotIndex) 135 .setUnixEpochTime(timeSignal) 136 .addDebugInfo("Command line injection"); 137 return builder.build(); 138 } 139 140 /** @hide */ printCommandLineOpts(PrintWriter pw)141 public static void printCommandLineOpts(PrintWriter pw) { 142 pw.println("Telephony suggestion options:"); 143 pw.println(" --slot_index <number>"); 144 pw.println(" --elapsed_realtime <elapsed realtime millis>"); 145 pw.println(" --unix_epoch_time <Unix epoch time millis>"); 146 pw.println(); 147 pw.println("See " + TelephonyTimeSuggestion.class.getName() + " for more information"); 148 } 149 150 @Override describeContents()151 public int describeContents() { 152 return 0; 153 } 154 155 @Override writeToParcel(@onNull Parcel dest, int flags)156 public void writeToParcel(@NonNull Parcel dest, int flags) { 157 dest.writeInt(mSlotIndex); 158 dest.writeParcelable(mUnixEpochTime, 0); 159 dest.writeList(mDebugInfo); 160 } 161 162 /** 163 * Returns an identifier for the source of this suggestion. 164 * 165 * <p>See {@link TelephonyTimeSuggestion} for more information about {@code slotIndex}. 166 */ getSlotIndex()167 public int getSlotIndex() { 168 return mSlotIndex; 169 } 170 171 /** 172 * Returns the suggested time or {@code null} if there isn't one. 173 * 174 * <p>See {@link TelephonyTimeSuggestion} for more information about {@code unixEpochTime}. 175 */ 176 @Nullable getUnixEpochTime()177 public UnixEpochTime getUnixEpochTime() { 178 return mUnixEpochTime; 179 } 180 181 /** 182 * Returns debug metadata for the suggestion. 183 * 184 * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}. 185 */ 186 @NonNull getDebugInfo()187 public List<String> getDebugInfo() { 188 return mDebugInfo == null 189 ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo); 190 } 191 192 /** 193 * Associates information with the instance that can be useful for debugging / logging. 194 * 195 * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}. 196 */ addDebugInfo(@onNull String debugInfo)197 public void addDebugInfo(@NonNull String debugInfo) { 198 if (mDebugInfo == null) { 199 mDebugInfo = new ArrayList<>(); 200 } 201 mDebugInfo.add(debugInfo); 202 } 203 204 /** 205 * Associates information with the instance that can be useful for debugging / logging. 206 * 207 * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}. 208 */ addDebugInfo(@onNull List<String> debugInfo)209 public void addDebugInfo(@NonNull List<String> debugInfo) { 210 if (mDebugInfo == null) { 211 mDebugInfo = new ArrayList<>(debugInfo.size()); 212 } 213 mDebugInfo.addAll(debugInfo); 214 } 215 216 @Override equals(@ullable Object o)217 public boolean equals(@Nullable Object o) { 218 if (this == o) { 219 return true; 220 } 221 if (o == null || getClass() != o.getClass()) { 222 return false; 223 } 224 TelephonyTimeSuggestion that = (TelephonyTimeSuggestion) o; 225 return mSlotIndex == that.mSlotIndex 226 && Objects.equals(mUnixEpochTime, that.mUnixEpochTime); 227 } 228 229 @Override hashCode()230 public int hashCode() { 231 return Objects.hash(mSlotIndex, mUnixEpochTime); 232 } 233 234 @Override toString()235 public String toString() { 236 return "TelephonyTimeSuggestion{" 237 + "mSlotIndex='" + mSlotIndex + '\'' 238 + ", mUnixEpochTime=" + mUnixEpochTime 239 + ", mDebugInfo=" + mDebugInfo 240 + '}'; 241 } 242 243 /** 244 * Builds {@link TelephonyTimeSuggestion} instances. 245 * 246 * @hide 247 */ 248 public static final class Builder { 249 private final int mSlotIndex; 250 @Nullable private UnixEpochTime mUnixEpochTime; 251 @Nullable private List<String> mDebugInfo; 252 253 /** 254 * Creates a builder with the specified {@code slotIndex}. 255 * 256 * <p>See {@link TelephonyTimeSuggestion} for more information about {@code slotIndex}. 257 */ Builder(int slotIndex)258 public Builder(int slotIndex) { 259 mSlotIndex = slotIndex; 260 } 261 262 /** 263 * Returns the builder for call chaining. 264 * 265 * <p>See {@link TelephonyTimeSuggestion} for more information about {@code unixEpochTime}. 266 */ 267 @NonNull setUnixEpochTime(@ullable UnixEpochTime unixEpochTime)268 public Builder setUnixEpochTime(@Nullable UnixEpochTime unixEpochTime) { 269 mUnixEpochTime = unixEpochTime; 270 return this; 271 } 272 273 /** 274 * Returns the builder for call chaining. 275 * 276 * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}. 277 */ 278 @NonNull addDebugInfo(@onNull String debugInfo)279 public Builder addDebugInfo(@NonNull String debugInfo) { 280 if (mDebugInfo == null) { 281 mDebugInfo = new ArrayList<>(); 282 } 283 mDebugInfo.add(debugInfo); 284 return this; 285 } 286 287 /** Returns the {@link TelephonyTimeSuggestion}. */ 288 @NonNull build()289 public TelephonyTimeSuggestion build() { 290 return new TelephonyTimeSuggestion(this); 291 } 292 } 293 } 294