Ensure local settings caches are not stale

We used the system proterties as a shared memory mechanism
to propagate information to local settings caches when the
content has changed and the cache should be cleared. The
system properties are unfortunately updated asynchronously
leading to cases where clients may read stale data.

This change adds a simple int array data structure backed
by shared memory which guarantees individual values are
atomically read and updated without memory tear. Multi-
index opearations are not synchronized between each other.

The settings provider is using the new data structure to
propagate the settings generation which drives when caches
are purged.

We have a single memory array keeping the generation for
different settings tables per user. Since memory array is
not a compact data structure and the user space exceeds
the memory array size we use an in-memory map from keys
to indices in the memory array where the generation id of
a key is stored. A key is derived by the setting type in
the 4 most significant bits and the user id in the 28 least
significant bits.

The mapping from a key to an index is cleared if the user is
removed and the corresponding index in the memory arry is
reset to make it available for other users. The size of the
memory array is derived from the max user count that can be
created at the same time.

bug:18826179

Change-Id: I64009cc5105309ef9aa83aba90b82afc8ad8c659
diff --git a/Android.mk b/Android.mk
index 1c5b08b..aa5d82c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -597,6 +597,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 b8b9079..85ec671 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -32486,7 +32486,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";
@@ -32567,7 +32566,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";
@@ -32682,7 +32680,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 ecc978d..dfcc64a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -35143,7 +35143,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";
@@ -35226,7 +35225,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";
@@ -35341,7 +35339,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 a1047cb..4fdb14c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -32558,7 +32558,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";
@@ -32639,7 +32638,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";
@@ -32755,7 +32753,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 d80d4be..6264918 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);
@@ -6357,8 +6436,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.
          */
@@ -8409,7 +8486,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) {