Merge "Ensure local settings caches are not stale" into nyc-dev
diff --git a/Android.mk b/Android.mk
index 1cde699..7d7aa48 100644
--- a/Android.mk
+++ b/Android.mk
@@ -598,6 +598,7 @@
frameworks/base/core/java/android/net/Uri.aidl \
frameworks/base/core/java/android/net/NetworkRequest.aidl \
frameworks/base/core/java/android/net/LinkAddress.aidl \
+ frameworks/base/core/java/android/util/MemoryIntArray.aidl \
frameworks/base/core/java/android/view/Display.aidl \
frameworks/base/core/java/android/view/InputDevice.aidl \
frameworks/base/core/java/android/view/InputEvent.aidl \
diff --git a/api/current.txt b/api/current.txt
index 98fa992..c738a2b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -32491,7 +32491,6 @@
field public static final java.lang.String RADIO_WIFI = "wifi";
field public static final java.lang.String SHOW_PROCESSES = "show_processes";
field public static final java.lang.String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
- field public static final java.lang.String SYS_PROP_SETTING_VERSION = "sys.settings_global_version";
field public static final java.lang.String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
field public static final java.lang.String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
field public static final java.lang.String USE_GOOGLE_MAIL = "use_google_mail";
@@ -32571,7 +32570,6 @@
field public static final java.lang.String SELECTED_INPUT_METHOD_SUBTYPE = "selected_input_method_subtype";
field public static final java.lang.String SETTINGS_CLASSNAME = "settings_classname";
field public static final java.lang.String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
- field public static final java.lang.String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version";
field public static final java.lang.String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
field public static final deprecated java.lang.String TTS_DEFAULT_COUNTRY = "tts_default_country";
field public static final deprecated java.lang.String TTS_DEFAULT_LANG = "tts_default_lang";
@@ -32686,7 +32684,6 @@
field public static final deprecated java.lang.String SHOW_WEB_SUGGESTIONS = "show_web_suggestions";
field public static final java.lang.String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
field public static final deprecated java.lang.String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
- field public static final java.lang.String SYS_PROP_SETTING_VERSION = "sys.settings_system_version";
field public static final java.lang.String TEXT_AUTO_CAPS = "auto_caps";
field public static final java.lang.String TEXT_AUTO_PUNCTUATE = "auto_punctuate";
field public static final java.lang.String TEXT_AUTO_REPLACE = "auto_replace";
diff --git a/api/system-current.txt b/api/system-current.txt
index 6f67a97..1c7248c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -35212,7 +35212,6 @@
field public static final java.lang.String RADIO_WIFI = "wifi";
field public static final java.lang.String SHOW_PROCESSES = "show_processes";
field public static final java.lang.String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
- field public static final java.lang.String SYS_PROP_SETTING_VERSION = "sys.settings_global_version";
field public static final java.lang.String THEATER_MODE_ON = "theater_mode_on";
field public static final java.lang.String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
field public static final java.lang.String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
@@ -35294,7 +35293,6 @@
field public static final java.lang.String SELECTED_INPUT_METHOD_SUBTYPE = "selected_input_method_subtype";
field public static final java.lang.String SETTINGS_CLASSNAME = "settings_classname";
field public static final java.lang.String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
- field public static final java.lang.String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version";
field public static final java.lang.String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
field public static final deprecated java.lang.String TTS_DEFAULT_COUNTRY = "tts_default_country";
field public static final deprecated java.lang.String TTS_DEFAULT_LANG = "tts_default_lang";
@@ -35409,7 +35407,6 @@
field public static final deprecated java.lang.String SHOW_WEB_SUGGESTIONS = "show_web_suggestions";
field public static final java.lang.String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
field public static final deprecated java.lang.String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
- field public static final java.lang.String SYS_PROP_SETTING_VERSION = "sys.settings_system_version";
field public static final java.lang.String TEXT_AUTO_CAPS = "auto_caps";
field public static final java.lang.String TEXT_AUTO_PUNCTUATE = "auto_punctuate";
field public static final java.lang.String TEXT_AUTO_REPLACE = "auto_replace";
diff --git a/api/test-current.txt b/api/test-current.txt
index 5dc6d51..40505a1 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -32563,7 +32563,6 @@
field public static final java.lang.String RADIO_WIFI = "wifi";
field public static final java.lang.String SHOW_PROCESSES = "show_processes";
field public static final java.lang.String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
- field public static final java.lang.String SYS_PROP_SETTING_VERSION = "sys.settings_global_version";
field public static final java.lang.String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
field public static final java.lang.String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
field public static final java.lang.String USE_GOOGLE_MAIL = "use_google_mail";
@@ -32644,7 +32643,6 @@
field public static final java.lang.String SELECTED_INPUT_METHOD_SUBTYPE = "selected_input_method_subtype";
field public static final java.lang.String SETTINGS_CLASSNAME = "settings_classname";
field public static final java.lang.String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
- field public static final java.lang.String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version";
field public static final java.lang.String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
field public static final deprecated java.lang.String TTS_DEFAULT_COUNTRY = "tts_default_country";
field public static final deprecated java.lang.String TTS_DEFAULT_LANG = "tts_default_lang";
@@ -32760,7 +32758,6 @@
field public static final deprecated java.lang.String SHOW_WEB_SUGGESTIONS = "show_web_suggestions";
field public static final java.lang.String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
field public static final deprecated java.lang.String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
- field public static final java.lang.String SYS_PROP_SETTING_VERSION = "sys.settings_system_version";
field public static final java.lang.String TEXT_AUTO_CAPS = "auto_caps";
field public static final java.lang.String TEXT_AUTO_PUNCTUATE = "auto_punctuate";
field public static final java.lang.String TEXT_AUTO_REPLACE = "auto_replace";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 12307c5..4ca1b97 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.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.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 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 @@
}
}
+ 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 @@
// 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 @@
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 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 @@
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 @@
* 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 @@
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 @@
* 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 @@
// 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 @@
* 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 @@
// 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 0000000..3b15535
--- /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 0000000..c3bd963
--- /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];
+ }
+ };
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 8a512a6..06d9b29 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -95,6 +95,7 @@
android_util_AssetManager.cpp \
android_util_Binder.cpp \
android_util_EventLog.cpp \
+ android_util_MemoryIntArray.cpp \
android_util_Log.cpp \
android_util_PathParser.cpp \
android_util_Process.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index d3dca5d9..a6a4f6b 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -113,6 +113,7 @@
extern int register_android_content_AssetManager(JNIEnv* env);
extern int register_android_util_EventLog(JNIEnv* env);
extern int register_android_util_Log(JNIEnv* env);
+extern int register_android_util_MemoryIntArray(JNIEnv* env);
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_content_StringBlock(JNIEnv* env);
extern int register_android_content_XmlBlock(JNIEnv* env);
@@ -1256,6 +1257,7 @@
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
REG_JNI(register_android_util_Log),
+ REG_JNI(register_android_util_MemoryIntArray),
REG_JNI(register_android_util_PathParser),
REG_JNI(register_android_app_admin_SecurityLog),
REG_JNI(register_android_content_AssetManager),
diff --git a/core/jni/android_util_MemoryIntArray.cpp b/core/jni/android_util_MemoryIntArray.cpp
new file mode 100644
index 0000000..dbe8ed3
--- /dev/null
+++ b/core/jni/android_util_MemoryIntArray.cpp
@@ -0,0 +1,219 @@
+/*
+ * 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.
+ */
+
+
+#include "core_jni_helpers.h"
+#include <cutils/ashmem.h>
+#include <sys/mman.h>
+
+namespace android {
+
+static jint android_util_MemoryIntArray_create(JNIEnv* env, jobject clazz, jstring name,
+ jint size)
+{
+ if (name == NULL) {
+ jniThrowException(env, "java/io/IOException", "bad name");
+ return -1;
+ }
+
+ if (size <= 0) {
+ jniThrowException(env, "java/io/IOException", "bad size");
+ return -1;
+ }
+
+ const char* nameStr = env->GetStringUTFChars(name, NULL);
+ const int ashmemSize = sizeof(std::atomic_int) * size;
+ int fd = ashmem_create_region(nameStr, ashmemSize);
+ env->ReleaseStringUTFChars(name, nameStr);
+
+ if (fd < 0) {
+ jniThrowException(env, "java/io/IOException", "ashmem creation failed");
+ return -1;
+ }
+
+ if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
+ jniThrowException(env, "java/io/IOException", "ashmem was purged");
+ return -1;
+ }
+
+ int setProtResult = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
+ if (setProtResult < 0) {
+ jniThrowException(env, "java/io/IOException", "cannot set ashmem prot mode");
+ return -1;
+ }
+
+ return fd;
+}
+
+static jlong android_util_MemoryIntArray_open(JNIEnv* env, jobject clazz, jint fd,
+ jboolean owner, jboolean writable)
+{
+ if (fd < 0) {
+ jniThrowException(env, "java/io/IOException", "bad file descriptor");
+ return -1;
+ }
+
+ int ashmemSize = ashmem_get_size_region(fd);
+ if (ashmemSize <= 0) {
+ jniThrowException(env, "java/io/IOException", "bad ashmem size");
+ return -1;
+ }
+
+ int protMode = (owner || writable) ? (PROT_READ | PROT_WRITE) : PROT_READ;
+ void* ashmemAddr = mmap(NULL, ashmemSize, protMode, MAP_SHARED, fd, 0);
+ if (ashmemAddr == MAP_FAILED) {
+ jniThrowException(env, "java/io/IOException", "cannot mmap ashmem");
+ return -1;
+ }
+
+ if (owner) {
+ int size = ashmemSize / sizeof(std::atomic_int);
+ new (ashmemAddr) std::atomic_int[size];
+ }
+
+ if (owner && !writable) {
+ int setProtResult = ashmem_set_prot_region(fd, PROT_READ);
+ if (setProtResult < 0) {
+ jniThrowException(env, "java/io/IOException", "cannot set ashmem prot mode");
+ return -1;
+ }
+ }
+
+ return reinterpret_cast<jlong>(ashmemAddr);
+}
+
+static void android_util_MemoryIntArray_close(JNIEnv* env, jobject clazz, jint fd,
+ jlong ashmemAddr, jboolean owner)
+{
+ if (fd < 0) {
+ jniThrowException(env, "java/io/IOException", "bad file descriptor");
+ return;
+ }
+
+ int ashmemSize = ashmem_get_size_region(fd);
+ if (ashmemSize <= 0) {
+ jniThrowException(env, "java/io/IOException", "bad ashmem size");
+ return;
+ }
+
+ int unmapResult = munmap(reinterpret_cast<void *>(ashmemAddr), ashmemSize);
+ if (unmapResult < 0) {
+ jniThrowException(env, "java/io/IOException", "munmap failed");
+ return;
+ }
+
+ // We don't deallocate the atomic ints we created with placement new in the ashmem
+ // region as the kernel we reclaim all pages when the ashmem region is destroyed.
+ if (owner && (ashmem_unpin_region(fd, 0, 0) != ASHMEM_IS_UNPINNED)) {
+ jniThrowException(env, "java/io/IOException", "ashmem unpinning failed");
+ return;
+ }
+
+ close(fd);
+}
+
+static jint android_util_MemoryIntArray_get(JNIEnv* env, jobject clazz,
+ jint fd, jlong address, jint index, jboolean owner)
+{
+ if (fd < 0) {
+ jniThrowException(env, "java/io/IOException", "bad file descriptor");
+ return -1;
+ }
+
+ bool unpin = false;
+
+ if (!owner) {
+ if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
+ jniThrowException(env, "java/io/IOException", "ashmem region was purged");
+ return -1;
+ }
+ unpin = true;
+ }
+
+ std::atomic_int* value = reinterpret_cast<std::atomic_int*>(address) + index;
+ const int result = value->load(std::memory_order_relaxed);
+
+ if (unpin) {
+ ashmem_unpin_region(fd, 0, 0);
+ }
+
+ return result;
+}
+
+static void android_util_MemoryIntArray_set(JNIEnv* env, jobject clazz,
+ jint fd, jlong address, jint index, jint newValue, jboolean owner)
+{
+ if (fd < 0) {
+ jniThrowException(env, "java/io/IOException", "bad file descriptor");
+ return;
+ }
+
+ bool unpin = false;
+
+ if (!owner) {
+ if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
+ jniThrowException(env, "java/io/IOException", "ashmem region was purged");
+ return;
+ }
+ unpin = true;
+ }
+
+ std::atomic_int* value = reinterpret_cast<std::atomic_int*>(address) + index;
+ value->store(newValue, std::memory_order_relaxed);
+
+ if (unpin) {
+ ashmem_unpin_region(fd, 0, 0);
+ }
+}
+
+static jint android_util_MemoryIntArray_size(JNIEnv* env, jobject clazz, jint fd) {
+ if (fd < 0) {
+ jniThrowException(env, "java/io/IOException", "bad file descriptor");
+ return -1;
+ }
+
+ // Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region.
+ // ASHMEM_GET_SIZE should succeed for all ashmem regions, and the kernel
+ // should return ENOTTY for all other valid file descriptors
+ int ashmemSize = ashmem_get_size_region(fd);
+ if (ashmemSize < 0) {
+ if (errno == ENOTTY) {
+ // ENOTTY means that the ioctl does not apply to this object,
+ // i.e., it is not an ashmem region.
+ return -1;
+ }
+ // Some other error, throw exception
+ jniThrowIOException(env, errno);
+ return -1;
+ }
+ return ashmemSize / sizeof(std::atomic_int);
+}
+
+static const JNINativeMethod methods[] = {
+ {"nativeCreate", "(Ljava/lang/String;I)I", (void*)android_util_MemoryIntArray_create},
+ {"nativeOpen", "(IZZ)J", (void*)android_util_MemoryIntArray_open},
+ {"nativeClose", "(IJZ)V", (void*)android_util_MemoryIntArray_close},
+ {"nativeGet", "(IJIZ)I", (void*)android_util_MemoryIntArray_get},
+ {"nativeSet", "(IJIIZ)V", (void*) android_util_MemoryIntArray_set},
+ {"nativeSize", "(I)I", (void*) android_util_MemoryIntArray_size},
+};
+
+int register_android_util_MemoryIntArray(JNIEnv* env)
+{
+ return RegisterMethodsOrDie(env, "android/util/MemoryIntArray", methods, NELEM(methods));
+}
+
+}
diff --git a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
new file mode 100644
index 0000000..6b31916
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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 junit.framework.TestCase;
+import libcore.io.IoUtils;
+
+public class MemoryIntArrayTest extends TestCase {
+
+ public void testSize() throws Exception {
+ MemoryIntArray array = null;
+ try {
+ array = new MemoryIntArray(3, false);
+ assertEquals("size must be three", 3, array.size());
+ } finally {
+ IoUtils.closeQuietly(array);
+ }
+ }
+
+ public void testGetSet() throws Exception {
+ MemoryIntArray array = null;
+ try {
+ array = new MemoryIntArray(3, false);
+
+ array.set(0, 1);
+ array.set(1, 2);
+ array.set(2, 3);
+
+ assertEquals("First element should be 1", 1, array.get(0));
+ assertEquals("First element should be 2", 2, array.get(1));
+ assertEquals("First element should be 3", 3, array.get(2));
+ } finally {
+ IoUtils.closeQuietly(array);
+ }
+ }
+
+ public void testWritable() throws Exception {
+ MemoryIntArray array = null;
+ try {
+ array = new MemoryIntArray(3, true);
+ assertTrue("Must be mutable", array.isWritable());
+ } finally {
+ IoUtils.closeQuietly(array);
+ }
+ }
+
+ public void testClose() throws Exception {
+ MemoryIntArray array = null;
+ try {
+ array = new MemoryIntArray(3, false);
+ array.close();
+ assertTrue("Must be closed", array.isClosed());
+ } finally {
+ if (array != null && !array.isClosed()) {
+ IoUtils.closeQuietly(array);
+ }
+ }
+ }
+
+ public void testMarshalledGetSet() throws Exception {
+ MemoryIntArray firstArray = null;
+ MemoryIntArray secondArray = null;
+ try {
+ firstArray = new MemoryIntArray(3, false);
+
+ firstArray.set(0, 1);
+ firstArray.set(1, 2);
+ firstArray.set(2, 3);
+
+ Parcel parcel = Parcel.obtain();
+ parcel.writeParcelable(firstArray, 0);
+ parcel.setDataPosition(0);
+ secondArray = parcel.readParcelable(null);
+ parcel.recycle();
+
+ assertNotNull("Should marshall file descriptor", secondArray);
+
+ assertEquals("First element should be 1", 1, secondArray.get(0));
+ assertEquals("First element should be 2", 2, secondArray.get(1));
+ assertEquals("First element should be 3", 3, secondArray.get(2));
+ } finally {
+ IoUtils.closeQuietly(firstArray);
+ IoUtils.closeQuietly(secondArray);
+ }
+ }
+
+ public void testInteractOnceClosed() throws Exception {
+ MemoryIntArray array = null;
+ try {
+ array = new MemoryIntArray(3, false);
+ array.close();
+
+ array.close();
+
+ try {
+ array.size();
+ fail("Cannot interact with a closed instance");
+ } catch (IllegalStateException e) {
+ /* expected */
+ }
+
+ try {
+ array.get(0);
+ fail("Cannot interact with a closed instance");
+ } catch (IllegalStateException e) {
+ /* expected */
+ }
+
+ try {
+ array.set(0, 1);
+ fail("Cannot interact with a closed instance");
+ } catch (IllegalStateException e) {
+ /* expected */
+ }
+
+ try {
+ array.isWritable();
+ fail("Cannot interact with a closed instance");
+ } catch (IllegalStateException e) {
+ /* expected */
+ }
+ } finally {
+ if (array != null && !array.isClosed()) {
+ IoUtils.closeQuietly(array);
+ }
+ }
+ }
+
+ public void testInteractPutOfBounds() throws Exception {
+ MemoryIntArray array = null;
+ try {
+ array = new MemoryIntArray(3, false);
+
+ try {
+ array.get(-1);
+ fail("Cannot interact out of array bounds");
+ } catch (IndexOutOfBoundsException e) {
+ /* expected */
+ }
+
+ try {
+ array.get(3);
+ fail("Cannot interact out of array bounds");
+ } catch (IndexOutOfBoundsException e) {
+ /* expected */
+ }
+
+ try {
+ array.set(-1, 0);
+ fail("Cannot interact out of array bounds");
+ } catch (IndexOutOfBoundsException e) {
+ /* expected */
+ }
+
+ try {
+ array.set(3, 0);
+ fail("Cannot interact out of array bounds");
+ } catch (IndexOutOfBoundsException e) {
+ /* expected */
+ }
+ } finally {
+ IoUtils.closeQuietly(array);
+ }
+ }
+
+ public void testOverMaxSize() throws Exception {
+ MemoryIntArray array = null;
+ try {
+ array = new MemoryIntArray(MemoryIntArray.getMaxSize() + 1, false);
+ fail("Cannot use over max size");
+ } catch (IllegalArgumentException e) {
+ /* expected */
+ } finally {
+ IoUtils.closeQuietly(array);
+ }
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
new file mode 100644
index 0000000..48533fa
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -0,0 +1,160 @@
+/*
+ * 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 com.android.providers.settings;
+
+import android.os.Bundle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.MemoryIntArray;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.IOException;
+
+/**
+ * This class tracks changes for global/secure/system tables on a
+ * per user basis and updates a shared memory region which client
+ * processes can read to determine if their local caches are stale,
+ */
+final class GenerationRegistry {
+ private static final String LOG_TAG = "GenerationTracker";
+
+ private static final boolean DEBUG = false;
+
+ private final Object mLock;
+
+ @GuardedBy("mLock")
+ private final SparseIntArray mKeyToIndexMap = new SparseIntArray();
+
+ @GuardedBy("mLock")
+ private final MemoryIntArray mImpl;
+
+ public GenerationRegistry(Object lock) {
+ mLock = lock;
+ // One for the global table, two for system and secure tables for a
+ // managed profile (managed profile is not included in the max user
+ // count), ten for partially deleted users if users are quickly removed,
+ // and twice max user count for system and secure.
+ final int size = 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
+ MemoryIntArray impl = null;
+ try {
+ impl = new MemoryIntArray(size, false);
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Error creating generation tracker", e);
+ }
+ mImpl = impl;
+ }
+
+ public void incrementGeneration(int key) {
+ synchronized (mLock) {
+ if (mImpl != null) {
+ try {
+ final int index = getKeyIndexLocked(key);
+ if (index >= 0) {
+ final int generation = mImpl.get(index) + 1;
+ mImpl.set(index, generation);
+ }
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Error updating generation id", e);
+ }
+ }
+ }
+ }
+
+ public void addGenerationData(Bundle bundle, int key) {
+ synchronized (mLock) {
+ if (mImpl != null) {
+ final int index = getKeyIndexLocked(key);
+ if (index >= 0) {
+ bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY, mImpl);
+ bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
+ + SettingsProvider.keyToString(key));
+ }
+ }
+ }
+ }
+ }
+
+ private int getKeyIndexLocked(int key) {
+ int index = mKeyToIndexMap.get(key, -1);
+ if (index < 0) {
+ index = findNextEmptyIndex();
+ if (index >= 0) {
+ try {
+ mImpl.set(index, 1);
+ mKeyToIndexMap.append(key, index);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
+ + SettingsProvider.keyToString(key));
+ }
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Cannot write to generation memory array", e);
+ }
+ } else {
+ Slog.e(LOG_TAG, "Could not allocate generation index");
+ }
+ }
+ return index;
+ }
+
+ public void onUserRemoved(int userId) {
+ synchronized (mLock) {
+ if (mImpl != null && mKeyToIndexMap.size() > 0) {
+ final int secureKey = SettingsProvider.makeKey(
+ SettingsProvider.SETTINGS_TYPE_SECURE, userId);
+ resetSlotForKeyLocked(secureKey);
+
+ final int systemKey = SettingsProvider.makeKey(
+ SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
+ resetSlotForKeyLocked(systemKey);
+ }
+ }
+ }
+
+ private void resetSlotForKeyLocked(int key) {
+ final int index = mKeyToIndexMap.get(key, -1);
+ if (index >= 0) {
+ mKeyToIndexMap.delete(key);
+ try {
+ mImpl.set(index, 0);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Freed index:" + index + " for key:"
+ + SettingsProvider.keyToString(key));
+ }
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Cannot write to generation memory array", e);
+ }
+ }
+ }
+
+ private int findNextEmptyIndex() {
+ try {
+ final int size = mImpl.size();
+ for (int i = 0; i < size; i++) {
+ if (mImpl.get(i) == 0) {
+ return i;
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Error reading generation memory array", e);
+ }
+ return -1;
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 8dc247a..75fb413 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -42,6 +42,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Debug;
import android.os.DropBoxManager;
import android.os.Environment;
import android.os.Handler;
@@ -51,9 +52,9 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SELinux;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -64,6 +65,7 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.providers.settings.SettingsState.Setting;
+import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import java.io.File;
@@ -146,8 +148,15 @@
Settings.NameValueTable.VALUE
};
- private static final Bundle NULL_SETTING = Bundle.forPair(Settings.NameValueTable.VALUE, null);
+ public static final int SETTINGS_TYPE_GLOBAL = 0;
+ public static final int SETTINGS_TYPE_SYSTEM = 1;
+ public static final int SETTINGS_TYPE_SECURE = 2;
+ public static final int SETTINGS_TYPE_MASK = 0xF0000000;
+ public static final int SETTINGS_TYPE_SHIFT = 28;
+
+ private static final Bundle NULL_SETTING_BUNDLE = Bundle.forPair(
+ Settings.NameValueTable.VALUE, null);
// Per user secure settings that moved to the for all users global settings.
static final Set<String> sSecureMovedToGlobalSettings = new ArraySet<>();
@@ -196,6 +205,40 @@
// We have to call in the package manager with no lock held,
private volatile IPackageManager mPackageManager;
+ public static int makeKey(int type, int userId) {
+ return (type << SETTINGS_TYPE_SHIFT) | userId;
+ }
+
+ public static int getTypeFromKey(int key) {
+ return key >>> SETTINGS_TYPE_SHIFT;
+ }
+
+ public static int getUserIdFromKey(int key) {
+ return key & ~SETTINGS_TYPE_MASK;
+ }
+
+ public static String settingTypeToString(int type) {
+ switch (type) {
+ case SETTINGS_TYPE_GLOBAL: {
+ return "SETTINGS_GLOBAL";
+ }
+ case SETTINGS_TYPE_SECURE: {
+ return "SETTINGS_SECURE";
+ }
+ case SETTINGS_TYPE_SYSTEM: {
+ return "SETTINGS_SYSTEM";
+ }
+ default: {
+ return "UNKNOWN";
+ }
+ }
+ }
+
+ public static String keyToString(int key) {
+ return "Key[user=" + getUserIdFromKey(key) + ";type="
+ + settingTypeToString(getTypeFromKey(key)) + "]";
+ }
+
@Override
public boolean onCreate() {
synchronized (mLock) {
@@ -204,6 +247,7 @@
mSettingsRegistry = new SettingsRegistry();
}
registerBroadcastReceivers();
+ startWatchingUserRestrictionChanges();
return true;
}
@@ -213,28 +257,28 @@
switch (method) {
case Settings.CALL_METHOD_GET_GLOBAL: {
Setting setting = getGlobalSetting(name);
- return packageValueForCallResult(setting);
+ return packageValueForCallResult(setting, isTrackingGeneration(args));
}
case Settings.CALL_METHOD_GET_SECURE: {
Setting setting = getSecureSetting(name, requestingUserId);
- return packageValueForCallResult(setting);
+ return packageValueForCallResult(setting, isTrackingGeneration(args));
}
case Settings.CALL_METHOD_GET_SYSTEM: {
Setting setting = getSystemSetting(name, requestingUserId);
- return packageValueForCallResult(setting);
+ return packageValueForCallResult(setting, isTrackingGeneration(args));
}
case Settings.CALL_METHOD_PUT_GLOBAL: {
String value = getSettingValue(args);
- insertGlobalSetting(name, value, requestingUserId);
+ insertGlobalSetting(name, value, requestingUserId, false);
break;
}
case Settings.CALL_METHOD_PUT_SECURE: {
String value = getSettingValue(args);
- insertSecureSetting(name, value, requestingUserId);
+ insertSecureSetting(name, value, requestingUserId, false);
break;
}
@@ -335,13 +379,13 @@
switch (table) {
case TABLE_GLOBAL: {
- if (insertGlobalSetting(name, value, UserHandle.getCallingUserId())) {
+ if (insertGlobalSetting(name, value, UserHandle.getCallingUserId(), false)) {
return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name);
}
} break;
case TABLE_SECURE: {
- if (insertSecureSetting(name, value, UserHandle.getCallingUserId())) {
+ if (insertSecureSetting(name, value, UserHandle.getCallingUserId(), false)) {
return Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
}
} break;
@@ -398,12 +442,12 @@
switch (args.table) {
case TABLE_GLOBAL: {
final int userId = UserHandle.getCallingUserId();
- return deleteGlobalSetting(args.name, userId) ? 1 : 0;
+ return deleteGlobalSetting(args.name, userId, false) ? 1 : 0;
}
case TABLE_SECURE: {
final int userId = UserHandle.getCallingUserId();
- return deleteSecureSetting(args.name, userId) ? 1 : 0;
+ return deleteSecureSetting(args.name, userId, false) ? 1 : 0;
}
case TABLE_SYSTEM: {
@@ -439,12 +483,12 @@
switch (args.table) {
case TABLE_GLOBAL: {
final int userId = UserHandle.getCallingUserId();
- return updateGlobalSetting(args.name, value, userId) ? 1 : 0;
+ return updateGlobalSetting(args.name, value, userId, false) ? 1 : 0;
}
case TABLE_SECURE: {
final int userId = UserHandle.getCallingUserId();
- return updateSecureSetting(args.name, value, userId) ? 1 : 0;
+ return updateSecureSetting(args.name, value, userId, false) ? 1 : 0;
}
case TABLE_SYSTEM: {
@@ -557,11 +601,15 @@
switch (intent.getAction()) {
case Intent.ACTION_USER_REMOVED: {
- mSettingsRegistry.removeUserStateLocked(userId, true);
+ synchronized (mLock) {
+ mSettingsRegistry.removeUserStateLocked(userId, true);
+ }
} break;
case Intent.ACTION_USER_STOPPED: {
- mSettingsRegistry.removeUserStateLocked(userId, false);
+ synchronized (mLock) {
+ mSettingsRegistry.removeUserStateLocked(userId, false);
+ }
} break;
}
}
@@ -582,6 +630,92 @@
UserHandle.ALL, true);
}
+ private void startWatchingUserRestrictionChanges() {
+ // TODO: The current design of settings looking different based on user restrictions
+ // should be reworked to keep them separate and system code should check the setting
+ // first followed by checking the user restriction before performing an operation.
+ UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
+ userManager.addUserRestrictionsListener((int userId, Bundle newRestrictions,
+ Bundle prevRestrictions) -> {
+ // We are changing the settings affected by restrictions to their current
+ // value with a forced update to ensure that all cross profile dependencies
+ // are taken into account. Also make sure the settings update to.. the same
+ // value passes the security checks, so clear binder calling id.
+ if (newRestrictions.containsKey(UserManager.DISALLOW_SHARE_LOCATION)
+ != prevRestrictions.containsKey(UserManager.DISALLOW_SHARE_LOCATION)) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Setting setting = getSecureSetting(
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED, userId);
+ updateSecureSetting(Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ setting != null ? setting.getValue() : null, userId, true);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ if (newRestrictions.containsKey(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)
+ != prevRestrictions.containsKey(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Setting setting = getGlobalSetting(Settings.Global.INSTALL_NON_MARKET_APPS);
+ updateGlobalSetting(Settings.Global.INSTALL_NON_MARKET_APPS,
+ setting != null ? setting.getValue() : null, userId, true);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ if (newRestrictions.containsKey(UserManager.DISALLOW_DEBUGGING_FEATURES)
+ != prevRestrictions.containsKey(UserManager.DISALLOW_DEBUGGING_FEATURES)) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Setting setting = getGlobalSetting(Settings.Global.ADB_ENABLED);
+ updateGlobalSetting(Settings.Global.ADB_ENABLED,
+ setting != null ? setting.getValue() : null, userId, true);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ if (newRestrictions.containsKey(UserManager.ENSURE_VERIFY_APPS)
+ != prevRestrictions.containsKey(UserManager.ENSURE_VERIFY_APPS)) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Setting enable = getGlobalSetting(
+ Settings.Global.PACKAGE_VERIFIER_ENABLE);
+ updateGlobalSetting(Settings.Global.PACKAGE_VERIFIER_ENABLE,
+ enable != null ? enable.getValue() : null, userId, true);
+ Setting include = getGlobalSetting(
+ Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB);
+ updateGlobalSetting(Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB,
+ include != null ? include.getValue() : null, userId, true);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ if (newRestrictions.containsKey(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)
+ != prevRestrictions.containsKey(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Setting setting = getGlobalSetting(
+ Settings.Global.PREFERRED_NETWORK_MODE);
+ updateGlobalSetting(Settings.Global.PREFERRED_NETWORK_MODE,
+ setting != null ? setting.getValue() : null, userId, true);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ });
+ }
+
private Cursor getAllGlobalSettings(String[] projection) {
if (DEBUG) {
Slog.v(LOG_TAG, "getAllGlobalSettings()");
@@ -590,7 +724,7 @@
synchronized (mLock) {
// Get the settings.
SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
- SettingsRegistry.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
+ SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
List<String> names = settingsState.getSettingNamesLocked();
@@ -617,34 +751,39 @@
// Get the value.
synchronized (mLock) {
- return mSettingsRegistry.getSettingLocked(SettingsRegistry.SETTINGS_TYPE_GLOBAL,
+ return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_GLOBAL,
UserHandle.USER_SYSTEM, name);
}
}
- private boolean updateGlobalSetting(String name, String value, int requestingUserId) {
+ private boolean updateGlobalSetting(String name, String value, int requestingUserId,
+ boolean forceNotify) {
if (DEBUG) {
Slog.v(LOG_TAG, "updateGlobalSetting(" + name + ", " + value + ")");
}
- return mutateGlobalSetting(name, value, requestingUserId, MUTATION_OPERATION_UPDATE);
+ return mutateGlobalSetting(name, value, requestingUserId, MUTATION_OPERATION_UPDATE,
+ forceNotify);
}
- private boolean insertGlobalSetting(String name, String value, int requestingUserId) {
+ private boolean insertGlobalSetting(String name, String value, int requestingUserId,
+ boolean forceNotify) {
if (DEBUG) {
Slog.v(LOG_TAG, "insertGlobalSetting(" + name + ", " + value + ")");
}
- return mutateGlobalSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT);
+ return mutateGlobalSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT,
+ forceNotify);
}
- private boolean deleteGlobalSetting(String name, int requestingUserId) {
+ private boolean deleteGlobalSetting(String name, int requestingUserId, boolean forceNotify) {
if (DEBUG) {
Slog.v(LOG_TAG, "deleteGlobalSettingLocked(" + name + ")");
}
- return mutateGlobalSetting(name, null, requestingUserId, MUTATION_OPERATION_DELETE);
+ return mutateGlobalSetting(name, null, requestingUserId, MUTATION_OPERATION_DELETE,
+ forceNotify);
}
private boolean mutateGlobalSetting(String name, String value, int requestingUserId,
- int operation) {
+ int operation, boolean forceNotify) {
// Make sure the caller can change the settings - treated as secure.
enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
@@ -662,20 +801,19 @@
switch (operation) {
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry
- .insertSettingLocked(SettingsRegistry.SETTINGS_TYPE_GLOBAL,
- UserHandle.USER_SYSTEM, name, value, getCallingPackage());
+ .insertSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM,
+ name, value, getCallingPackage(), forceNotify);
}
case MUTATION_OPERATION_DELETE: {
- return mSettingsRegistry.deleteSettingLocked(
- SettingsRegistry.SETTINGS_TYPE_GLOBAL,
- UserHandle.USER_SYSTEM, name);
+ return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_GLOBAL,
+ UserHandle.USER_SYSTEM, name, forceNotify);
}
case MUTATION_OPERATION_UPDATE: {
return mSettingsRegistry
- .updateSettingLocked(SettingsRegistry.SETTINGS_TYPE_GLOBAL,
- UserHandle.USER_SYSTEM, name, value, getCallingPackage());
+ .updateSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM,
+ name, value, getCallingPackage(), forceNotify);
}
}
}
@@ -693,7 +831,7 @@
synchronized (mLock) {
List<String> names = mSettingsRegistry.getSettingsNamesLocked(
- SettingsRegistry.SETTINGS_TYPE_SECURE, callingUserId);
+ SETTINGS_TYPE_SECURE, callingUserId);
final int nameCount = names.size();
@@ -712,7 +850,7 @@
}
Setting setting = mSettingsRegistry.getSettingLocked(
- SettingsRegistry.SETTINGS_TYPE_SECURE, owningUserId, name);
+ SETTINGS_TYPE_SECURE, owningUserId, name);
appendSettingToCursor(result, setting);
}
@@ -738,39 +876,44 @@
// Get the value.
synchronized (mLock) {
- return mSettingsRegistry.getSettingLocked(SettingsRegistry.SETTINGS_TYPE_SECURE,
+ return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SECURE,
owningUserId, name);
}
}
- private boolean insertSecureSetting(String name, String value, int requestingUserId) {
+ private boolean insertSecureSetting(String name, String value, int requestingUserId,
+ boolean forceNotify) {
if (DEBUG) {
Slog.v(LOG_TAG, "insertSecureSetting(" + name + ", " + value + ", "
+ requestingUserId + ")");
}
- return mutateSecureSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT);
+ return mutateSecureSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT,
+ forceNotify);
}
- private boolean deleteSecureSetting(String name, int requestingUserId) {
+ private boolean deleteSecureSetting(String name, int requestingUserId, boolean forceNotify) {
if (DEBUG) {
Slog.v(LOG_TAG, "deleteSecureSetting(" + name + ", " + requestingUserId + ")");
}
- return mutateSecureSetting(name, null, requestingUserId, MUTATION_OPERATION_DELETE);
+ return mutateSecureSetting(name, null, requestingUserId, MUTATION_OPERATION_DELETE,
+ forceNotify);
}
- private boolean updateSecureSetting(String name, String value, int requestingUserId) {
+ private boolean updateSecureSetting(String name, String value, int requestingUserId,
+ boolean forceNotify) {
if (DEBUG) {
Slog.v(LOG_TAG, "updateSecureSetting(" + name + ", " + value + ", "
+ requestingUserId + ")");
}
- return mutateSecureSetting(name, value, requestingUserId, MUTATION_OPERATION_UPDATE);
+ return mutateSecureSetting(name, value, requestingUserId, MUTATION_OPERATION_UPDATE,
+ forceNotify);
}
private boolean mutateSecureSetting(String name, String value, int requestingUserId,
- int operation) {
+ int operation, boolean forceNotify) {
// Make sure the caller can change the settings.
enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
@@ -793,28 +936,25 @@
// Special cases for location providers (sigh).
if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
- return updateLocationProvidersAllowedLocked(value, owningUserId);
+ return updateLocationProvidersAllowedLocked(value, owningUserId, forceNotify);
}
// Mutate the value.
synchronized (mLock) {
switch (operation) {
case MUTATION_OPERATION_INSERT: {
- return mSettingsRegistry
- .insertSettingLocked(SettingsRegistry.SETTINGS_TYPE_SECURE,
- owningUserId, name, value, getCallingPackage());
+ return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
+ owningUserId, name, value, getCallingPackage(), forceNotify);
}
case MUTATION_OPERATION_DELETE: {
- return mSettingsRegistry.deleteSettingLocked(
- SettingsRegistry.SETTINGS_TYPE_SECURE,
- owningUserId, name);
+ return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SECURE,
+ owningUserId, name, forceNotify);
}
case MUTATION_OPERATION_UPDATE: {
- return mSettingsRegistry
- .updateSettingLocked(SettingsRegistry.SETTINGS_TYPE_SECURE,
- owningUserId, name, value, getCallingPackage());
+ return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SECURE,
+ owningUserId, name, value, getCallingPackage(), forceNotify);
}
}
}
@@ -832,7 +972,7 @@
synchronized (mLock) {
List<String> names = mSettingsRegistry.getSettingsNamesLocked(
- SettingsRegistry.SETTINGS_TYPE_SYSTEM, callingUserId);
+ SETTINGS_TYPE_SYSTEM, callingUserId);
final int nameCount = names.size();
@@ -847,7 +987,7 @@
name);
Setting setting = mSettingsRegistry.getSettingLocked(
- SettingsRegistry.SETTINGS_TYPE_SYSTEM, owningUserId, name);
+ SETTINGS_TYPE_SYSTEM, owningUserId, name);
appendSettingToCursor(result, setting);
}
@@ -868,8 +1008,7 @@
// Get the value.
synchronized (mLock) {
- return mSettingsRegistry.getSettingLocked(SettingsRegistry.SETTINGS_TYPE_SYSTEM,
- owningUserId, name);
+ return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SYSTEM, owningUserId, name);
}
}
@@ -944,22 +1083,19 @@
switch (operation) {
case MUTATION_OPERATION_INSERT: {
validateSystemSettingValue(name, value);
- return mSettingsRegistry
- .insertSettingLocked(SettingsRegistry.SETTINGS_TYPE_SYSTEM,
- owningUserId, name, value, getCallingPackage());
+ return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM,
+ owningUserId, name, value, getCallingPackage(), false);
}
case MUTATION_OPERATION_DELETE: {
- return mSettingsRegistry.deleteSettingLocked(
- SettingsRegistry.SETTINGS_TYPE_SYSTEM,
- owningUserId, name);
+ return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SYSTEM,
+ owningUserId, name, false);
}
case MUTATION_OPERATION_UPDATE: {
validateSystemSettingValue(name, value);
- return mSettingsRegistry
- .updateSettingLocked(SettingsRegistry.SETTINGS_TYPE_SYSTEM,
- owningUserId, name, value, getCallingPackage());
+ return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SYSTEM,
+ owningUserId, name, value, getCallingPackage(), false);
}
}
@@ -1003,8 +1139,8 @@
* Checks whether changing a setting to a value is prohibited by the corresponding user
* restriction.
*
- * <p>See also {@link com.android.server.pm.UserRestrictionsUtils#applyUserRestrictionLR},
- * which should be in sync with this method.
+ * <p>See also {@link com.android.server.pm.UserRestrictionsUtils#applyUserRestriction(
+ * Context, int, String, boolean)}, which should be in sync with this method.
*
* @return true if the change is prohibited, false if the change is allowed.
*/
@@ -1177,13 +1313,19 @@
*
* @returns whether the enabled location providers changed.
*/
- private boolean updateLocationProvidersAllowedLocked(String value, int owningUserId) {
+ private boolean updateLocationProvidersAllowedLocked(String value, int owningUserId,
+ boolean forceNotify) {
if (TextUtils.isEmpty(value)) {
return false;
}
final char prefix = value.charAt(0);
if (prefix != '+' && prefix != '-') {
+ if (forceNotify) {
+ final int key = makeKey(SETTINGS_TYPE_SECURE, owningUserId);
+ mSettingsRegistry.notifyForSettingsChange(key,
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
+ }
return false;
}
@@ -1232,12 +1374,17 @@
}
} else {
// nothing changed, so no need to update the database
+ if (forceNotify) {
+ final int key = makeKey(SETTINGS_TYPE_SECURE, owningUserId);
+ mSettingsRegistry.notifyForSettingsChange(key,
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
+ }
return false;
}
- return mSettingsRegistry.insertSettingLocked(SettingsRegistry.SETTINGS_TYPE_SECURE,
+ return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
owningUserId, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, newProviders,
- getCallingPackage());
+ getCallingPackage(), forceNotify);
}
private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
@@ -1270,11 +1417,19 @@
"get/set setting for user", null);
}
- private static Bundle packageValueForCallResult(Setting setting) {
- if (setting == null) {
- return NULL_SETTING;
+ private Bundle packageValueForCallResult(Setting setting,
+ boolean trackingGeneration) {
+ if (!trackingGeneration) {
+ if (setting.isNull()) {
+ return NULL_SETTING_BUNDLE;
+ }
+ return Bundle.forPair(Settings.NameValueTable.VALUE, setting.getValue());
}
- return Bundle.forPair(Settings.NameValueTable.VALUE, setting.getValue());
+ Bundle result = new Bundle();
+ result.putString(Settings.NameValueTable.VALUE,
+ !setting.isNull() ? setting.getValue() : null);
+ mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getkey());
+ return result;
}
private static int getRequestingUserId(Bundle args) {
@@ -1283,6 +1438,10 @@
: callingUserId;
}
+ private boolean isTrackingGeneration(Bundle args) {
+ return args != null && args.containsKey(Settings.CALL_METHOD_TRACK_GENERATION_KEY);
+ }
+
private static String getSettingValue(Bundle args) {
return (args != null) ? args.getString(Settings.NameValueTable.VALUE) : null;
}
@@ -1448,26 +1607,22 @@
final class SettingsRegistry {
private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid";
- private static final int SETTINGS_TYPE_GLOBAL = 0;
- private static final int SETTINGS_TYPE_SYSTEM = 1;
- private static final int SETTINGS_TYPE_SECURE = 2;
-
- private static final int SETTINGS_TYPE_MASK = 0xF0000000;
- private static final int SETTINGS_TYPE_SHIFT = 28;
-
private static final String SETTINGS_FILE_GLOBAL = "settings_global.xml";
private static final String SETTINGS_FILE_SYSTEM = "settings_system.xml";
private static final String SETTINGS_FILE_SECURE = "settings_secure.xml";
private final SparseArray<SettingsState> mSettingsStates = new SparseArray<>();
- private final BackupManager mBackupManager;
+ private GenerationRegistry mGenerationRegistry;
private final Handler mHandler;
+ private final BackupManager mBackupManager;
+
public SettingsRegistry() {
- mBackupManager = new BackupManager(getContext());
mHandler = new MyHandler(getContext().getMainLooper());
+ mGenerationRegistry = new GenerationRegistry(mLock);
+ mBackupManager = new BackupManager(getContext());
migrateAllLegacySettingsIfNeeded();
}
@@ -1554,28 +1709,31 @@
});
}
}
+
+ // Nuke generation tracking data
+ mGenerationRegistry.onUserRemoved(userId);
}
public boolean insertSettingLocked(int type, int userId, String name, String value,
- String packageName) {
+ String packageName, boolean forceNotify) {
final int key = makeKey(type, userId);
SettingsState settingsState = peekSettingsStateLocked(key);
final boolean success = settingsState.insertSettingLocked(name, value, packageName);
- if (success) {
+ if (forceNotify || success) {
notifyForSettingsChange(key, name);
}
return success;
}
- public boolean deleteSettingLocked(int type, int userId, String name) {
+ public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify) {
final int key = makeKey(type, userId);
SettingsState settingsState = peekSettingsStateLocked(key);
final boolean success = settingsState.deleteSettingLocked(name);
- if (success) {
+ if (forceNotify || success) {
notifyForSettingsChange(key, name);
}
return success;
@@ -1589,13 +1747,13 @@
}
public boolean updateSettingLocked(int type, int userId, String name, String value,
- String packageName) {
+ String packageName, boolean forceNotify) {
final int key = makeKey(type, userId);
SettingsState settingsState = peekSettingsStateLocked(key);
final boolean success = settingsState.updateSettingLocked(name, value, packageName);
- if (success) {
+ if (forceNotify || success) {
notifyForSettingsChange(key, name);
}
@@ -1786,38 +1944,6 @@
}
private void notifyForSettingsChange(int key, String name) {
- // Update the system property *first*, so if someone is listening for
- // a notification and then using the contract class to get their data,
- // the system property will be updated and they'll get the new data.
-
- boolean backedUpDataChanged = false;
- String property = null;
- if (isGlobalSettingsKey(key)) {
- property = Settings.Global.SYS_PROP_SETTING_VERSION;
- backedUpDataChanged = true;
- } else if (isSecureSettingsKey(key)) {
- property = Settings.Secure.SYS_PROP_SETTING_VERSION;
- backedUpDataChanged = true;
- } else if (isSystemSettingsKey(key)) {
- property = Settings.System.SYS_PROP_SETTING_VERSION;
- backedUpDataChanged = true;
- }
-
- if (property != null) {
- final long version = SystemProperties.getLong(property, 0) + 1;
- SystemProperties.set(property, Long.toString(version));
- if (DEBUG) {
- Slog.v(LOG_TAG, "System property " + property + "=" + version);
- }
- }
-
- // Inform the backup manager about a data change
- if (backedUpDataChanged) {
- mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
- }
-
- // Now send the notification through the content framework.
-
final int userId = getUserIdFromKey(key);
Uri uri = getNotificationUriFor(key, name);
@@ -1825,13 +1951,19 @@
userId, 0, uri).sendToTarget();
if (isSecureSettingsKey(key)) {
- maybeNotifyProfiles(userId, uri, name, sSecureCloneToManagedSettings);
+ maybeNotifyProfiles(getTypeFromKey(key), userId, uri, name,
+ sSecureCloneToManagedSettings);
} else if (isSystemSettingsKey(key)) {
- maybeNotifyProfiles(userId, uri, name, sSystemCloneToManagedSettings);
+ maybeNotifyProfiles(getTypeFromKey(key), userId, uri, name,
+ sSystemCloneToManagedSettings);
}
+
+ mGenerationRegistry.incrementGeneration(key);
+
+ mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
}
- private void maybeNotifyProfiles(int userId, Uri uri, String name,
+ private void maybeNotifyProfiles(int type, int userId, Uri uri, String name,
Set<String> keysCloned) {
if (keysCloned.contains(name)) {
for (int profileId : mUserManager.getProfileIdsWithDisabled(userId)) {
@@ -1839,23 +1971,15 @@
if (profileId != userId) {
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
profileId, 0, uri).sendToTarget();
+ final int key = makeKey(type, profileId);
+ mGenerationRegistry.incrementGeneration(key);
+
+ mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
}
}
}
}
- private int makeKey(int type, int userId) {
- return (type << SETTINGS_TYPE_SHIFT) | userId;
- }
-
- private int getTypeFromKey(int key) {
- return key >> SETTINGS_TYPE_SHIFT;
- }
-
- private int getUserIdFromKey(int key) {
- return key & ~SETTINGS_TYPE_MASK;
- }
-
private boolean isGlobalSettingsKey(int key) {
return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
}
@@ -1953,7 +2077,7 @@
public void upgradeIfNeededLocked() {
// The version of all settings for a user is the same (all users have secure).
SettingsState secureSettings = getSettingsLocked(
- SettingsRegistry.SETTINGS_TYPE_SECURE, mUserId);
+ SETTINGS_TYPE_SECURE, mUserId);
// Try an update from the current state.
final int oldVersion = secureSettings.getVersionLocked();
@@ -1987,7 +2111,7 @@
// Set the global settings version if owner.
if (mUserId == UserHandle.USER_SYSTEM) {
SettingsState globalSettings = getSettingsLocked(
- SettingsRegistry.SETTINGS_TYPE_GLOBAL, mUserId);
+ SETTINGS_TYPE_GLOBAL, mUserId);
globalSettings.setVersionLocked(newVersion);
}
@@ -1996,7 +2120,7 @@
// Set the system settings version.
SettingsState systemSettings = getSettingsLocked(
- SettingsRegistry.SETTINGS_TYPE_SYSTEM, mUserId);
+ SETTINGS_TYPE_SYSTEM, mUserId);
systemSettings.setVersionLocked(newVersion);
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index dde9709..6dfd0e6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -16,6 +16,7 @@
package com.android.providers.settings;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
@@ -109,6 +110,14 @@
@GuardedBy("mLock")
private final File mStatePersistFile;
+ private final Setting mNullSetting = new Setting(null, null, null) {
+ @Override
+ public boolean isNull() {
+ return true;
+ }
+ };
+
+ @GuardedBy("mLock")
public final int mKey;
@GuardedBy("mLock")
@@ -198,9 +207,13 @@
// The settings provider must hold its lock when calling here.
public Setting getSettingLocked(String name) {
if (TextUtils.isEmpty(name)) {
- return null;
+ return mNullSetting;
}
- return mSettings.get(name);
+ Setting setting = mSettings.get(name);
+ if (setting != null) {
+ return new Setting(setting);
+ }
+ return mNullSetting;
}
// The settings provider must hold its lock when calling here.
@@ -549,12 +562,19 @@
}
}
- public final class Setting {
+ class Setting {
private String name;
private String value;
private String packageName;
private String id;
+ public Setting(Setting other) {
+ name = other.name;
+ value = other.value;
+ packageName = other.packageName;
+ id = other.id;
+ }
+
public Setting(String name, String value, String packageName) {
init(name, value, packageName, String.valueOf(mNextId++));
}
@@ -575,6 +595,10 @@
return name;
}
+ public int getkey() {
+ return mKey;
+ }
+
public String getValue() {
return value;
}
@@ -587,6 +611,10 @@
return id;
}
+ public boolean isNull() {
+ return false;
+ }
+
public boolean update(String value, String packageName) {
if (Objects.equal(value, this.value)) {
return false;
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 364e9fa6..38a3f42 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -370,19 +370,6 @@
android.provider.Settings.Secure.LOCATION_MODE_OFF,
userId);
}
- // Send out notifications as some clients may want to reread the
- // value which actually changed due to a restriction having been
- // applied.
- final String property =
- android.provider.Settings.Secure.SYS_PROP_SETTING_VERSION;
- long version = SystemProperties.getLong(property, 0) + 1;
- SystemProperties.set(property, Long.toString(version));
-
- final String name = android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
- final Uri url = Uri.withAppendedPath(
- android.provider.Settings.Secure.CONTENT_URI, name);
- context.getContentResolver().notifyChange(url, null, true, userId);
-
break;
case UserManager.DISALLOW_DEBUGGING_FEATURES:
if (newValue) {