1 /* 2 * Copyright (C) 2022 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.ShellCommand; 24 25 import java.io.PrintWriter; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.Collections; 29 import java.util.List; 30 import java.util.Objects; 31 32 /** 33 * A delegate class to support time suggestion classes that could diverge in the future. This class 34 * exists purely for code re-use and provides support methods. It avoids class inheritance 35 * deliberately to allow each suggestion to evolve in different directions later without affecting 36 * SDK APIs. 37 * 38 * <p>{@code unixEpochTime} is the suggested time. The {@code unixEpochTime.value} is the number of 39 * milliseconds elapsed since 1/1/1970 00:00:00 UTC according to the Unix time system. The {@code 40 * unixEpochTime.referenceTimeMillis} is the value of the elapsed realtime clock when the {@code 41 * unixEpochTime.value} was established. Note that the elapsed realtime clock is considered accurate 42 * but it is volatile, so time suggestions cannot be persisted across device resets. 43 * 44 * <p>{@code debugInfo} contains debugging metadata associated with the suggestion. This is used to 45 * record why the suggestion exists and how it was entered. This information exists only to aid in 46 * debugging and therefore is used by {@link #toString()}, but it is not for use in detection 47 * logic and is not considered in {@link #hashCode()} or {@link #equals(Object)}. 48 * 49 * @hide 50 */ 51 public final class TimeSuggestionHelper { 52 53 @NonNull private final Class<?> mHelpedClass; 54 @NonNull private final UnixEpochTime mUnixEpochTime; 55 @Nullable private ArrayList<String> mDebugInfo; 56 57 /** Creates a helper for the specified class, containing the supplied properties. */ TimeSuggestionHelper(@onNull Class<?> helpedClass, @NonNull UnixEpochTime unixEpochTime)58 public TimeSuggestionHelper(@NonNull Class<?> helpedClass, 59 @NonNull UnixEpochTime unixEpochTime) { 60 mHelpedClass = Objects.requireNonNull(helpedClass); 61 mUnixEpochTime = Objects.requireNonNull(unixEpochTime); 62 } 63 64 /** See {@link TimeSuggestionHelper} for property details. */ 65 @NonNull getUnixEpochTime()66 public UnixEpochTime getUnixEpochTime() { 67 return mUnixEpochTime; 68 } 69 70 /** See {@link TimeSuggestionHelper} for information about {@code debugInfo}. */ 71 @NonNull getDebugInfo()72 public List<String> getDebugInfo() { 73 return mDebugInfo == null 74 ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo); 75 } 76 77 /** 78 * Associates information with the instance that can be useful for debugging / logging. 79 * 80 * <p>See {@link TimeSuggestionHelper} for more information about {@code debugInfo}. 81 */ addDebugInfo(@onNull String debugInfo)82 public void addDebugInfo(@NonNull String debugInfo) { 83 if (mDebugInfo == null) { 84 mDebugInfo = new ArrayList<>(); 85 } 86 mDebugInfo.add(debugInfo); 87 } 88 89 /** 90 * Associates information with the instance that can be useful for debugging / logging. The 91 * information is present in {@link #toString()} but is not considered for 92 * {@link #equals(Object)} and {@link #hashCode()}. 93 */ addDebugInfo(String... debugInfos)94 public void addDebugInfo(String... debugInfos) { 95 addDebugInfo(Arrays.asList(debugInfos)); 96 } 97 98 /** 99 * Associates information with the instance that can be useful for debugging / logging. 100 * 101 * <p>See {@link TimeSuggestionHelper} for more information about {@code debugInfo}. 102 */ addDebugInfo(@onNull List<String> debugInfo)103 public void addDebugInfo(@NonNull List<String> debugInfo) { 104 if (mDebugInfo == null) { 105 mDebugInfo = new ArrayList<>(debugInfo.size()); 106 } 107 mDebugInfo.addAll(debugInfo); 108 } 109 110 /** 111 * Implemented in case users call this insteam of {@link #handleEquals(TimeSuggestionHelper)}. 112 */ 113 @Override equals(Object o)114 public boolean equals(Object o) { 115 if (this == o) { 116 return true; 117 } 118 if (o == null || getClass() != o.getClass()) { 119 return false; 120 } 121 TimeSuggestionHelper that = (TimeSuggestionHelper) o; 122 return handleEquals(that); 123 } 124 125 /** Used to implement {@link Object#equals(Object)}. */ handleEquals(TimeSuggestionHelper o)126 public boolean handleEquals(TimeSuggestionHelper o) { 127 return Objects.equals(mHelpedClass, o.mHelpedClass) 128 && Objects.equals(mUnixEpochTime, o.mUnixEpochTime); 129 } 130 131 @Override hashCode()132 public int hashCode() { 133 return Objects.hash(mUnixEpochTime); 134 } 135 136 /** Used to implement {@link Object#toString()}. */ handleToString()137 public String handleToString() { 138 return mHelpedClass.getSimpleName() + "{" 139 + "mUnixEpochTime=" + mUnixEpochTime 140 + ", mDebugInfo=" + mDebugInfo 141 + '}'; 142 } 143 144 /** Constructs a helper with suggestion state from a Parcel. */ handleCreateFromParcel(@onNull Class<?> helpedClass, @NonNull Parcel in)145 public static TimeSuggestionHelper handleCreateFromParcel(@NonNull Class<?> helpedClass, 146 @NonNull Parcel in) { 147 @SuppressWarnings("unchecked") 148 UnixEpochTime unixEpochTime = 149 in.readParcelable(null /* classLoader */, UnixEpochTime.class); 150 TimeSuggestionHelper suggestionHelper = 151 new TimeSuggestionHelper(helpedClass, unixEpochTime); 152 suggestionHelper.mDebugInfo = in.readArrayList(null /* classLoader */, String.class); 153 return suggestionHelper; 154 } 155 156 /** Writes the helper suggestion state to a Parcel. */ handleWriteToParcel(@onNull Parcel dest, int flags)157 public void handleWriteToParcel(@NonNull Parcel dest, int flags) { 158 dest.writeParcelable(mUnixEpochTime, 0); 159 dest.writeList(mDebugInfo); 160 } 161 162 /** Parses command line args to create a {@link TimeSuggestionHelper}. */ handleParseCommandLineArg( @onNull Class<?> helpedClass, @NonNull ShellCommand cmd)163 public static TimeSuggestionHelper handleParseCommandLineArg( 164 @NonNull Class<?> helpedClass, @NonNull ShellCommand cmd) 165 throws IllegalArgumentException { 166 Long elapsedRealtimeMillis = null; 167 Long unixEpochTimeMillis = null; 168 String opt; 169 while ((opt = cmd.getNextArg()) != null) { 170 switch (opt) { 171 case "--reference_time": 172 case "--elapsed_realtime": { 173 elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired()); 174 break; 175 } 176 case "--unix_epoch_time": { 177 unixEpochTimeMillis = Long.parseLong(cmd.getNextArgRequired()); 178 break; 179 } 180 default: { 181 throw new IllegalArgumentException("Unknown option: " + opt); 182 } 183 } 184 } 185 186 if (elapsedRealtimeMillis == null) { 187 throw new IllegalArgumentException("No referenceTimeMillis specified."); 188 } 189 if (unixEpochTimeMillis == null) { 190 throw new IllegalArgumentException("No unixEpochTimeMillis specified."); 191 } 192 193 UnixEpochTime timeSignal = new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis); 194 TimeSuggestionHelper suggestionHelper = new TimeSuggestionHelper(helpedClass, timeSignal); 195 suggestionHelper.addDebugInfo("Command line injection"); 196 return suggestionHelper; 197 } 198 199 /** Prints the command line args needed to create a {@link TimeSuggestionHelper}. */ handlePrintCommandLineOpts( @onNull PrintWriter pw, @NonNull String typeName, @NonNull Class<?> clazz)200 public static void handlePrintCommandLineOpts( 201 @NonNull PrintWriter pw, @NonNull String typeName, @NonNull Class<?> clazz) { 202 pw.printf("%s suggestion options:\n", typeName); 203 pw.println(" --elapsed_realtime <elapsed realtime millis> - the elapsed realtime millis" 204 + " when unix epoch time was read"); 205 pw.println(" --unix_epoch_time <Unix epoch time millis>"); 206 pw.println(); 207 pw.println("See " + clazz.getName() + " for more information"); 208 } 209 } 210