/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.os; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.AtomicFile; import android.util.Log; import android.util.Xml; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; /** * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset * on reboot, but keeps going. */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class MonotonicClock { private static final String TAG = "MonotonicClock"; private static final String XML_TAG_MONOTONIC_TIME = "monotonic_time"; private static final String XML_ATTR_TIMESHIFT = "timeshift"; private final AtomicFile mFile; private final Clock mClock; private final long mTimeshift; public static final long UNDEFINED = -1; public MonotonicClock(File file) { this (file, Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK); } public MonotonicClock(long monotonicTime, @NonNull Clock clock) { this(null, monotonicTime, clock); } public MonotonicClock(@Nullable File file, long monotonicTime, @NonNull Clock clock) { mClock = clock; if (file != null) { mFile = new AtomicFile(file); mTimeshift = read(monotonicTime - mClock.elapsedRealtime()); } else { mFile = null; mTimeshift = monotonicTime - mClock.elapsedRealtime(); } } /** * Returns time in milliseconds, based on SystemClock.elapsedTime, adjusted so that * after a device reboot the time keeps increasing. */ public long monotonicTime() { return monotonicTime(mClock.elapsedRealtime()); } /** * Like {@link #monotonicTime()}, except the elapsed time is supplied as an argument instead * of being read from the Clock. */ public long monotonicTime(long elapsedRealtimeMs) { return mTimeshift + elapsedRealtimeMs; } private long read(long defaultTimeshift) { if (!mFile.exists()) { return defaultTimeshift; } try { return readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser()); } catch (IOException e) { Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e); return defaultTimeshift; } } /** * Saves the timeshift into a file. Call this method just before system shutdown, after * writing the last battery history event. */ public void write() { if (mFile == null) { return; } FileOutputStream out = null; try { out = mFile.startWrite(); writeXml(out, Xml.newBinarySerializer()); mFile.finishWrite(out); } catch (IOException e) { Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e); mFile.failWrite(out); } } /** * Parses an XML file containing the persistent state of the monotonic clock. */ private long readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException { long savedTimeshift = 0; try { parser.setInput(inputStream, StandardCharsets.UTF_8.name()); int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_TAG && parser.getName().equals(XML_TAG_MONOTONIC_TIME)) { savedTimeshift = parser.getAttributeLong(null, XML_ATTR_TIMESHIFT); } eventType = parser.next(); } } catch (XmlPullParserException e) { throw new IOException(e); } return savedTimeshift - mClock.elapsedRealtime(); } /** * Creates an XML file containing the persistent state of the monotonic clock. */ private void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException { serializer.setOutput(out, StandardCharsets.UTF_8.name()); serializer.startDocument(null, true); serializer.startTag(null, XML_TAG_MONOTONIC_TIME); serializer.attributeLong(null, XML_ATTR_TIMESHIFT, monotonicTime()); serializer.endTag(null, XML_TAG_MONOTONIC_TIME); serializer.endDocument(); } }