Add SystemApi(MODULE_LIBRARIES) to AtomicFile API

Cleaning up the API in the process

Test: atest android.jobscheduler.cts
Bug: 142281756

Change-Id: Ia52dc6fda867f3015ecbf068a0a69cc0f17cd92a
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 2f5f555..f2a5580 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -36,6 +36,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SystemConfigFileCommitEventLogger;
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
@@ -102,6 +103,7 @@
     private boolean mWriteInProgress;
 
     private static final Object sSingletonLock = new Object();
+    private final SystemConfigFileCommitEventLogger mEventLogger;
     private final AtomicFile mJobsFile;
     /** Handler backed by IoThread for writing to disk. */
     private final Handler mIoHandler = IoThread.getHandler();
@@ -141,7 +143,8 @@
         File systemDir = new File(dataDir, "system");
         File jobDir = new File(systemDir, "job");
         jobDir.mkdirs();
-        mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"), "jobs");
+        mEventLogger = new SystemConfigFileCommitEventLogger("jobs");
+        mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"), mEventLogger);
 
         mJobSet = new JobSet();
 
@@ -426,7 +429,7 @@
             int numSystemJobs = 0;
             int numSyncJobs = 0;
             try {
-                final long startTime = SystemClock.uptimeMillis();
+                mEventLogger.setStartTime(SystemClock.uptimeMillis());
                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
                 XmlSerializer out = new FastXmlSerializer();
                 out.setOutput(baos, StandardCharsets.UTF_8.name());
@@ -459,7 +462,7 @@
                 out.endDocument();
 
                 // Write out to disk in one fell swoop.
-                FileOutputStream fos = mJobsFile.startWrite(startTime);
+                FileOutputStream fos = mJobsFile.startWrite();
                 fos.write(baos.toByteArray());
                 mJobsFile.finishWrite(fos);
             } catch (IOException e) {
diff --git a/api/current.txt b/api/current.txt
index d897c8b..533734b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -51408,6 +51408,7 @@
     method public void failWrite(java.io.FileOutputStream);
     method public void finishWrite(java.io.FileOutputStream);
     method public java.io.File getBaseFile();
+    method public long getLastModifiedTime();
     method public java.io.FileInputStream openRead() throws java.io.FileNotFoundException;
     method public byte[] readFully() throws java.io.IOException;
     method public java.io.FileOutputStream startWrite() throws java.io.IOException;
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 032e30c..b505ce9 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -183,9 +183,18 @@
 
 package android.util {
 
+  public class AtomicFile {
+    ctor public AtomicFile(@NonNull java.io.File, @Nullable android.util.SystemConfigFileCommitEventLogger);
+  }
+
   public final class Log {
     method public static int logToRadioBuffer(int, @Nullable String, @Nullable String);
   }
 
+  public class SystemConfigFileCommitEventLogger {
+    ctor public SystemConfigFileCommitEventLogger(@NonNull String);
+    method public void setStartTime(long);
+  }
+
 }
 
diff --git a/core/java/android/annotation/UptimeMillisLong.java b/core/java/android/annotation/UptimeMillisLong.java
new file mode 100644
index 0000000..8946eea
--- /dev/null
+++ b/core/java/android/annotation/UptimeMillisLong.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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 android.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.os.SystemClock;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc Value is a non-negative timestamp in the
+ *            {@link SystemClock#uptimeMillis()} time base.
+ * @paramDoc Value is a non-negative timestamp in the
+ *            {@link SystemClock#uptimeMillis()} time base.
+ * @returnDoc Value is a non-negative timestamp in the
+ *            {@link SystemClock#uptimeMillis()} time base.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface UptimeMillisLong {
+}
diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
index cf7ed9b..944d7ba 100644
--- a/core/java/android/util/AtomicFile.java
+++ b/core/java/android/util/AtomicFile.java
@@ -16,6 +16,11 @@
 
 package android.util;
 
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.os.FileUtils;
 import android.os.SystemClock;
 
@@ -48,15 +53,14 @@
 public class AtomicFile {
     private final File mBaseName;
     private final File mBackupName;
-    private final String mCommitTag;
-    private long mStartTime;
+    private SystemConfigFileCommitEventLogger mCommitEventLogger;
 
     /**
      * Create a new AtomicFile for a file located at the given File path.
      * The secondary backup file will be the same file path with ".bak" appended.
      */
     public AtomicFile(File baseName) {
-        this(baseName, null);
+        this(baseName, (SystemConfigFileCommitEventLogger) null);
     }
 
     /**
@@ -64,9 +68,22 @@
      * automatically log commit events.
      */
     public AtomicFile(File baseName, String commitTag) {
+        this(baseName, new SystemConfigFileCommitEventLogger(commitTag));
+    }
+
+    /**
+     * Internal constructor that also allows you to have the class
+     * automatically log commit events.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @SuppressLint("StreamFiles")
+    public AtomicFile(@NonNull File baseName,
+            @Nullable SystemConfigFileCommitEventLogger commitEventLogger) {
         mBaseName = baseName;
         mBackupName = new File(baseName.getPath() + ".bak");
-        mCommitTag = commitTag;
+        mCommitEventLogger = commitEventLogger;
     }
 
     /**
@@ -100,7 +117,7 @@
      * access to AtomicFile.
      */
     public FileOutputStream startWrite() throws IOException {
-        return startWrite(mCommitTag != null ? SystemClock.uptimeMillis() : 0);
+        return startWrite(0);
     }
 
     /**
@@ -108,9 +125,19 @@
      * start time of the operation to adjust how the commit is logged.
      * @param startTime The effective start time of the operation, in the time
      * base of {@link SystemClock#uptimeMillis()}.
+     *
+     * @deprecated Use {@link SystemConfigFileCommitEventLogger#setStartTime} followed
+     * by {@link #startWrite()}
      */
+    @Deprecated
     public FileOutputStream startWrite(long startTime) throws IOException {
-        mStartTime = startTime;
+        if (mCommitEventLogger != null) {
+            if (startTime != 0) {
+                mCommitEventLogger.setStartTime(startTime);
+            }
+
+            mCommitEventLogger.onStartWrite();
+        }
 
         // Rename the current file so it may be used as a backup during the next read
         if (mBaseName.exists()) {
@@ -159,9 +186,8 @@
             } catch (IOException e) {
                 Log.w("AtomicFile", "finishWrite: Got exception:", e);
             }
-            if (mCommitTag != null) {
-                com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
-                        mCommitTag, SystemClock.uptimeMillis() - mStartTime);
+            if (mCommitEventLogger != null) {
+                mCommitEventLogger.onFinishWrite();
             }
         }
     }
@@ -240,11 +266,11 @@
 
     /**
      * Gets the last modified time of the atomic file.
-     * {@hide}
      *
      * @return last modified time in milliseconds since epoch.  Returns zero if
      *     the file does not exist or an I/O error is encountered.
      */
+    @CurrentTimeMillisLong
     public long getLastModifiedTime() {
         if (mBackupName.exists()) {
             return mBackupName.lastModified();
diff --git a/core/java/android/util/SystemConfigFileCommitEventLogger.java b/core/java/android/util/SystemConfigFileCommitEventLogger.java
new file mode 100644
index 0000000..04d72fb
--- /dev/null
+++ b/core/java/android/util/SystemConfigFileCommitEventLogger.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 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 android.util;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.UptimeMillisLong;
+import android.os.SystemClock;
+
+/**
+ * Writes an EventLog event capturing the performance of system config file writes.
+ * The event log entry is formatted like this:
+ * <code>525000 commit_sys_config_file (name|3),(time|2|3)</code>, where <code>name</code> is
+ * a short unique name representing the type of configuration file and <code>time</code> is
+ * duration in the {@link SystemClock#uptimeMillis()} time base.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class SystemConfigFileCommitEventLogger {
+    private final String mName;
+    private long mStartTime;
+
+    /**
+     * @param name The short name of the config file that is included in the event log event,
+     *             e.g. "jobs", "appops", "uri-grants" etc.
+     */
+    public SystemConfigFileCommitEventLogger(@NonNull String name) {
+        mName = name;
+    }
+
+    /**
+     * Override the start timestamp.  Use this method when it's desired to include the time
+     * taken by the preparation of the configuration data in the overall duration of the
+     * "commitSysConfigFile" event.
+     *
+     * @param startTime Overridden start time, in system uptime milliseconds
+     */
+    public void setStartTime(@UptimeMillisLong long startTime) {
+        mStartTime = startTime;
+    }
+
+    /**
+     * Invoked just before the configuration file writing begins.
+     */
+    void onStartWrite() {
+        if (mStartTime == 0) {
+            mStartTime = SystemClock.uptimeMillis();
+        }
+    }
+
+    /**
+     * Invoked just after the configuration file writing ends.
+     */
+    void onFinishWrite() {
+        com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(mName,
+                SystemClock.uptimeMillis() - mStartTime);
+    }
+}