Ensure local settings caches are not stale
We used the system proterties as a shared memory mechanism to propagate information to local settings caches when the content has changed and the cache should be cleared. The system properties are unfortunately updated asynchronously leading to cases where clients may read stale data. This change adds a simple int array data structure backed by shared memory which guarantees individual values are atomically read and updated without memory tear. Multi- index opearations are not synchronized between each other. The settings provider is using the new data structure to propagate the settings generation which drives when caches are purged. We have a single memory array keeping the generation for different settings tables per user. Since memory array is not a compact data structure and the user space exceeds the memory array size we use an in-memory map from keys to indices in the memory array where the generation id of a key is stored. A key is derived by the setting type in the 4 most significant bits and the user id in the 28 least significant bits. The mapping from a key to an index is cleared if the user is removed and the corresponding index in the memory arry is reset to make it available for other users. The size of the memory array is derived from the max user count that can be created at the same time. bug:18826179 Change-Id: I64009cc5105309ef9aa83aba90b82afc8ad8c659
This commit is contained in:
committed by
Svetoslav Ganov
parent
5a91b61523
commit
53a441ca8e
@ -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 \
|
||||
|
@ -32486,7 +32486,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";
|
||||
@ -32567,7 +32566,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";
|
||||
@ -32682,7 +32680,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";
|
||||
|
@ -35143,7 +35143,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";
|
||||
@ -35226,7 +35225,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";
|
||||
@ -35341,7 +35339,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";
|
||||
|
@ -32558,7 +32558,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";
|
||||
@ -32639,7 +32638,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";
|
||||
@ -32755,7 +32753,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";
|
||||
|
@ -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();
|
||||
} 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);
|
||||
@ -6357,8 +6436,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.
|
||||
*/
|
||||
@ -8409,7 +8486,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);
|
||||
|
19
core/java/android/util/MemoryIntArray.aidl
Normal file
19
core/java/android/util/MemoryIntArray.aidl
Normal 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;
|
258
core/java/android/util/MemoryIntArray.java
Normal file
258
core/java/android/util/MemoryIntArray.java
Normal 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];
|
||||
}
|
||||
};
|
||||
}
|
@ -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 \
|
||||
|
@ -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),
|
||||
|
219
core/jni/android_util_MemoryIntArray.cpp
Normal file
219
core/jni/android_util_MemoryIntArray.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
192
core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
Normal file
192
core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user