Merge "Make AtomicFile a public API. It's about time!" into jb-mr1-dev
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 d8f9204..0bc0f91 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;