157 lines
5.2 KiB
Java
157 lines
5.2 KiB
Java
/*
|
|
* 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();
|
|
}
|
|
}
|