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