resolved conflicts for merge of 6b50986a to gingerbread-plus-aosp

Change-Id: I7c5ec7ae717e75ad242e2c1ad4537052e4eea718
This commit is contained in:
Brad Fitzpatrick
2010-08-27 12:08:40 -07:00
9 changed files with 325 additions and 79 deletions

View File

@ -1162,6 +1162,7 @@ public class Activity extends ContextThemeWrapper
*/
protected void onPause() {
mCalled = true;
QueuedWork.waitToFinish();
}
/**

View File

@ -152,7 +152,7 @@ public final class ActivityThread {
= new ArrayList<Application>();
// set of instantiated backup agents, keyed by package name
final HashMap<String, BackupAgent> mBackupAgents = new HashMap<String, BackupAgent>();
static final ThreadLocal sThreadLocal = new ThreadLocal();
static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal();
Instrumentation mInstrumentation;
String mInstrumentationAppDir = null;
String mInstrumentationAppPackage = null;
@ -186,6 +186,8 @@ public final class ActivityThread {
final GcIdler mGcIdler = new GcIdler();
boolean mGcIdlerScheduled = false;
static Handler sMainThreadHandler; // set once in main()
private static final class ActivityClientRecord {
IBinder token;
int ident;
@ -1111,7 +1113,7 @@ public final class ActivityThread {
}
public static final ActivityThread currentActivityThread() {
return (ActivityThread)sThreadLocal.get();
return sThreadLocal.get();
}
public static final String currentPackageName() {
@ -1780,6 +1782,8 @@ public final class ActivityThread {
}
}
QueuedWork.waitToFinish();
try {
if (data.sync) {
if (DEBUG_BROADCAST) Slog.i(TAG,
@ -2007,6 +2011,9 @@ public final class ActivityThread {
data.args.setExtrasClassLoader(s.getClassLoader());
}
int res = s.onStartCommand(data.args, data.flags, data.startId);
QueuedWork.waitToFinish();
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, 1, data.startId, res);
@ -2035,6 +2042,9 @@ public final class ActivityThread {
final String who = s.getClassName();
((ContextImpl) context).scheduleFinalCleanup(who, "Service");
}
QueuedWork.waitToFinish();
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
token, 0, 0, 0);
@ -3598,6 +3608,9 @@ public final class ActivityThread {
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = new Handler();
}
ActivityThread thread = new ActivityThread();
thread.attach(false);

View File

@ -116,9 +116,12 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
class ReceiverRestrictedContext extends ContextWrapper {
ReceiverRestrictedContext(Context base) {
@ -167,8 +170,8 @@ class ContextImpl extends Context {
private static ThrottleManager sThrottleManager;
private static WifiManager sWifiManager;
private static LocationManager sLocationManager;
private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs =
new HashMap<File, SharedPreferencesImpl>();
private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
new HashMap<String, SharedPreferencesImpl>();
private AudioManager mAudioManager;
/*package*/ LoadedApk mPackageInfo;
@ -332,15 +335,14 @@ class ContextImpl extends Context {
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
File f = getSharedPrefsFile(name);
synchronized (sSharedPrefs) {
sp = sSharedPrefs.get(f);
sp = sSharedPrefs.get(name);
if (sp != null && !sp.hasFileChanged()) {
//Log.i(TAG, "Returning existing prefs " + name + ": " + sp);
return sp;
}
}
File f = getSharedPrefsFile(name);
FileInputStream str = null;
File backup = makeBackupFile(f);
if (backup.exists()) {
@ -373,10 +375,10 @@ class ContextImpl extends Context {
//Log.i(TAG, "Updating existing prefs " + name + " " + sp + ": " + map);
sp.replace(map);
} else {
sp = sSharedPrefs.get(f);
sp = sSharedPrefs.get(name);
if (sp == null) {
sp = new SharedPreferencesImpl(f, mode, map);
sSharedPrefs.put(f, sp);
sSharedPrefs.put(name, sp);
}
}
return sp;
@ -2696,10 +2698,12 @@ class ContextImpl extends Context {
private final File mFile;
private final File mBackupFile;
private final int mMode;
private Map mMap;
private final FileStatus mFileStatus = new FileStatus();
private long mTimestamp;
private Map<String, Object> mMap; // guarded by 'this'
private long mTimestamp; // guarded by 'this'
private int mDiskWritesInFlight = 0; // guarded by 'this'
private final Object mWritingToDiskLock = new Object();
private static final Object mContent = new Object();
private WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners;
@ -2708,19 +2712,21 @@ class ContextImpl extends Context {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mMap = initialContents != null ? initialContents : new HashMap();
if (FileUtils.getFileStatus(file.getPath(), mFileStatus)) {
mTimestamp = mFileStatus.mtime;
mMap = initialContents != null ? initialContents : new HashMap<String, Object>();
FileStatus stat = new FileStatus();
if (FileUtils.getFileStatus(file.getPath(), stat)) {
mTimestamp = stat.mtime;
}
mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
}
public boolean hasFileChanged() {
FileStatus stat = new FileStatus();
if (!FileUtils.getFileStatus(mFile.getPath(), stat)) {
return true;
}
synchronized (this) {
if (!FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
return true;
}
return mTimestamp != mFileStatus.mtime;
return mTimestamp != stat.mtime;
}
}
@ -2747,7 +2753,7 @@ class ContextImpl extends Context {
public Map<String, ?> getAll() {
synchronized(this) {
//noinspection unchecked
return new HashMap(mMap);
return new HashMap<String, Object>(mMap);
}
}
@ -2766,7 +2772,7 @@ class ContextImpl extends Context {
}
public long getLong(String key, long defValue) {
synchronized (this) {
Long v = (Long) mMap.get(key);
Long v = (Long)mMap.get(key);
return v != null ? v : defValue;
}
}
@ -2789,10 +2795,31 @@ class ContextImpl extends Context {
}
}
public Editor edit() {
return new EditorImpl();
}
// Return value from EditorImpl#commitToMemory()
private static class MemoryCommitResult {
public boolean changesMade; // any keys different?
public List<String> keysModified; // may be null
public Set<OnSharedPreferenceChangeListener> listeners; // may be null
public Map<?, ?> mapToWriteToDisk;
public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
public volatile boolean writeToDiskResult = false;
public void setDiskWriteResult(boolean result) {
writeToDiskResult = result;
writtenToDiskLatch.countDown();
}
}
public final class EditorImpl implements Editor {
private final Map<String, Object> mModified = Maps.newHashMap();
private boolean mClear = false;
private AtomicBoolean mCommitInFlight = new AtomicBoolean(false);
public Editor putString(String key, String value) {
synchronized (this) {
mModified.put(key, value);
@ -2839,30 +2866,67 @@ class ContextImpl extends Context {
}
public void startCommit() {
// TODO: implement
commit();
if (!mCommitInFlight.compareAndSet(false, true)) {
throw new IllegalStateException("can't call startCommit() twice");
}
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
QueuedWork.add(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
mCommitInFlight.set(false);
QueuedWork.remove(awaitCommit);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
// Okay to notify the listeners before it's hit disk
// because the listeners should always get the same
// SharedPreferences instance back, which has the
// changes reflected in memory.
notifyListeners(mcr);
}
public boolean commit() {
boolean returnValue;
boolean hasListeners;
boolean changesMade = false;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
// Returns true if any changes were made
private MemoryCommitResult commitToMemory() {
MemoryCommitResult mcr = new MemoryCommitResult();
synchronized (SharedPreferencesImpl.this) {
hasListeners = mListeners.size() > 0;
// We optimistically don't make a deep copy until
// a memory commit comes in when we're already
// writing to disk.
if (mDiskWritesInFlight > 0) {
// We can't modify our mMap as a currently
// in-flight write owns it. Clone it before
// modifying it.
// noinspection unchecked
mMap = new HashMap<String, Object>(mMap);
}
mcr.mapToWriteToDisk = mMap;
mDiskWritesInFlight++;
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
keysModified = new ArrayList<String>();
listeners =
new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
mcr.keysModified = new ArrayList<String>();
mcr.listeners =
new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (this) {
if (mClear) {
if (!mMap.isEmpty()) {
changesMade = true;
mcr.changesMade = true;
mMap.clear();
}
mClear = false;
@ -2872,53 +2936,122 @@ class ContextImpl extends Context {
String k = e.getKey();
Object v = e.getValue();
if (v == this) { // magic value for a removal mutation
if (mMap.containsKey(k)) {
mMap.remove(k);
changesMade = true;
if (!mMap.containsKey(k)) {
continue;
}
mMap.remove(k);
} else {
boolean isSame = false;
if (mMap.containsKey(k)) {
Object existingValue = mMap.get(k);
isSame = existingValue != null && existingValue.equals(v);
}
if (!isSame) {
mMap.put(k, v);
changesMade = true;
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mMap.put(k, v);
}
mcr.changesMade = true;
if (hasListeners) {
keysModified.add(k);
mcr.keysModified.add(k);
}
}
mModified.clear();
}
returnValue = writeFileLocked(changesMade);
}
return mcr;
}
if (hasListeners) {
for (int i = keysModified.size() - 1; i >= 0; i--) {
final String key = keysModified.get(i);
for (OnSharedPreferenceChangeListener listener : listeners) {
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
private void notifyListeners(final MemoryCommitResult mcr) {
if (mcr.listeners == null || mcr.keysModified == null ||
mcr.keysModified.size() == 0) {
return;
}
if (Looper.myLooper() == Looper.getMainLooper()) {
for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
final String key = mcr.keysModified.get(i);
for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
}
}
}
} else {
// Run this function on the main thread.
ActivityThread.sMainThreadHandler.post(new Runnable() {
public void run() {
notifyListeners(mcr);
}
});
}
return returnValue;
}
}
public Editor edit() {
return new EditorImpl();
/**
* Enqueue an already-committed-to-memory result to be written
* to disk.
*
* They will be written to disk one-at-a-time in the order
* that they're enqueued.
*
* @param postWriteRunnable if non-null, we're being called
* from startCommit() and this is the runnable to run after
* the write proceeds. if null (from a regular commit()),
* then we're allowed to do this disk write on the main
* thread (which in addition to reducing allocations and
* creating a background thread, this has the advantage that
* we catch them in userdebug StrictMode reports to convert
* them where possible to startCommit...)
*/
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr);
}
synchronized (SharedPreferencesImpl.this) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
final boolean isFromSyncCommit = (postWriteRunnable == null);
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (SharedPreferencesImpl.this) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}
private FileOutputStream createFileOutputStream(File file) {
private static FileOutputStream createFileOutputStream(File file) {
FileOutputStream str = null;
try {
str = new FileOutputStream(file);
@ -2941,21 +3074,24 @@ class ContextImpl extends Context {
return str;
}
private boolean writeFileLocked(boolean changesMade) {
// Note: must hold mWritingToDiskLock
private void writeToFile(MemoryCommitResult mcr) {
// Rename the current file so it may be used as a backup during the next read
if (mFile.exists()) {
if (!changesMade) {
if (!mcr.changesMade) {
// If the file already exists, but no changes were
// made to the underlying map, it's wasteful to
// re-write the file. Return as if we wrote it
// out.
return true;
mcr.setDiskWriteResult(true);
return;
}
if (!mBackupFile.exists()) {
if (!mFile.renameTo(mBackupFile)) {
Log.e(TAG, "Couldn't rename file " + mFile
+ " to backup file " + mBackupFile);
return false;
mcr.setDiskWriteResult(false);
return;
}
} else {
mFile.delete();
@ -2968,22 +3104,26 @@ class ContextImpl extends Context {
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
return false;
mcr.setDiskWriteResult(false);
return;
}
XmlUtils.writeMapXml(mMap, str);
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
str.close();
setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
if (FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
mTimestamp = mFileStatus.mtime;
FileStatus stat = new FileStatus();
if (FileUtils.getFileStatus(mFile.getPath(), stat)) {
synchronized (this) {
mTimestamp = stat.mtime;
}
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
return true;
mcr.setDiskWriteResult(true);
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeFileLocked: Got exception:", e);
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeFileLocked: Got exception:", e);
Log.w(TAG, "writeToFile: Got exception:", e);
}
// Clean up an unsuccessfully written file
if (mFile.exists()) {
@ -2991,7 +3131,7 @@ class ContextImpl extends Context {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
return false;
mcr.setDiskWriteResult(false);
}
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2010 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.app;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Internal utility class to keep track of process-global work that's
* outstanding and hasn't been finished yet.
*
* This was created for writing SharedPreference edits out
* asynchronously so we'd have a mechanism to wait for the writes in
* Activity.onPause and similar places, but we may use this mechanism
* for other things in the future.
*
* @hide
*/
public class QueuedWork {
// The set of Runnables that will finish or wait on any async
// activities started by the application.
private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers =
new ConcurrentLinkedQueue<Runnable>();
private static ExecutorService sSingleThreadExecutor = null; // lazy, guarded by class
/**
* Returns a single-thread Executor shared by the entire process,
* creating it if necessary.
*/
public static ExecutorService singleThreadExecutor() {
synchronized (QueuedWork.class) {
if (sSingleThreadExecutor == null) {
// TODO: can we give this single thread a thread name?
sSingleThreadExecutor = Executors.newSingleThreadExecutor();
}
return sSingleThreadExecutor;
}
}
/**
* Add a runnable to finish (or wait for) a deferred operation
* started in this context earlier. Typically finished by e.g.
* an Activity#onPause. Used by SharedPreferences$Editor#startCommit().
*
* Note that this doesn't actually start it running. This is just
* a scratch set for callers doing async work to keep updated with
* what's in-flight. In the common case, caller code
* (e.g. SharedPreferences) will pretty quickly call remove()
* after an add(). The only time these Runnables are run is from
* waitToFinish(), below.
*/
public static void add(Runnable finisher) {
sPendingWorkFinishers.add(finisher);
}
public static void remove(Runnable finisher) {
sPendingWorkFinishers.remove(finisher);
}
/**
* Finishes or waits for async operations to complete.
* (e.g. SharedPreferences$Editor#startCommit writes)
*
* Is called from the Activity base class's onPause(), after
* BroadcastReceiver's onReceive, after Service command handling,
* etc. (so async work is never lost)
*/
public static void waitToFinish() {
Runnable toFinish;
while ((toFinish = sPendingWorkFinishers.poll()) != null) {
toFinish.run();
}
}
}

View File

@ -40,7 +40,9 @@ public interface SharedPreferences {
/**
* Called when a shared preference is changed, added, or removed. This
* may be called even if a preference is set to its existing value.
*
*
* <p>This callback will be run on your main thread.
*
* @param sharedPreferences The {@link SharedPreferences} that received
* the change.
* @param key The key of the preference that was changed, added, or
@ -187,9 +189,6 @@ public interface SharedPreferences {
* <p>If you call this from an {@link android.app.Activity},
* the base class will wait for any async commits to finish in
* its {@link android.app.Activity#onPause}.</p>
*
* @return Returns true if the new values were successfully written
* to persistent storage.
*/
void startCommit();
}

View File

@ -563,13 +563,13 @@ public class Handler {
return mMessenger;
}
}
private final class MessengerImpl extends IMessenger.Stub {
public void send(Message msg) {
Handler.this.sendMessage(msg);
}
}
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;

View File

@ -1195,7 +1195,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
private void tryCommit(SharedPreferences.Editor editor) {
if (mPreferenceManager.shouldCommit()) {
editor.commit();
editor.startCommit();
}
}

View File

@ -1737,7 +1737,7 @@ public class BluetoothService extends IBluetooth.Stub {
mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
mContext.MODE_PRIVATE).edit();
editor.putBoolean(SHARED_PREFERENCE_DOCK_ADDRESS + mDockAddress, true);
editor.commit();
editor.startCommit();
}
}
}

View File

@ -165,7 +165,9 @@ public class BootReceiver extends BroadcastReceiver {
if (prefs != null) {
long lastTime = prefs.getLong(filename, 0);
if (lastTime == fileTime) return; // Already logged this particular file
prefs.edit().putLong(filename, fileTime).commit();
// TODO: move all these SharedPreferences Editor commits
// outside this function to the end of logBootEvents
prefs.edit().putLong(filename, fileTime).startCommit();
}
Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")");