Make AtomicFile a public API.  It's about time!

Change-Id: Ib34e294747405b7ab709cb0bbb2d9a0cc80ce86a
diff --git a/api/current.txt b/api/current.txt
index f987d5e..491bc07 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22524,6 +22524,17 @@
     ctor public AndroidRuntimeException(java.lang.Exception);
   }
 
+  public class AtomicFile {
+    ctor public AtomicFile(java.io.File);
+    method public void delete();
+    method public void failWrite(java.io.FileOutputStream);
+    method public void finishWrite(java.io.FileOutputStream);
+    method public java.io.File getBaseFile();
+    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;
+  }
+
   public abstract interface AttributeSet {
     method public abstract boolean getAttributeBooleanValue(java.lang.String, java.lang.String, boolean);
     method public abstract boolean getAttributeBooleanValue(int, boolean);
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 226e107..773e0fe 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -16,7 +16,6 @@
 
 package android.content;
 
-import com.android.internal.os.AtomicFile;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 
@@ -37,7 +36,7 @@
 import android.os.Parcel;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.os.SystemClock;
+import android.util.AtomicFile;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.Xml;
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index b1fc788..0d84d73 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -26,6 +26,7 @@
 import android.content.res.XmlResourceParser;
 import android.os.Environment;
 import android.os.Handler;
+import android.util.AtomicFile;
 import android.util.Log;
 import android.util.AttributeSet;
 import android.util.Xml;
@@ -44,7 +45,6 @@
 import java.io.IOException;
 import java.io.FileInputStream;
 
-import com.android.internal.os.AtomicFile;
 import com.android.internal.util.FastXmlSerializer;
 
 import com.google.android.collect.Maps;
diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
new file mode 100644
index 0000000..4fca570
--- /dev/null
+++ b/core/java/android/util/AtomicFile.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2009 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.os.FileUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Helper class for performing atomic operations on a file by creating a
+ * backup file until a write has successfully completed.  If you need this
+ * on older versions of the platform you can use
+ * {@link android.support.v4.util.AtomicFile} in the v4 support library.
+ * <p>
+ * Atomic file guarantees file integrity by ensuring that a file has
+ * been completely written and sync'd to disk before removing its backup.
+ * As long as the backup file exists, the original file is considered
+ * to be invalid (left over from a previous attempt to write the file).
+ * </p><p>
+ * Atomic file does not confer any file locking semantics.
+ * Do not use this class when the file may be accessed or modified concurrently
+ * by multiple threads or processes.  The caller is responsible for ensuring
+ * appropriate mutual exclusion invariants whenever it accesses the file.
+ * </p>
+ */
+public class AtomicFile {
+    private final File mBaseName;
+    private final File mBackupName;
+
+    /**
+     * 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) {
+        mBaseName = baseName;
+        mBackupName = new File(baseName.getPath() + ".bak");
+    }
+
+    /**
+     * Return the path to the base file.  You should not generally use this,
+     * as the data at that path may not be valid.
+     */
+    public File getBaseFile() {
+        return mBaseName;
+    }
+
+    /**
+     * Delete the atomic file.  This deletes both the base and backup files.
+     */
+    public void delete() {
+        mBaseName.delete();
+        mBackupName.delete();
+    }
+
+    /**
+     * Start a new write operation on the file.  This returns a FileOutputStream
+     * to which you can write the new file data.  The existing file is replaced
+     * with the new data.  You <em>must not</em> directly close the given
+     * FileOutputStream; instead call either {@link #finishWrite(FileOutputStream)}
+     * or {@link #failWrite(FileOutputStream)}.
+     *
+     * <p>Note that if another thread is currently performing
+     * a write, this will simply replace whatever that thread is writing
+     * with the new file being written by this thread, and when the other
+     * thread finishes the write the new write operation will no longer be
+     * safe (or will be lost).  You must do your own threading protection for
+     * access to AtomicFile.
+     */
+    public FileOutputStream startWrite() throws IOException {
+        // Rename the current file so it may be used as a backup during the next read
+        if (mBaseName.exists()) {
+            if (!mBackupName.exists()) {
+                if (!mBaseName.renameTo(mBackupName)) {
+                    Log.w("AtomicFile", "Couldn't rename file " + mBaseName
+                            + " to backup file " + mBackupName);
+                }
+            } else {
+                mBaseName.delete();
+            }
+        }
+        FileOutputStream str = null;
+        try {
+            str = new FileOutputStream(mBaseName);
+        } catch (FileNotFoundException e) {
+            File parent = mBaseName.getParentFile();
+            if (!parent.mkdir()) {
+                throw new IOException("Couldn't create directory " + mBaseName);
+            }
+            FileUtils.setPermissions(
+                parent.getPath(),
+                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                -1, -1);
+            try {
+                str = new FileOutputStream(mBaseName);
+            } catch (FileNotFoundException e2) {
+                throw new IOException("Couldn't create " + mBaseName);
+            }
+        }
+        return str;
+    }
+
+    /**
+     * Call when you have successfully finished writing to the stream
+     * returned by {@link #startWrite()}.  This will close, sync, and
+     * commit the new data.  The next attempt to read the atomic file
+     * will return the new file stream.
+     */
+    public void finishWrite(FileOutputStream str) {
+        if (str != null) {
+            FileUtils.sync(str);
+            try {
+                str.close();
+                mBackupName.delete();
+            } catch (IOException e) {
+                Log.w("AtomicFile", "finishWrite: Got exception:", e);
+            }
+        }
+    }
+
+    /**
+     * Call when you have failed for some reason at writing to the stream
+     * returned by {@link #startWrite()}.  This will close the current
+     * write stream, and roll back to the previous state of the file.
+     */
+    public void failWrite(FileOutputStream str) {
+        if (str != null) {
+            FileUtils.sync(str);
+            try {
+                str.close();
+                mBaseName.delete();
+                mBackupName.renameTo(mBaseName);
+            } catch (IOException e) {
+                Log.w("AtomicFile", "failWrite: Got exception:", e);
+            }
+        }
+    }
+
+    /** @hide
+     * @deprecated This is not safe.
+     */
+    @Deprecated public void truncate() throws IOException {
+        try {
+            FileOutputStream fos = new FileOutputStream(mBaseName);
+            FileUtils.sync(fos);
+            fos.close();
+        } catch (FileNotFoundException e) {
+            throw new IOException("Couldn't append " + mBaseName);
+        } catch (IOException e) {
+        }
+    }
+
+    /** @hide
+     * @deprecated This is not safe.
+     */
+    @Deprecated public FileOutputStream openAppend() throws IOException {
+        try {
+            return new FileOutputStream(mBaseName, true);
+        } catch (FileNotFoundException e) {
+            throw new IOException("Couldn't append " + mBaseName);
+        }
+    }
+
+    /**
+     * Open the atomic file for reading.  If there previously was an
+     * incomplete write, this will roll back to the last good data before
+     * opening for read.  You should call close() on the FileInputStream when
+     * you are done reading from it.
+     *
+     * <p>Note that if another thread is currently performing
+     * a write, this will incorrectly consider it to be in the state of a bad
+     * write and roll back, causing the new data currently being written to
+     * be dropped.  You must do your own threading protection for access to
+     * AtomicFile.
+     */
+    public FileInputStream openRead() throws FileNotFoundException {
+        if (mBackupName.exists()) {
+            mBaseName.delete();
+            mBackupName.renameTo(mBaseName);
+        }
+        return new FileInputStream(mBaseName);
+    }
+
+    /**
+     * A convenience for {@link #openRead()} that also reads all of the
+     * file contents into a byte array which is returned.
+     */
+    public byte[] readFully() throws IOException {
+        FileInputStream stream = openRead();
+        try {
+            int pos = 0;
+            int avail = stream.available();
+            byte[] data = new byte[avail];
+            while (true) {
+                int amt = stream.read(data, pos, data.length-pos);
+                //Log.i("foo", "Read " + amt + " bytes at " + pos
+                //        + " of avail " + data.length);
+                if (amt <= 0) {
+                    //Log.i("foo", "**** FINISHED READING: pos=" + pos
+                    //        + " len=" + data.length);
+                    return data;
+                }
+                pos += amt;
+                avail = stream.available();
+                if (avail > data.length-pos) {
+                    byte[] newData = new byte[pos+avail];
+                    System.arraycopy(data, 0, newData, 0, pos);
+                    data = newData;
+                }
+            }
+        } finally {
+            stream.close();
+        }
+    }
+}
diff --git a/core/java/com/android/internal/util/JournaledFile.java b/core/java/com/android/internal/util/JournaledFile.java
index af0c6c6..eeffc16 100644
--- a/core/java/com/android/internal/util/JournaledFile.java
+++ b/core/java/com/android/internal/util/JournaledFile.java
@@ -19,6 +19,15 @@
 import java.io.File;
 import java.io.IOException;
 
+/**
+ * @Deprecated Use {@link com.android.internal.os.AtomicFile} instead.  It would
+ * be nice to update all existing uses of this to switch to AtomicFile, but since
+ * their on-file semantics are slightly different that would run the risk of losing
+ * data if at the point of the platform upgrade to the new code it would need to
+ * roll back to the backup file.  This can be solved...  but is it worth it and
+ * all of the testing needed to make sure it is correct?
+ */
+@Deprecated
 public class JournaledFile {
     File mReal;
     File mTemp;
diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java
index 8836bac..46b968a 100644
--- a/services/java/com/android/server/AppWidgetServiceImpl.java
+++ b/services/java/com/android/server/AppWidgetServiceImpl.java
@@ -44,6 +44,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserId;
+import android.util.AtomicFile;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Pair;
@@ -55,7 +56,6 @@
 import android.widget.RemoteViews;
 
 import com.android.internal.appwidget.IAppWidgetHost;
-import com.android.internal.os.AtomicFile;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.widget.IRemoteViewsAdapterConnection;
 import com.android.internal.widget.IRemoteViewsFactory;
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index fdb278d..1e39492 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -16,7 +16,6 @@
 package com.android.server;
 
 import com.android.internal.content.PackageMonitor;
-import com.android.internal.os.AtomicFile;
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.view.IInputContext;
@@ -74,6 +73,7 @@
 import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
 import android.text.style.SuggestionSpan;
+import android.util.AtomicFile;
 import android.util.EventLog;
 import android.util.LruCache;
 import android.util.Pair;
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index f6d3b608..9ab1a9d 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -53,6 +53,7 @@
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.AtomicFile;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
@@ -61,7 +62,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
 
-import com.android.internal.os.AtomicFile;
 import com.android.internal.statusbar.StatusBarNotification;
 import com.android.internal.util.FastXmlSerializer;
 
diff --git a/services/java/com/android/server/am/CompatModePackages.java b/services/java/com/android/server/am/CompatModePackages.java
index 3ba3fbb..3a6492e 100644
--- a/services/java/com/android/server/am/CompatModePackages.java
+++ b/services/java/com/android/server/am/CompatModePackages.java
@@ -4,7 +4,6 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 
@@ -12,7 +11,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
-import com.android.internal.os.AtomicFile;
 import com.android.internal.util.FastXmlSerializer;
 
 import android.app.ActivityManager;
@@ -24,6 +22,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.RemoteException;
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
 
diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java
index ba65f39..7059674 100644
--- a/services/java/com/android/server/am/UsageStatsService.java
+++ b/services/java/com/android/server/am/UsageStatsService.java
@@ -27,12 +27,12 @@
 import android.os.Process;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
 
 import com.android.internal.app.IUsageStats;
 import com.android.internal.content.PackageMonitor;
-import com.android.internal.os.AtomicFile;
 import com.android.internal.os.PkgUsageStats;
 import com.android.internal.util.FastXmlSerializer;
 
diff --git a/services/java/com/android/server/input/PersistentDataStore.java b/services/java/com/android/server/input/PersistentDataStore.java
index fbe3e8b..71de776 100644
--- a/services/java/com/android/server/input/PersistentDataStore.java
+++ b/services/java/com/android/server/input/PersistentDataStore.java
@@ -16,7 +16,6 @@
 
 package com.android.server.input;
 
-import com.android.internal.os.AtomicFile;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
@@ -25,6 +24,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
 
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index fe43d11..3eb29db 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -117,6 +117,7 @@
 import android.telephony.TelephonyManager;
 import android.text.format.Formatter;
 import android.text.format.Time;
+import android.util.AtomicFile;
 import android.util.Log;
 import android.util.NtpTrustedTime;
 import android.util.Slog;
@@ -127,7 +128,6 @@
 import android.util.Xml;
 
 import com.android.internal.R;
-import com.android.internal.os.AtomicFile;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Objects;
diff --git a/services/java/com/android/server/net/NetworkStatsCollection.java b/services/java/com/android/server/net/NetworkStatsCollection.java
index 9ddf011..60666b4 100644
--- a/services/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/java/com/android/server/net/NetworkStatsCollection.java
@@ -29,8 +29,8 @@
 import android.net.NetworkTemplate;
 import android.net.TrafficStats;
 import android.text.format.DateUtils;
+import android.util.AtomicFile;
 
-import com.android.internal.os.AtomicFile;
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Objects;