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