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) {