summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
authorSvetoslav Ganov <svetoslavganov@google.com>2016-04-26 18:36:26 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2016-04-26 18:36:28 +0000
commitf71d7feef22db9e0cab2f32edc7440aedb86fdfe (patch)
tree518e0a0e00d16c48d386d89cff0c5588719b9aab /core/java
parent83ca62bdbe00359a1cf574efc5abfb19c5f57337 (diff)
parent53a441ca8eda5a3e6209a952b1bbd32a39e19a1c (diff)
Merge "Ensure local settings caches are not stale" into nyc-dev
Diffstat (limited to 'core/java')
-rwxr-xr-xcore/java/android/provider/Settings.java136
-rw-r--r--core/java/android/util/MemoryIntArray.aidl19
-rw-r--r--core/java/android/util/MemoryIntArray.java258
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];
+ }
+ };
+}