diff options
| author | Svetoslav Ganov <svetoslavganov@google.com> | 2016-04-26 18:36:26 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2016-04-26 18:36:28 +0000 |
| commit | f71d7feef22db9e0cab2f32edc7440aedb86fdfe (patch) | |
| tree | 518e0a0e00d16c48d386d89cff0c5588719b9aab /core/java | |
| parent | 83ca62bdbe00359a1cf574efc5abfb19c5f57337 (diff) | |
| parent | 53a441ca8eda5a3e6209a952b1bbd32a39e19a1c (diff) | |
Merge "Ensure local settings caches are not stale" into nyc-dev
Diffstat (limited to 'core/java')
| -rwxr-xr-x | core/java/android/provider/Settings.java | 136 | ||||
| -rw-r--r-- | core/java/android/util/MemoryIntArray.aidl | 19 | ||||
| -rw-r--r-- | core/java/android/util/MemoryIntArray.java | 258 |
3 files changed, 383 insertions, 30 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 12307c54b7a8..4ca1b977d857 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -16,6 +16,7 @@ package android.provider; +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; @@ -50,7 +51,6 @@ import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.Build.VERSION_CODES; import android.speech.tts.TextToSpeech; @@ -61,9 +61,12 @@ import android.util.ArraySet; import android.util.LocaleList; import android.util.Log; +import android.util.MemoryIntArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.internal.widget.ILockSettings; +import java.io.IOException; import java.net.URISyntaxException; import java.text.SimpleDateFormat; import java.util.HashMap; @@ -1282,6 +1285,29 @@ public final class Settings { public static final String CALL_METHOD_GET_GLOBAL = "GET_global"; /** + * @hide - Specifies that the caller of the fast-path call()-based flow tracks + * the settings generation in order to cache values locally. If this key is + * mapped to a <code>null</code> string extra in the request bundle, the response + * bundle will contain the same key mapped to a parcelable extra which would be + * an {@link android.util.MemoryIntArray}. The response will also contain an + * integer mapped to the {@link #CALL_METHOD_GENERATION_INDEX_KEY} which is the + * index in the array clients should use to lookup the generation. For efficiency + * the caller should request the generation tracking memory array only if it + * doesn't already have it. + * + * @see #CALL_METHOD_GENERATION_INDEX_KEY + */ + public static final String CALL_METHOD_TRACK_GENERATION_KEY = "_track_generation"; + + /** + * @hide Key with the location in the {@link android.util.MemoryIntArray} where + * to look up the generation id of the backing table. + * + * @see #CALL_METHOD_TRACK_GENERATION_KEY + */ + public static final String CALL_METHOD_GENERATION_INDEX_KEY = "_generation_index"; + + /** * @hide - User handle argument extra to the fast-path call()-based requests */ public static final String CALL_METHOD_USER_KEY = "_user"; @@ -1424,9 +1450,42 @@ public final class Settings { } } + private static final class GenerationTracker { + private final MemoryIntArray mArray; + private final int mIndex; + private int mCurrentGeneration; + + public GenerationTracker(@NonNull MemoryIntArray array, int index) { + mArray = array; + mIndex = index; + mCurrentGeneration = readCurrentGeneration(); + } + + public boolean isGenerationChanged() { + final int currentGeneration = readCurrentGeneration(); + if (currentGeneration >= 0) { + if (currentGeneration == mCurrentGeneration) { + return false; + } + mCurrentGeneration = currentGeneration; + } + return true; + } + + private int readCurrentGeneration() { + try { + return mArray.get(mIndex); + } catch (IOException e) { + Log.e(TAG, "Error getting current generation", e); + } + return -1; + } + } + // Thread-safe. private static class NameValueCache { - private final String mVersionSystemProperty; + private static final boolean DEBUG = false; + private final Uri mUri; private static final String[] SELECT_VALUE = @@ -1435,7 +1494,6 @@ public final class Settings { // Must synchronize on 'this' to access mValues and mValuesVersion. private final HashMap<String, String> mValues = new HashMap<String, String>(); - private long mValuesVersion = 0; // Initially null; set lazily and held forever. Synchronized on 'this'. private IContentProvider mContentProvider = null; @@ -1445,9 +1503,10 @@ public final class Settings { private final String mCallGetCommand; private final String mCallSetCommand; - public NameValueCache(String versionSystemProperty, Uri uri, - String getCommand, String setCommand) { - mVersionSystemProperty = versionSystemProperty; + @GuardedBy("this") + private GenerationTracker mGenerationTracker; + + public NameValueCache(Uri uri, String getCommand, String setCommand) { mUri = uri; mCallGetCommand = getCommand; mCallSetCommand = setCommand; @@ -1482,22 +1541,18 @@ public final class Settings { public String getStringForUser(ContentResolver cr, String name, final int userHandle) { final boolean isSelf = (userHandle == UserHandle.myUserId()); if (isSelf) { - long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0); - - // Our own user's settings data uses a client-side cache synchronized (this) { - if (mValuesVersion != newValuesVersion) { - if (LOCAL_LOGV || false) { - Log.v(TAG, "invalidate [" + mUri.getLastPathSegment() + "]: current " - + newValuesVersion + " != cached " + mValuesVersion); + if (mGenerationTracker != null) { + if (mGenerationTracker.isGenerationChanged()) { + if (DEBUG) { + Log.i(TAG, "Generation changed for type:" + + mUri.getPath() + " in package:" + + cr.getPackageName() +" and user:" + userHandle); + } + mValues.clear(); + } else if (mValues.containsKey(name)) { + return mValues.get(name); } - - mValues.clear(); - mValuesVersion = newValuesVersion; - } - - if (mValues.containsKey(name)) { - return mValues.get(name); // Could be null, that's OK -- negative caching } } } else { @@ -1518,12 +1573,42 @@ public final class Settings { args = new Bundle(); args.putInt(CALL_METHOD_USER_KEY, userHandle); } + boolean needsGenerationTracker = false; + synchronized (this) { + if (isSelf && mGenerationTracker == null) { + needsGenerationTracker = true; + if (args == null) { + args = new Bundle(); + } + args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null); + if (DEBUG) { + Log.i(TAG, "Requested generation tracker for type: "+ mUri.getPath() + + " in package:" + cr.getPackageName() +" and user:" + + userHandle); + } + } + } Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args); if (b != null) { - String value = b.getPairValue(); + String value = b.getString(Settings.NameValueTable.VALUE); // Don't update our cache for reads of other users' data if (isSelf) { synchronized (this) { + if (needsGenerationTracker) { + MemoryIntArray array = b.getParcelable( + CALL_METHOD_TRACK_GENERATION_KEY); + final int index = b.getInt( + CALL_METHOD_GENERATION_INDEX_KEY, -1); + if (array != null && index >= 0) { + if (DEBUG) { + Log.i(TAG, "Received generation tracker for type:" + + mUri.getPath() + " in package:" + + cr.getPackageName() + " and user:" + + userHandle + " with index:" + index); + } + mGenerationTracker = new GenerationTracker(array, index); + } + } mValues.put(name, value); } } else { @@ -1592,8 +1677,6 @@ public final class Settings { * functions for accessing individual settings entries. */ public static final class System extends NameValueTable { - public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version"; - private static final float DEFAULT_FONT_SCALE = 1.0f; /** @hide */ @@ -1608,7 +1691,6 @@ public final class Settings { Uri.parse("content://" + AUTHORITY + "/system"); private static final NameValueCache sNameValueCache = new NameValueCache( - SYS_PROP_SETTING_VERSION, CONTENT_URI, CALL_METHOD_GET_SYSTEM, CALL_METHOD_PUT_SYSTEM); @@ -3913,8 +3995,6 @@ public final class Settings { * APIs for those values, not modified directly by applications. */ public static final class Secure extends NameValueTable { - public static final String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version"; - /** * The content:// style URL for this table */ @@ -3923,7 +4003,6 @@ public final class Settings { // Populated lazily, guarded by class object: private static final NameValueCache sNameValueCache = new NameValueCache( - SYS_PROP_SETTING_VERSION, CONTENT_URI, CALL_METHOD_GET_SECURE, CALL_METHOD_PUT_SECURE); @@ -6360,8 +6439,6 @@ public final class Settings { * explicitly modify through the system UI or specialized APIs for those values. */ public static final class Global extends NameValueTable { - public static final String SYS_PROP_SETTING_VERSION = "sys.settings_global_version"; - /** * The content:// style URL for global secure settings items. Not public. */ @@ -8412,7 +8489,6 @@ public final class Settings { // Populated lazily, guarded by class object: private static NameValueCache sNameValueCache = new NameValueCache( - SYS_PROP_SETTING_VERSION, CONTENT_URI, CALL_METHOD_GET_GLOBAL, CALL_METHOD_PUT_GLOBAL); diff --git a/core/java/android/util/MemoryIntArray.aidl b/core/java/android/util/MemoryIntArray.aidl new file mode 100644 index 000000000000..3b15535b28c2 --- /dev/null +++ b/core/java/android/util/MemoryIntArray.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2016, 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; + +parcelable MemoryIntArray; diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java new file mode 100644 index 000000000000..c3bd963708d7 --- /dev/null +++ b/core/java/android/util/MemoryIntArray.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2016 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.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; +import android.os.Process; + +import java.io.Closeable; +import java.io.IOException; +import java.util.UUID; + +/** + * This class is an array of integers that is backed by shared memory. + * It is useful for efficiently sharing state between processes. The + * write and read operations are guaranteed to not result in read/ + * write memory tear, i.e. they are atomic. However, multiple read/ + * write operations are <strong>not</strong> synchronized between + * each other. + * <p> + * The data structure is designed to have one owner process that can + * read/write. There may be multiple client processes that can only read or + * read/write depending how the data structure was configured when + * instantiated. The owner process is the process that created the array. + * The shared memory is pinned (not reclaimed by the system) until the + * owning process dies or the data structure is closed. This class + * is <strong>not</strong> thread safe. You should not interact with + * an instance of this class once it is closed. + * </p> + * + * @hide + */ +public final class MemoryIntArray implements Parcelable, Closeable { + private static final int MAX_SIZE = 1024; + + private final int mOwnerPid; + private final boolean mClientWritable; + private final long mMemoryAddr; + private ParcelFileDescriptor mFd; + + /** + * Creates a new instance. + * + * @param size The size of the array in terms of integer slots. Cannot be + * more than {@link #getMaxSize()}. + * @param clientWritable Whether other processes can write to the array. + * @throws IOException If an error occurs while accessing the shared memory. + */ + public MemoryIntArray(int size, boolean clientWritable) throws IOException { + if (size > MAX_SIZE) { + throw new IllegalArgumentException("Max size is " + MAX_SIZE); + } + mOwnerPid = Process.myPid(); + mClientWritable = clientWritable; + final String name = UUID.randomUUID().toString(); + mFd = ParcelFileDescriptor.fromFd(nativeCreate(name, size)); + mMemoryAddr = nativeOpen(mFd.getFd(), true, clientWritable); + } + + private MemoryIntArray(Parcel parcel) throws IOException { + mOwnerPid = parcel.readInt(); + mClientWritable = (parcel.readInt() == 1); + mFd = parcel.readParcelable(null); + if (mFd == null) { + throw new IOException("No backing file descriptor"); + } + final long memoryAddress = parcel.readLong(); + if (isOwner()) { + mMemoryAddr = memoryAddress; + } else { + mMemoryAddr = nativeOpen(mFd.getFd(), false, mClientWritable); + } + } + + /** + * @return Gets whether this array is mutable. + */ + public boolean isWritable() { + enforceNotClosed(); + return isOwner() || mClientWritable; + } + + /** + * Gets the value at a given index. + * + * @param index The index. + * @return The value at this index. + * @throws IOException If an error occurs while accessing the shared memory. + */ + public int get(int index) throws IOException { + enforceNotClosed(); + enforceValidIndex(index); + return nativeGet(mFd.getFd(), mMemoryAddr, index, isOwner()); + } + + /** + * Sets the value at a given index. This method can be called only if + * {@link #isWritable()} returns true which means your process is the + * owner. + * + * @param index The index. + * @param value The value to set. + * @throws IOException If an error occurs while accessing the shared memory. + */ + public void set(int index, int value) throws IOException { + enforceNotClosed(); + enforceWritable(); + enforceValidIndex(index); + nativeSet(mFd.getFd(), mMemoryAddr, index, value, isOwner()); + } + + /** + * Gets the array size. + * + * @throws IOException If an error occurs while accessing the shared memory. + */ + public int size() throws IOException { + enforceNotClosed(); + return nativeSize(mFd.getFd()); + } + + /** + * Closes the array releasing resources. + * + * @throws IOException If an error occurs while accessing the shared memory. + */ + @Override + public void close() throws IOException { + if (!isClosed()) { + nativeClose(mFd.getFd(), mMemoryAddr, isOwner()); + mFd = null; + } + } + + /** + * @return Whether this array is closed and shouldn't be used. + */ + public boolean isClosed() { + return mFd == null; + } + + @Override + protected void finalize() throws Throwable { + close(); + super.finalize(); + } + + @Override + public int describeContents() { + return CONTENTS_FILE_DESCRIPTOR; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mOwnerPid); + parcel.writeInt(mClientWritable ? 1 : 0); + parcel.writeParcelable(mFd, 0); + parcel.writeLong(mMemoryAddr); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (getClass() != obj.getClass()) { + return false; + } + MemoryIntArray other = (MemoryIntArray) obj; + if (mFd == null) { + if (other.mFd != null) { + return false; + } + } else if (mFd.getFd() != other.mFd.getFd()) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return mFd != null ? mFd.hashCode() : 1; + } + + private boolean isOwner() { + return mOwnerPid == Process.myPid(); + } + + private void enforceNotClosed() { + if (isClosed()) { + throw new IllegalStateException("cannot interact with a closed instance"); + } + } + + private void enforceValidIndex(int index) throws IOException { + final int size = size(); + if (index < 0 || index > size - 1) { + throw new IndexOutOfBoundsException( + index + " not between 0 and " + (size - 1)); + } + } + + private void enforceWritable() { + if (!isWritable()) { + throw new UnsupportedOperationException("array is not writable"); + } + } + + private native int nativeCreate(String name, int size); + private native long nativeOpen(int fd, boolean owner, boolean writable); + private native void nativeClose(int fd, long memoryAddr, boolean owner); + private native int nativeGet(int fd, long memoryAddr, int index, boolean owner); + private native void nativeSet(int fd, long memoryAddr, int index, int value, boolean owner); + private native int nativeSize(int fd); + private native static int nativeGetMemoryPageSize(); + + /** + * @return The max array size. + */ + public static int getMaxSize() { + return MAX_SIZE; + } + + public static final Parcelable.Creator<MemoryIntArray> CREATOR = + new Parcelable.Creator<MemoryIntArray>() { + @Override + public MemoryIntArray createFromParcel(Parcel parcel) { + try { + return new MemoryIntArray(parcel); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + @Override + public MemoryIntArray[] newArray(int size) { + return new MemoryIntArray[size]; + } + }; +} |
