diff options
| author | 2016-04-26 18:36:26 +0000 | |
|---|---|---|
| committer | 2016-04-26 18:36:28 +0000 | |
| commit | f71d7feef22db9e0cab2f32edc7440aedb86fdfe (patch) | |
| tree | 518e0a0e00d16c48d386d89cff0c5588719b9aab | |
| parent | 83ca62bdbe00359a1cf574efc5abfb19c5f57337 (diff) | |
| parent | 53a441ca8eda5a3e6209a952b1bbd32a39e19a1c (diff) | |
Merge "Ensure local settings caches are not stale" into nyc-dev
| -rw-r--r-- | Android.mk | 1 | ||||
| -rw-r--r-- | api/current.txt | 3 | ||||
| -rw-r--r-- | api/system-current.txt | 3 | ||||
| -rw-r--r-- | api/test-current.txt | 3 | ||||
| -rwxr-xr-x | core/java/android/provider/Settings.java | 136 | ||||
| -rw-r--r-- | core/java/android/util/MemoryIntArray.aidl | 19 | ||||
| -rw-r--r-- | core/java/android/util/MemoryIntArray.java | 258 | ||||
| -rw-r--r-- | core/jni/Android.mk | 1 | ||||
| -rw-r--r-- | core/jni/AndroidRuntime.cpp | 2 | ||||
| -rw-r--r-- | core/jni/android_util_MemoryIntArray.cpp | 219 | ||||
| -rw-r--r-- | core/tests/utiltests/src/android/util/MemoryIntArrayTest.java | 192 | ||||
| -rw-r--r-- | packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java | 160 | ||||
| -rw-r--r-- | packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java | 400 | ||||
| -rw-r--r-- | packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java | 34 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/UserRestrictionsUtils.java | 13 |
15 files changed, 1251 insertions, 193 deletions
diff --git a/Android.mk b/Android.mk index 1cde699bcc06..7d7aa48067d1 100644 --- a/Android.mk +++ b/Android.mk @@ -598,6 +598,7 @@ aidl_files := \ 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 98fa99233f55..c738a2b3ed3e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -32491,7 +32491,6 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.provider { 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 6f67a978282a..1c7248c807d1 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -35212,7 +35212,6 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.provider { 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 5dc6d51213c0..40505a1ce6e7 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -32563,7 +32563,6 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.provider { 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 12307c54b7a8..4ca1b977d857 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -16,6 +16,7 @@ package android.provider; +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; @@ -50,7 +51,6 @@ import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.Build.VERSION_CODES; import android.speech.tts.TextToSpeech; @@ -61,9 +61,12 @@ import android.util.ArraySet; import android.util.LocaleList; import android.util.Log; +import android.util.MemoryIntArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.internal.widget.ILockSettings; +import java.io.IOException; import java.net.URISyntaxException; import java.text.SimpleDateFormat; import java.util.HashMap; @@ -1282,6 +1285,29 @@ public final class Settings { public static final String CALL_METHOD_GET_GLOBAL = "GET_global"; /** + * @hide - Specifies that the caller of the fast-path call()-based flow tracks + * the settings generation in order to cache values locally. If this key is + * mapped to a <code>null</code> string extra in the request bundle, the response + * bundle will contain the same key mapped to a parcelable extra which would be + * an {@link android.util.MemoryIntArray}. The response will also contain an + * integer mapped to the {@link #CALL_METHOD_GENERATION_INDEX_KEY} which is the + * index in the array clients should use to lookup the generation. For efficiency + * the caller should request the generation tracking memory array only if it + * doesn't already have it. + * + * @see #CALL_METHOD_GENERATION_INDEX_KEY + */ + public static final String CALL_METHOD_TRACK_GENERATION_KEY = "_track_generation"; + + /** + * @hide Key with the location in the {@link android.util.MemoryIntArray} where + * to look up the generation id of the backing table. + * + * @see #CALL_METHOD_TRACK_GENERATION_KEY + */ + public static final String CALL_METHOD_GENERATION_INDEX_KEY = "_generation_index"; + + /** * @hide - User handle argument extra to the fast-path call()-based requests */ public static final String CALL_METHOD_USER_KEY = "_user"; @@ -1424,9 +1450,42 @@ public final class Settings { } } + private static final class GenerationTracker { + private final MemoryIntArray mArray; + private final int mIndex; + private int mCurrentGeneration; + + public GenerationTracker(@NonNull MemoryIntArray array, int index) { + mArray = array; + mIndex = index; + mCurrentGeneration = readCurrentGeneration(); + } + + public boolean isGenerationChanged() { + final int currentGeneration = readCurrentGeneration(); + if (currentGeneration >= 0) { + if (currentGeneration == mCurrentGeneration) { + return false; + } + mCurrentGeneration = currentGeneration; + } + return true; + } + + private int readCurrentGeneration() { + try { + return mArray.get(mIndex); + } catch (IOException e) { + Log.e(TAG, "Error getting current generation", e); + } + return -1; + } + } + // Thread-safe. private static class NameValueCache { - private final String mVersionSystemProperty; + private static final boolean DEBUG = false; + private final Uri mUri; private static final String[] SELECT_VALUE = @@ -1435,7 +1494,6 @@ public final class Settings { // Must synchronize on 'this' to access mValues and mValuesVersion. private final HashMap<String, String> mValues = new HashMap<String, String>(); - private long mValuesVersion = 0; // Initially null; set lazily and held forever. Synchronized on 'this'. private IContentProvider mContentProvider = null; @@ -1445,9 +1503,10 @@ public final class Settings { private final String mCallGetCommand; private final String mCallSetCommand; - public NameValueCache(String versionSystemProperty, Uri uri, - String getCommand, String setCommand) { - mVersionSystemProperty = versionSystemProperty; + @GuardedBy("this") + private GenerationTracker mGenerationTracker; + + public NameValueCache(Uri uri, String getCommand, String setCommand) { mUri = uri; mCallGetCommand = getCommand; mCallSetCommand = setCommand; @@ -1482,22 +1541,18 @@ public final class Settings { public String getStringForUser(ContentResolver cr, String name, final int userHandle) { final boolean isSelf = (userHandle == UserHandle.myUserId()); if (isSelf) { - long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0); - - // Our own user's settings data uses a client-side cache synchronized (this) { - if (mValuesVersion != newValuesVersion) { - if (LOCAL_LOGV || false) { - Log.v(TAG, "invalidate [" + mUri.getLastPathSegment() + "]: current " - + newValuesVersion + " != cached " + mValuesVersion); + if (mGenerationTracker != null) { + if (mGenerationTracker.isGenerationChanged()) { + if (DEBUG) { + Log.i(TAG, "Generation changed for type:" + + mUri.getPath() + " in package:" + + cr.getPackageName() +" and user:" + userHandle); + } + mValues.clear(); + } else if (mValues.containsKey(name)) { + return mValues.get(name); } - - mValues.clear(); - mValuesVersion = newValuesVersion; - } - - if (mValues.containsKey(name)) { - return mValues.get(name); // Could be null, that's OK -- negative caching } } } else { @@ -1518,12 +1573,42 @@ public final class Settings { args = new Bundle(); args.putInt(CALL_METHOD_USER_KEY, userHandle); } + boolean needsGenerationTracker = false; + synchronized (this) { + if (isSelf && mGenerationTracker == null) { + needsGenerationTracker = true; + if (args == null) { + args = new Bundle(); + } + args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null); + if (DEBUG) { + Log.i(TAG, "Requested generation tracker for type: "+ mUri.getPath() + + " in package:" + cr.getPackageName() +" and user:" + + userHandle); + } + } + } Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args); if (b != null) { - String value = b.getPairValue(); + String value = b.getString(Settings.NameValueTable.VALUE); // Don't update our cache for reads of other users' data if (isSelf) { synchronized (this) { + if (needsGenerationTracker) { + MemoryIntArray array = b.getParcelable( + CALL_METHOD_TRACK_GENERATION_KEY); + final int index = b.getInt( + CALL_METHOD_GENERATION_INDEX_KEY, -1); + if (array != null && index >= 0) { + if (DEBUG) { + Log.i(TAG, "Received generation tracker for type:" + + mUri.getPath() + " in package:" + + cr.getPackageName() + " and user:" + + userHandle + " with index:" + index); + } + mGenerationTracker = new GenerationTracker(array, index); + } + } mValues.put(name, value); } } else { @@ -1592,8 +1677,6 @@ public final class Settings { * functions for accessing individual settings entries. */ public static final class System extends NameValueTable { - public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version"; - private static final float DEFAULT_FONT_SCALE = 1.0f; /** @hide */ @@ -1608,7 +1691,6 @@ public final class Settings { Uri.parse("content://" + AUTHORITY + "/system"); private static final NameValueCache sNameValueCache = new NameValueCache( - SYS_PROP_SETTING_VERSION, CONTENT_URI, CALL_METHOD_GET_SYSTEM, CALL_METHOD_PUT_SYSTEM); @@ -3913,8 +3995,6 @@ public final class Settings { * APIs for those values, not modified directly by applications. */ public static final class Secure extends NameValueTable { - public static final String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version"; - /** * The content:// style URL for this table */ @@ -3923,7 +4003,6 @@ public final class Settings { // Populated lazily, guarded by class object: private static final NameValueCache sNameValueCache = new NameValueCache( - SYS_PROP_SETTING_VERSION, CONTENT_URI, CALL_METHOD_GET_SECURE, CALL_METHOD_PUT_SECURE); @@ -6360,8 +6439,6 @@ public final class Settings { * explicitly modify through the system UI or specialized APIs for those values. */ public static final class Global extends NameValueTable { - public static final String SYS_PROP_SETTING_VERSION = "sys.settings_global_version"; - /** * The content:// style URL for global secure settings items. Not public. */ @@ -8412,7 +8489,6 @@ public final class Settings { // Populated lazily, guarded by class object: private static NameValueCache sNameValueCache = new NameValueCache( - SYS_PROP_SETTING_VERSION, CONTENT_URI, CALL_METHOD_GET_GLOBAL, CALL_METHOD_PUT_GLOBAL); diff --git a/core/java/android/util/MemoryIntArray.aidl b/core/java/android/util/MemoryIntArray.aidl new file mode 100644 index 000000000000..3b15535b28c2 --- /dev/null +++ b/core/java/android/util/MemoryIntArray.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +parcelable MemoryIntArray; diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java new file mode 100644 index 000000000000..c3bd963708d7 --- /dev/null +++ b/core/java/android/util/MemoryIntArray.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; +import android.os.Process; + +import java.io.Closeable; +import java.io.IOException; +import java.util.UUID; + +/** + * This class is an array of integers that is backed by shared memory. + * It is useful for efficiently sharing state between processes. The + * write and read operations are guaranteed to not result in read/ + * write memory tear, i.e. they are atomic. However, multiple read/ + * write operations are <strong>not</strong> synchronized between + * each other. + * <p> + * The data structure is designed to have one owner process that can + * read/write. There may be multiple client processes that can only read or + * read/write depending how the data structure was configured when + * instantiated. The owner process is the process that created the array. + * The shared memory is pinned (not reclaimed by the system) until the + * owning process dies or the data structure is closed. This class + * is <strong>not</strong> thread safe. You should not interact with + * an instance of this class once it is closed. + * </p> + * + * @hide + */ +public final class MemoryIntArray implements Parcelable, Closeable { + private static final int MAX_SIZE = 1024; + + private final int mOwnerPid; + private final boolean mClientWritable; + private final long mMemoryAddr; + private ParcelFileDescriptor mFd; + + /** + * Creates a new instance. + * + * @param size The size of the array in terms of integer slots. Cannot be + * more than {@link #getMaxSize()}. + * @param clientWritable Whether other processes can write to the array. + * @throws IOException If an error occurs while accessing the shared memory. + */ + public MemoryIntArray(int size, boolean clientWritable) throws IOException { + if (size > MAX_SIZE) { + throw new IllegalArgumentException("Max size is " + MAX_SIZE); + } + mOwnerPid = Process.myPid(); + mClientWritable = clientWritable; + final String name = UUID.randomUUID().toString(); + mFd = ParcelFileDescriptor.fromFd(nativeCreate(name, size)); + mMemoryAddr = nativeOpen(mFd.getFd(), true, clientWritable); + } + + private MemoryIntArray(Parcel parcel) throws IOException { + mOwnerPid = parcel.readInt(); + mClientWritable = (parcel.readInt() == 1); + mFd = parcel.readParcelable(null); + if (mFd == null) { + throw new IOException("No backing file descriptor"); + } + final long memoryAddress = parcel.readLong(); + if (isOwner()) { + mMemoryAddr = memoryAddress; + } else { + mMemoryAddr = nativeOpen(mFd.getFd(), false, mClientWritable); + } + } + + /** + * @return Gets whether this array is mutable. + */ + public boolean isWritable() { + enforceNotClosed(); + return isOwner() || mClientWritable; + } + + /** + * Gets the value at a given index. + * + * @param index The index. + * @return The value at this index. + * @throws IOException If an error occurs while accessing the shared memory. + */ + public int get(int index) throws IOException { + enforceNotClosed(); + enforceValidIndex(index); + return nativeGet(mFd.getFd(), mMemoryAddr, index, isOwner()); + } + + /** + * Sets the value at a given index. This method can be called only if + * {@link #isWritable()} returns true which means your process is the + * owner. + * + * @param index The index. + * @param value The value to set. + * @throws IOException If an error occurs while accessing the shared memory. + */ + public void set(int index, int value) throws IOException { + enforceNotClosed(); + enforceWritable(); + enforceValidIndex(index); + nativeSet(mFd.getFd(), mMemoryAddr, index, value, isOwner()); + } + + /** + * Gets the array size. + * + * @throws IOException If an error occurs while accessing the shared memory. + */ + public int size() throws IOException { + enforceNotClosed(); + return nativeSize(mFd.getFd()); + } + + /** + * Closes the array releasing resources. + * + * @throws IOException If an error occurs while accessing the shared memory. + */ + @Override + public void close() throws IOException { + if (!isClosed()) { + nativeClose(mFd.getFd(), mMemoryAddr, isOwner()); + mFd = null; + } + } + + /** + * @return Whether this array is closed and shouldn't be used. + */ + public boolean isClosed() { + return mFd == null; + } + + @Override + protected void finalize() throws Throwable { + close(); + super.finalize(); + } + + @Override + public int describeContents() { + return CONTENTS_FILE_DESCRIPTOR; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mOwnerPid); + parcel.writeInt(mClientWritable ? 1 : 0); + parcel.writeParcelable(mFd, 0); + parcel.writeLong(mMemoryAddr); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (getClass() != obj.getClass()) { + return false; + } + MemoryIntArray other = (MemoryIntArray) obj; + if (mFd == null) { + if (other.mFd != null) { + return false; + } + } else if (mFd.getFd() != other.mFd.getFd()) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return mFd != null ? mFd.hashCode() : 1; + } + + private boolean isOwner() { + return mOwnerPid == Process.myPid(); + } + + private void enforceNotClosed() { + if (isClosed()) { + throw new IllegalStateException("cannot interact with a closed instance"); + } + } + + private void enforceValidIndex(int index) throws IOException { + final int size = size(); + if (index < 0 || index > size - 1) { + throw new IndexOutOfBoundsException( + index + " not between 0 and " + (size - 1)); + } + } + + private void enforceWritable() { + if (!isWritable()) { + throw new UnsupportedOperationException("array is not writable"); + } + } + + private native int nativeCreate(String name, int size); + private native long nativeOpen(int fd, boolean owner, boolean writable); + private native void nativeClose(int fd, long memoryAddr, boolean owner); + private native int nativeGet(int fd, long memoryAddr, int index, boolean owner); + private native void nativeSet(int fd, long memoryAddr, int index, int value, boolean owner); + private native int nativeSize(int fd); + private native static int nativeGetMemoryPageSize(); + + /** + * @return The max array size. + */ + public static int getMaxSize() { + return MAX_SIZE; + } + + public static final Parcelable.Creator<MemoryIntArray> CREATOR = + new Parcelable.Creator<MemoryIntArray>() { + @Override + public MemoryIntArray createFromParcel(Parcel parcel) { + try { + return new MemoryIntArray(parcel); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + @Override + public MemoryIntArray[] newArray(int size) { + return new MemoryIntArray[size]; + } + }; +} diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 8a512a6df06b..06d9b294d848 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -95,6 +95,7 @@ LOCAL_SRC_FILES:= \ 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 d3dca5d91478..a6a4f6bf2d1b 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -113,6 +113,7 @@ extern int register_android_app_admin_SecurityLog(JNIEnv* env); 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 @@ static const RegJNIRec gRegJNI[] = { 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 000000000000..dbe8ed3e26d0 --- /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 000000000000..6b319165599c --- /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 000000000000..48533fa2aadb --- /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 8dc247a010a2..75fb413354f9 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.net.Uri; 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.ParcelFileDescriptor; 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.annotations.GuardedBy; 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { // 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 @@ public class SettingsProvider extends ContentProvider { mSettingsRegistry = new SettingsRegistry(); } registerBroadcastReceivers(); + startWatchingUserRestrictionChanges(); return true; } @@ -213,28 +257,28 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { // 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { } Setting setting = mSettingsRegistry.getSettingLocked( - SettingsRegistry.SETTINGS_TYPE_SECURE, owningUserId, name); + SETTINGS_TYPE_SECURE, owningUserId, name); appendSettingToCursor(result, setting); } @@ -738,39 +876,44 @@ public class SettingsProvider extends ContentProvider { // 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 @@ public class SettingsProvider extends ContentProvider { // 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { name); Setting setting = mSettingsRegistry.getSettingLocked( - SettingsRegistry.SETTINGS_TYPE_SYSTEM, owningUserId, name); + SETTINGS_TYPE_SYSTEM, owningUserId, name); appendSettingToCursor(result, setting); } @@ -868,8 +1008,7 @@ public class SettingsProvider extends ContentProvider { // 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { * 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 @@ public class SettingsProvider extends ContentProvider { * * @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 @@ public class SettingsProvider extends ContentProvider { } } 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 @@ public class SettingsProvider extends ContentProvider { "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 @@ public class SettingsProvider extends ContentProvider { : 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { }); } } + + // 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 class SettingsProvider extends ContentProvider { } 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 @@ public class SettingsProvider extends ContentProvider { } 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { // 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 @@ public class SettingsProvider extends ContentProvider { // 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 dde9709fee50..6dfd0e678614 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 @@ final class SettingsState { @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 @@ final class SettingsState { // The settings provider must hold its lock when calling here. public Setting getSettingLocked(String name) { if (TextUtils.isEmpty(name)) { - return null; + return mNullSetting; + } + Setting setting = mSettings.get(name); + if (setting != null) { + return new Setting(setting); } - return mSettings.get(name); + return mNullSetting; } // The settings provider must hold its lock when calling here. @@ -549,12 +562,19 @@ final class SettingsState { } } - 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 @@ final class SettingsState { return name; } + public int getkey() { + return mKey; + } + public String getValue() { return value; } @@ -587,6 +611,10 @@ final class SettingsState { 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 364e9fa6719c..38a3f421264f 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 @@ public class UserRestrictionsUtils { 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) { |