Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 505735b1 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick Committed by Android (Google) Code Review
Browse files

Merge "resolved conflicts for merge of 28130bae to master"

parents 1ec50566 7f7ce40f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1236,6 +1236,7 @@ public class Activity extends ContextThemeWrapper
     */
    protected void onPause() {
        mCalled = true;
        QueuedWork.waitToFinish();
    }

    /**
+15 −2
Original line number Diff line number Diff line
@@ -153,7 +153,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;
@@ -187,6 +187,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;
@@ -1148,7 +1150,7 @@ public final class ActivityThread {
    }

    public static final ActivityThread currentActivityThread() {
        return (ActivityThread)sThreadLocal.get();
        return sThreadLocal.get();
    }

    public static final String currentPackageName() {
@@ -1815,6 +1817,8 @@ public final class ActivityThread {
            }
        }

        QueuedWork.waitToFinish();

        try {
            if (data.sync) {
                if (DEBUG_BROADCAST) Slog.i(TAG,
@@ -2042,6 +2046,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);
@@ -2070,6 +2077,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);
@@ -3643,6 +3653,9 @@ public final class ActivityThread {
        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

        ActivityThread thread = new ActivityThread();
        thread.attach(false);
+206 −66
Original line number Diff line number Diff line
@@ -119,9 +119,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) {
@@ -171,8 +174,8 @@ class ContextImpl extends Context {
    private static WifiManager sWifiManager;
    private static LocationManager sLocationManager;
    private static CountryDetector sCountryDetector;
    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;
@@ -336,15 +339,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()) {
@@ -377,10 +379,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;
@@ -2723,10 +2725,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;

@@ -2735,19 +2739,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() {
            synchronized (this) {
                if (!FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
            FileStatus stat = new FileStatus();
            if (!FileUtils.getFileStatus(mFile.getPath(), stat)) {
                return true;
            }
                return mTimestamp != mFileStatus.mtime;
            synchronized (this) {
                return mTimestamp != stat.mtime;
            }
        }

@@ -2774,7 +2780,7 @@ class ContextImpl extends Context {
        public Map<String, ?> getAll() {
            synchronized(this) {
                //noinspection unchecked
                return new HashMap(mMap);
                return new HashMap<String, Object>(mMap);
            }
        }

@@ -2823,10 +2829,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);
@@ -2879,30 +2906,67 @@ class ContextImpl extends Context {
            }

            public void startCommit() {
                // TODO: implement
                commit();
                if (!mCommitInFlight.compareAndSet(false, true)) {
                    throw new IllegalStateException("can't call startCommit() twice");
                }

            public boolean commit() {
                boolean returnValue;
                final MemoryCommitResult mcr = commitToMemory();
                final Runnable awaitCommit = new Runnable() {
                        public void run() {
                            try {
                                mcr.writtenToDiskLatch.await();
                            } catch (InterruptedException ignored) {
                            }
                        }
                    };

                boolean hasListeners;
                boolean changesMade = false;
                List<String> keysModified = null;
                Set<OnSharedPreferenceChangeListener> listeners = null;
                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);
            }

            // 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 =
                        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;
@@ -2912,53 +2976,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 (existingValue != null && existingValue.equals(v)) {
                                        continue;
                                    }
                                if (!isSame) {
                                    mMap.put(k, v);
                                    changesMade = true;
                                }
                                mMap.put(k, v);
                            }

                            mcr.changesMade = true;
                            if (hasListeners) {
                                keysModified.add(k);
                                mcr.keysModified.add(k);
                            }
                        }

                        mModified.clear();
                    }
                }
                return mcr;
            }

                    returnValue = writeFileLocked(changesMade);
            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;
            }

                if (hasListeners) {
                    for (int i = keysModified.size() - 1; i >= 0; i--) {
                        final String key = keysModified.get(i);
                        for (OnSharedPreferenceChangeListener listener : listeners) {
            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);
                            }
                        });
                }
            }
        }

        /**
         * 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);

                return returnValue;
            // 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;
                }
            }

        public Editor edit() {
            return new EditorImpl();
            QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
        }

        private FileOutputStream createFileOutputStream(File file) {
        private static FileOutputStream createFileOutputStream(File file) {
            FileOutputStream str = null;
            try {
                str = new FileOutputStream(file);
@@ -2981,21 +3114,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();
@@ -3008,22 +3144,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()) {
@@ -3031,7 +3171,7 @@ class ContextImpl extends Context {
                    Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
                }
            }
            return false;
            mcr.setDiskWriteResult(false);
        }
    }
}
+91 −0
Original line number Diff line number Diff line
/*
 * 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();
        }
    }
}
+3 −4
Original line number Diff line number Diff line
@@ -42,6 +42,8 @@ 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
@@ -199,9 +201,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();
    }
Loading