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:
@ -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 \
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
|
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: {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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