null 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+ * 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 not thread safe. You should not interact with + * an instance of this class once it is closed. + *
+ * + * @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.CreatorSee also {@link com.android.server.pm.UserRestrictionsUtils#applyUserRestrictionLR}, - * which should be in sync with this method. + *
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