Merge "Ensure local settings caches are not stale" into nyc-dev

am: f71d7fe

* commit 'f71d7feef22db9e0cab2f32edc7440aedb86fdfe':
  Ensure local settings caches are not stale

Change-Id: I356b9ad0b6dc1e91bfad140de1b9fc79ab6efef3
This commit is contained in:
Svet Ganov
2016-04-26 18:47:52 +00:00
committed by android-build-merger
15 changed files with 1251 additions and 193 deletions

View File

@ -597,6 +597,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 \

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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;
@ -1281,6 +1284,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
*/
@ -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();
mValuesVersion = newValuesVersion;
} else if (mValues.containsKey(name)) {
return mValues.get(name);
}
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);

View File

@ -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;

View File

@ -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];
}
};
}

View File

@ -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 \

View File

@ -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),

View File

@ -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));
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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: {
synchronized (mLock) {
mSettingsRegistry.removeUserStateLocked(userId, true);
}
} break;
case Intent.ACTION_USER_STOPPED: {
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,12 +1417,20 @@ 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());
}
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) {
final int callingUserId = UserHandle.getCallingUserId();
@ -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);
}
private void maybeNotifyProfiles(int userId, Uri uri, String name,
mGenerationRegistry.incrementGeneration(key);
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
}
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,21 +1971,13 @@ 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);
private int makeKey(int type, int userId) {
return (type << SETTINGS_TYPE_SHIFT) | userId;
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
}
}
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) {
@ -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);
}

View File

@ -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;
}
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 @@ 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;

View File

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