• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.os;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.util.AtomicFile;
22 import android.util.Log;
23 import android.util.Xml;
24 
25 import com.android.modules.utils.TypedXmlPullParser;
26 import com.android.modules.utils.TypedXmlSerializer;
27 
28 import org.xmlpull.v1.XmlPullParser;
29 import org.xmlpull.v1.XmlPullParserException;
30 
31 import java.io.ByteArrayInputStream;
32 import java.io.File;
33 import java.io.FileOutputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.OutputStream;
37 import java.nio.charset.StandardCharsets;
38 
39 /**
40  * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset
41  * on reboot, but keeps going.
42  */
43 @android.ravenwood.annotation.RavenwoodKeepWholeClass
44 public class MonotonicClock {
45     private static final String TAG = "MonotonicClock";
46 
47     private static final String XML_TAG_MONOTONIC_TIME = "monotonic_time";
48     private static final String XML_ATTR_TIMESHIFT = "timeshift";
49 
50     private final AtomicFile mFile;
51     private final Clock mClock;
52     private final long mTimeshift;
53 
54     public static final long UNDEFINED = -1;
55 
MonotonicClock(File file)56     public MonotonicClock(File file) {
57         this (file, Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK);
58     }
59 
MonotonicClock(long monotonicTime, @NonNull Clock clock)60     public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
61         this(null, monotonicTime, clock);
62     }
63 
MonotonicClock(@ullable File file, long monotonicTime, @NonNull Clock clock)64     public MonotonicClock(@Nullable File file, long monotonicTime, @NonNull Clock clock) {
65         mClock = clock;
66         if (file != null) {
67             mFile = new AtomicFile(file);
68             mTimeshift = read(monotonicTime - mClock.elapsedRealtime());
69         } else {
70             mFile = null;
71             mTimeshift = monotonicTime - mClock.elapsedRealtime();
72         }
73     }
74 
75     /**
76      * Returns time in milliseconds, based on SystemClock.elapsedTime, adjusted so that
77      * after a device reboot the time keeps increasing.
78      */
monotonicTime()79     public long monotonicTime() {
80         return monotonicTime(mClock.elapsedRealtime());
81     }
82 
83     /**
84      * Like {@link #monotonicTime()}, except the elapsed time is supplied as an argument instead
85      * of being read from the Clock.
86      */
monotonicTime(long elapsedRealtimeMs)87     public long monotonicTime(long elapsedRealtimeMs) {
88         return mTimeshift + elapsedRealtimeMs;
89     }
90 
read(long defaultTimeshift)91     private long read(long defaultTimeshift) {
92         if (!mFile.exists()) {
93             return defaultTimeshift;
94         }
95 
96         try {
97             return readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
98         } catch (IOException e) {
99             Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
100             return defaultTimeshift;
101         }
102     }
103 
104     /**
105      * Saves the timeshift into a file.  Call this method just before system shutdown, after
106      * writing the last battery history event.
107      */
write()108     public void write() {
109         if (mFile == null) {
110             return;
111         }
112 
113         FileOutputStream out = null;
114         try  {
115             out = mFile.startWrite();
116             writeXml(out, Xml.newBinarySerializer());
117             mFile.finishWrite(out);
118         } catch (IOException e) {
119             Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
120             mFile.failWrite(out);
121         }
122     }
123 
124     /**
125      * Parses an XML file containing the persistent state of the monotonic clock.
126      */
readXml(InputStream inputStream, TypedXmlPullParser parser)127     private long readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
128         long savedTimeshift = 0;
129         try {
130             parser.setInput(inputStream, StandardCharsets.UTF_8.name());
131             int eventType = parser.getEventType();
132             while (eventType != XmlPullParser.END_DOCUMENT) {
133                 if (eventType == XmlPullParser.START_TAG
134                         && parser.getName().equals(XML_TAG_MONOTONIC_TIME)) {
135                     savedTimeshift = parser.getAttributeLong(null, XML_ATTR_TIMESHIFT);
136                 }
137                 eventType = parser.next();
138             }
139         } catch (XmlPullParserException e) {
140             throw new IOException(e);
141         }
142         return savedTimeshift - mClock.elapsedRealtime();
143     }
144 
145     /**
146      * Creates an XML file containing the persistent state of the monotonic clock.
147      */
writeXml(OutputStream out, TypedXmlSerializer serializer)148     private void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
149         serializer.setOutput(out, StandardCharsets.UTF_8.name());
150         serializer.startDocument(null, true);
151         serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
152         serializer.attributeLong(null, XML_ATTR_TIMESHIFT, monotonicTime());
153         serializer.endTag(null, XML_TAG_MONOTONIC_TIME);
154         serializer.endDocument();
155     }
156 }
157