Loading core/java/android/provider/Settings.java +123 −16 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.DropBoxManager; import android.os.IBinder; import android.os.IpcDataCache; import android.os.LocaleList; import android.os.PowerManager; import android.os.PowerManager.AutoPowerSaveModeTriggers; Loading Loading @@ -124,6 +125,9 @@ public final class Settings { /** @hide */ public static final boolean DEFAULT_OVERRIDEABLE_BY_RESTORE = false; /** @hide default value of whether IpcDataCache is enabled or not */ public static final boolean IPC_DATA_CACHE_ENABLED = false; // Intent actions for Settings /** Loading Loading @@ -2920,8 +2924,8 @@ public final class Settings { public static final String AUTHORITY = "settings"; private static final String TAG = "Settings"; private static final boolean LOCAL_LOGV = false; static final String TAG = "Settings"; static final boolean LOCAL_LOGV = false; // Used in system server calling uid workaround in call() private static boolean sInSystemServer = false; Loading Loading @@ -3031,10 +3035,10 @@ public final class Settings { } } private static final class ContentProviderHolder { static final class ContentProviderHolder { private final Object mLock = new Object(); private final Uri mUri; final Uri mUri; @GuardedBy("mLock") @UnsupportedAppUsage private IContentProvider mContentProvider; Loading @@ -3061,14 +3065,14 @@ public final class Settings { } // Thread-safe. private static class NameValueCache { static class NameValueCache { private static final boolean DEBUG = false; private static final String[] SELECT_VALUE_PROJECTION = new String[] { static final String[] SELECT_VALUE_PROJECTION = new String[] { Settings.NameValueTable.VALUE }; private static final String NAME_EQ_PLACEHOLDER = "name=?"; static final String NAME_EQ_PLACEHOLDER = "name=?"; // Must synchronize on 'this' to access mValues and mValuesVersion. private final ArrayMap<String, String> mValues = new ArrayMap<>(); Loading @@ -3089,6 +3093,14 @@ public final class Settings { private final ArraySet<String> mAllFields; private final ArrayMap<String, Integer> mReadableFieldsWithMaxTargetSdk; private final String mSettingsType; // Caches for settings key -> value, only for the current user private final IpcDataCache<SettingsIpcDataCache.GetQuery, String> mValueCache; // Cache for settings namespace -> list of settings, only for the current user private final IpcDataCache<SettingsIpcDataCache.ListQuery, HashMap<String, String>> mNamespaceCache; @GuardedBy("this") private GenerationTracker mGenerationTracker; Loading @@ -3114,6 +3126,11 @@ public final class Settings { mReadableFieldsWithMaxTargetSdk = new ArrayMap<>(); getPublicSettingsForClass(callerClass, mAllFields, mReadableFields, mReadableFieldsWithMaxTargetSdk); mSettingsType = callerClass.getSimpleName().toLowerCase(); mValueCache = IPC_DATA_CACHE_ENABLED ? SettingsIpcDataCache.createValueCache( mProviderHolder, mCallGetCommand, mUri, mSettingsType) : null; mNamespaceCache = IPC_DATA_CACHE_ENABLED ? SettingsIpcDataCache.createListCache( mProviderHolder, mCallListCommand, mSettingsType) : null; } public boolean putStringForUser(ContentResolver cr, String name, String value, Loading Loading @@ -3212,6 +3229,30 @@ public final class Settings { } } if (IPC_DATA_CACHE_ENABLED) { if (userHandle != UserHandle.myUserId()) { if (LOCAL_LOGV) { Log.v(TAG, "get setting for user " + userHandle + " by user " + UserHandle.myUserId() + " so skipping cache"); } try { return SettingsIpcDataCache.getValueFromContentProviderCall( mProviderHolder, mCallGetCommand, mUri, userHandle, cr, name); } catch (RemoteException e) { return null; } } try { return mValueCache.query(new SettingsIpcDataCache.GetQuery(cr, name)); } catch (RuntimeException e) { // Failed to query the server return null; } } // Fall back to old cache mechanism final boolean isSelf = (userHandle == UserHandle.myUserId()); int currentGeneration = -1; if (isSelf) { Loading Loading @@ -3408,6 +3449,38 @@ public final class Settings { List<String> names) { String namespace = prefix.substring(0, prefix.length() - 1); Config.enforceReadPermission(namespace); if (mCallListCommand == null) { // No list command specified, return empty map return new ArrayMap<>(); } if (IPC_DATA_CACHE_ENABLED) { ArrayMap<String, String> results = new ArrayMap<>(); HashMap<String, String> flagsToValues; try { flagsToValues = mNamespaceCache.query( new SettingsIpcDataCache.ListQuery(cr, prefix)); } catch (RuntimeException e) { // Failed to query the server, return an empty map return results; } if (flagsToValues != null) { if (!names.isEmpty()) { for (Map.Entry<String, String> flag : flagsToValues.entrySet()) { if (names.contains(flag.getKey())) { results.put(flag.getKey(), flag.getValue()); } } } else { results.putAll(flagsToValues); } } return results; } // Fall back to old cache mechanism ArrayMap<String, String> keyValues = new ArrayMap<>(); int currentGeneration = -1; Loading Loading @@ -3447,10 +3520,7 @@ public final class Settings { } } if (mCallListCommand == null) { // No list command specified, return empty map return keyValues; } IContentProvider cp = mProviderHolder.getProvider(cr); try { Loading Loading @@ -3556,7 +3626,13 @@ public final class Settings { } } public void clearGenerationTrackerForTest() { public void clearCachesForTest() { if (IPC_DATA_CACHE_ENABLED) { mValueCache.clear(); mNamespaceCache.clear(); return; } // Fall back to old cache mechanism synchronized (NameValueCache.this) { if (mGenerationTracker != null) { mGenerationTracker.destroy(); Loading @@ -3565,6 +3641,12 @@ public final class Settings { mGenerationTracker = null; } } public void invalidateCache() { if (IPC_DATA_CACHE_ENABLED) { SettingsIpcDataCache.invalidateCache(mSettingsType); } } } /** Loading Loading @@ -3841,7 +3923,12 @@ public final class Settings { /** @hide */ public static void clearProviderForTest() { sProviderHolder.clearProviderForTest(); sNameValueCache.clearGenerationTrackerForTest(); sNameValueCache.clearCachesForTest(); } /** @hide */ public static void invalidateValueCache() { sNameValueCache.invalidateCache(); } /** @hide */ Loading Loading @@ -6279,7 +6366,12 @@ public final class Settings { /** @hide */ public static void clearProviderForTest() { sProviderHolder.clearProviderForTest(); sNameValueCache.clearGenerationTrackerForTest(); sNameValueCache.clearCachesForTest(); } /** @hide */ public static void invalidateValueCache() { sNameValueCache.invalidateCache(); } /** @hide */ Loading Loading @@ -16415,7 +16507,12 @@ public final class Settings { /** @hide */ public static void clearProviderForTest() { sProviderHolder.clearProviderForTest(); sNameValueCache.clearGenerationTrackerForTest(); sNameValueCache.clearCachesForTest(); } /** @hide */ public static void invalidateValueCache() { sNameValueCache.invalidateCache(); } /** @hide */ Loading Loading @@ -18631,7 +18728,17 @@ public final class Settings { /** @hide */ public static void clearProviderForTest() { sProviderHolder.clearProviderForTest(); sNameValueCache.clearGenerationTrackerForTest(); sNameValueCache.clearCachesForTest(); } /** @hide */ public static void invalidateValueCache() { sNameValueCache.invalidateCache(); } /** @hide */ public static void invalidateNamespaceCache() { sNameValueCache.invalidateCache(); } private static void handleMonitorCallback( core/java/android/provider/SettingsIpcDataCache.java 0 → 100644 +315 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.provider; import static android.provider.Settings.CALL_METHOD_USER_KEY; import static android.provider.Settings.ContentProviderHolder; import static android.provider.Settings.LOCAL_LOGV; import static android.provider.Settings.NameValueCache.NAME_EQ_PLACEHOLDER; import static android.provider.Settings.NameValueCache.SELECT_VALUE_PROJECTION; import static android.provider.Settings.TAG; import android.annotation.NonNull; import android.content.ContentResolver; import android.content.IContentProvider; import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.IpcDataCache; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; import androidx.annotation.Nullable; import java.util.HashMap; import java.util.Objects; /** @hide */ final class SettingsIpcDataCache { private static final boolean DEBUG = true; private static final int NUM_MAX_ENTRIES = 2048; static class GetQuery { @NonNull final ContentResolver mContentResolver; @NonNull final String mName; GetQuery(@NonNull ContentResolver contentResolver, @NonNull String name) { mContentResolver = contentResolver; mName = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof GetQuery)) return false; GetQuery getQuery = (GetQuery) o; return mContentResolver.equals( getQuery.mContentResolver) && mName.equals(getQuery.mName); } @Override public int hashCode() { return Objects.hash(mContentResolver, mName); } } private static class GetQueryHandler extends IpcDataCache.QueryHandler<GetQuery, String> { @NonNull final ContentProviderHolder mContentProviderHolder; @NonNull final String mCallGetCommand; @NonNull final Uri mUri; final int mUserId; private GetQueryHandler( ContentProviderHolder contentProviderHolder, String callGetCommand, Uri uri) { mContentProviderHolder = contentProviderHolder; mCallGetCommand = callGetCommand; mUri = uri; mUserId = UserHandle.myUserId(); } @Nullable @Override public String apply(GetQuery query) { try { return getValueFromContentProviderCall(mContentProviderHolder, mCallGetCommand, mUri, mUserId, query); } catch (RemoteException e) { // Throw to prevent caching e.rethrowAsRuntimeException(); } return null; } } @NonNull static IpcDataCache<GetQuery, String> createValueCache( @NonNull ContentProviderHolder contentProviderHolder, @NonNull String callGetCommand, @NonNull Uri uri, @NonNull String type) { if (DEBUG) { Log.i(TAG, "Creating value cache for type:" + type); } IpcDataCache.Config config = new IpcDataCache.Config( NUM_MAX_ENTRIES, IpcDataCache.MODULE_SYSTEM, type /* apiName */); return new IpcDataCache<>(config.child("get"), new GetQueryHandler(contentProviderHolder, callGetCommand, uri)); } @Nullable private static String getValueFromContentProviderCall( @NonNull ContentProviderHolder providerHolder, @NonNull String callGetCommand, @NonNull Uri uri, int userId, @NonNull GetQuery query) throws RemoteException { final ContentResolver cr = query.mContentResolver; final String name = query.mName; return getValueFromContentProviderCall(providerHolder, callGetCommand, uri, userId, cr, name); } @Nullable static String getValueFromContentProviderCall( @NonNull ContentProviderHolder providerHolder, @NonNull String callGetCommand, @NonNull Uri uri, int userId, ContentResolver cr, String name) throws RemoteException { final IContentProvider cp = providerHolder.getProvider(cr); // Try the fast path first, not using query(). If this // fails (alternate Settings provider that doesn't support // this interface?) then we fall back to the query/table // interface. if (callGetCommand != null) { try { Bundle args = new Bundle(); if (userId != UserHandle.myUserId()) { args.putInt(CALL_METHOD_USER_KEY, userId); } Bundle b; // If we're in system server and in a binder transaction we need to clear the // calling uid. This works around code in system server that did not call // clearCallingIdentity, previously this wasn't needed because reading settings // did not do permission checking but that's no longer the case. // Long term this should be removed and callers should properly call // clearCallingIdentity or use a ContentResolver from the caller as needed. if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) { final long token = Binder.clearCallingIdentity(); try { b = cp.call(cr.getAttributionSource(), providerHolder.mUri.getAuthority(), callGetCommand, name, args); } finally { Binder.restoreCallingIdentity(token); } } else { b = cp.call(cr.getAttributionSource(), providerHolder.mUri.getAuthority(), callGetCommand, name, args); } if (b != null) { return b.getString(Settings.NameValueTable.VALUE); } // If the response Bundle is null, we fall through // to the query interface below. } catch (RemoteException e) { // Not supported by the remote side? Fall through // to query(). } } Cursor c = null; try { Bundle queryArgs = ContentResolver.createSqlQueryBundle( NAME_EQ_PLACEHOLDER, new String[]{name}, null); // Same workaround as above. if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) { final long token = Binder.clearCallingIdentity(); try { c = cp.query(cr.getAttributionSource(), uri, SELECT_VALUE_PROJECTION, queryArgs, null); } finally { Binder.restoreCallingIdentity(token); } } else { c = cp.query(cr.getAttributionSource(), uri, SELECT_VALUE_PROJECTION, queryArgs, null); } if (c == null) { Log.w(TAG, "Can't get key " + name + " from " + uri); return null; } String value = c.moveToNext() ? c.getString(0) : null; if (LOCAL_LOGV) { Log.v(TAG, "cache miss [" + uri.getLastPathSegment() + "]: " + name + " = " + (value == null ? "(null)" : value)); } return value; } finally { if (c != null) c.close(); } } static class ListQuery { @NonNull ContentResolver mContentResolver; @NonNull final String mPrefix; ListQuery(@NonNull ContentResolver contentResolver, String prefix) { mContentResolver = contentResolver; mPrefix = prefix; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof ListQuery)) return false; ListQuery listQuery = (ListQuery) o; return mContentResolver.equals(listQuery.mContentResolver) && mPrefix.equals( listQuery.mPrefix); } @Override public int hashCode() { return Objects.hash(mContentResolver, mPrefix); } } private static class ListQueryHandler extends IpcDataCache.QueryHandler<ListQuery, HashMap<String, String>> { @NonNull final ContentProviderHolder mContentProviderHolder; @NonNull final String mCallListCommand; ListQueryHandler(@NonNull ContentProviderHolder contentProviderHolder, @NonNull String callListCommand) { mContentProviderHolder = contentProviderHolder; mCallListCommand = callListCommand; } @Nullable @Override public HashMap<String, String> apply(@NonNull ListQuery query) { try { return getListFromContentProviderCall(query); } catch (RemoteException e) { // Throw to prevent caching e.rethrowAsRuntimeException(); } return null; } @Nullable private HashMap<String, String> getListFromContentProviderCall(ListQuery query) throws RemoteException { final ContentResolver cr = query.mContentResolver; final IContentProvider cp = mContentProviderHolder.getProvider(cr); final String prefix = query.mPrefix; final String namespace = prefix.substring(0, prefix.length() - 1); HashMap<String, String> keyValues = new HashMap<>(); Bundle args = new Bundle(); args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix); Bundle b; // b/252663068: if we're in system server and the caller did not call // clearCallingIdentity, the read would fail due to mismatched AttributionSources. // TODO(b/256013480): remove this bypass after fixing the callers in system server. if (namespace.equals(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER) && Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) { final long token = Binder.clearCallingIdentity(); try { // Fetch all flags for the namespace at once for caching purposes b = cp.call(cr.getAttributionSource(), mContentProviderHolder.mUri.getAuthority(), mCallListCommand, null, args); } finally { Binder.restoreCallingIdentity(token); } } else { // Fetch all flags for the namespace at once for caching purposes b = cp.call(cr.getAttributionSource(), mContentProviderHolder.mUri.getAuthority(), mCallListCommand, null, args); } if (b == null) { // Invalid response, return an empty map return keyValues; } // Cache all flags for the namespace HashMap<String, String> flagsToValues = (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, java.util.HashMap.class); return flagsToValues; } } @NonNull static IpcDataCache<ListQuery, HashMap<String, String>> createListCache( @NonNull ContentProviderHolder providerHolder, @NonNull String callListCommand, String type) { if (DEBUG) { Log.i(TAG, "Creating cache for settings type:" + type); } IpcDataCache.Config config = new IpcDataCache.Config( NUM_MAX_ENTRIES, IpcDataCache.MODULE_SYSTEM, type /* apiName */); return new IpcDataCache<>(config.child("get"), new ListQueryHandler(providerHolder, callListCommand)); } static void invalidateCache(String type) { if (DEBUG) { Log.i(TAG, "Cache invalidated for type:" + type); } IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, type); } } packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +56 −25 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/provider/Settings.java +123 −16 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.DropBoxManager; import android.os.IBinder; import android.os.IpcDataCache; import android.os.LocaleList; import android.os.PowerManager; import android.os.PowerManager.AutoPowerSaveModeTriggers; Loading Loading @@ -124,6 +125,9 @@ public final class Settings { /** @hide */ public static final boolean DEFAULT_OVERRIDEABLE_BY_RESTORE = false; /** @hide default value of whether IpcDataCache is enabled or not */ public static final boolean IPC_DATA_CACHE_ENABLED = false; // Intent actions for Settings /** Loading Loading @@ -2920,8 +2924,8 @@ public final class Settings { public static final String AUTHORITY = "settings"; private static final String TAG = "Settings"; private static final boolean LOCAL_LOGV = false; static final String TAG = "Settings"; static final boolean LOCAL_LOGV = false; // Used in system server calling uid workaround in call() private static boolean sInSystemServer = false; Loading Loading @@ -3031,10 +3035,10 @@ public final class Settings { } } private static final class ContentProviderHolder { static final class ContentProviderHolder { private final Object mLock = new Object(); private final Uri mUri; final Uri mUri; @GuardedBy("mLock") @UnsupportedAppUsage private IContentProvider mContentProvider; Loading @@ -3061,14 +3065,14 @@ public final class Settings { } // Thread-safe. private static class NameValueCache { static class NameValueCache { private static final boolean DEBUG = false; private static final String[] SELECT_VALUE_PROJECTION = new String[] { static final String[] SELECT_VALUE_PROJECTION = new String[] { Settings.NameValueTable.VALUE }; private static final String NAME_EQ_PLACEHOLDER = "name=?"; static final String NAME_EQ_PLACEHOLDER = "name=?"; // Must synchronize on 'this' to access mValues and mValuesVersion. private final ArrayMap<String, String> mValues = new ArrayMap<>(); Loading @@ -3089,6 +3093,14 @@ public final class Settings { private final ArraySet<String> mAllFields; private final ArrayMap<String, Integer> mReadableFieldsWithMaxTargetSdk; private final String mSettingsType; // Caches for settings key -> value, only for the current user private final IpcDataCache<SettingsIpcDataCache.GetQuery, String> mValueCache; // Cache for settings namespace -> list of settings, only for the current user private final IpcDataCache<SettingsIpcDataCache.ListQuery, HashMap<String, String>> mNamespaceCache; @GuardedBy("this") private GenerationTracker mGenerationTracker; Loading @@ -3114,6 +3126,11 @@ public final class Settings { mReadableFieldsWithMaxTargetSdk = new ArrayMap<>(); getPublicSettingsForClass(callerClass, mAllFields, mReadableFields, mReadableFieldsWithMaxTargetSdk); mSettingsType = callerClass.getSimpleName().toLowerCase(); mValueCache = IPC_DATA_CACHE_ENABLED ? SettingsIpcDataCache.createValueCache( mProviderHolder, mCallGetCommand, mUri, mSettingsType) : null; mNamespaceCache = IPC_DATA_CACHE_ENABLED ? SettingsIpcDataCache.createListCache( mProviderHolder, mCallListCommand, mSettingsType) : null; } public boolean putStringForUser(ContentResolver cr, String name, String value, Loading Loading @@ -3212,6 +3229,30 @@ public final class Settings { } } if (IPC_DATA_CACHE_ENABLED) { if (userHandle != UserHandle.myUserId()) { if (LOCAL_LOGV) { Log.v(TAG, "get setting for user " + userHandle + " by user " + UserHandle.myUserId() + " so skipping cache"); } try { return SettingsIpcDataCache.getValueFromContentProviderCall( mProviderHolder, mCallGetCommand, mUri, userHandle, cr, name); } catch (RemoteException e) { return null; } } try { return mValueCache.query(new SettingsIpcDataCache.GetQuery(cr, name)); } catch (RuntimeException e) { // Failed to query the server return null; } } // Fall back to old cache mechanism final boolean isSelf = (userHandle == UserHandle.myUserId()); int currentGeneration = -1; if (isSelf) { Loading Loading @@ -3408,6 +3449,38 @@ public final class Settings { List<String> names) { String namespace = prefix.substring(0, prefix.length() - 1); Config.enforceReadPermission(namespace); if (mCallListCommand == null) { // No list command specified, return empty map return new ArrayMap<>(); } if (IPC_DATA_CACHE_ENABLED) { ArrayMap<String, String> results = new ArrayMap<>(); HashMap<String, String> flagsToValues; try { flagsToValues = mNamespaceCache.query( new SettingsIpcDataCache.ListQuery(cr, prefix)); } catch (RuntimeException e) { // Failed to query the server, return an empty map return results; } if (flagsToValues != null) { if (!names.isEmpty()) { for (Map.Entry<String, String> flag : flagsToValues.entrySet()) { if (names.contains(flag.getKey())) { results.put(flag.getKey(), flag.getValue()); } } } else { results.putAll(flagsToValues); } } return results; } // Fall back to old cache mechanism ArrayMap<String, String> keyValues = new ArrayMap<>(); int currentGeneration = -1; Loading Loading @@ -3447,10 +3520,7 @@ public final class Settings { } } if (mCallListCommand == null) { // No list command specified, return empty map return keyValues; } IContentProvider cp = mProviderHolder.getProvider(cr); try { Loading Loading @@ -3556,7 +3626,13 @@ public final class Settings { } } public void clearGenerationTrackerForTest() { public void clearCachesForTest() { if (IPC_DATA_CACHE_ENABLED) { mValueCache.clear(); mNamespaceCache.clear(); return; } // Fall back to old cache mechanism synchronized (NameValueCache.this) { if (mGenerationTracker != null) { mGenerationTracker.destroy(); Loading @@ -3565,6 +3641,12 @@ public final class Settings { mGenerationTracker = null; } } public void invalidateCache() { if (IPC_DATA_CACHE_ENABLED) { SettingsIpcDataCache.invalidateCache(mSettingsType); } } } /** Loading Loading @@ -3841,7 +3923,12 @@ public final class Settings { /** @hide */ public static void clearProviderForTest() { sProviderHolder.clearProviderForTest(); sNameValueCache.clearGenerationTrackerForTest(); sNameValueCache.clearCachesForTest(); } /** @hide */ public static void invalidateValueCache() { sNameValueCache.invalidateCache(); } /** @hide */ Loading Loading @@ -6279,7 +6366,12 @@ public final class Settings { /** @hide */ public static void clearProviderForTest() { sProviderHolder.clearProviderForTest(); sNameValueCache.clearGenerationTrackerForTest(); sNameValueCache.clearCachesForTest(); } /** @hide */ public static void invalidateValueCache() { sNameValueCache.invalidateCache(); } /** @hide */ Loading Loading @@ -16415,7 +16507,12 @@ public final class Settings { /** @hide */ public static void clearProviderForTest() { sProviderHolder.clearProviderForTest(); sNameValueCache.clearGenerationTrackerForTest(); sNameValueCache.clearCachesForTest(); } /** @hide */ public static void invalidateValueCache() { sNameValueCache.invalidateCache(); } /** @hide */ Loading Loading @@ -18631,7 +18728,17 @@ public final class Settings { /** @hide */ public static void clearProviderForTest() { sProviderHolder.clearProviderForTest(); sNameValueCache.clearGenerationTrackerForTest(); sNameValueCache.clearCachesForTest(); } /** @hide */ public static void invalidateValueCache() { sNameValueCache.invalidateCache(); } /** @hide */ public static void invalidateNamespaceCache() { sNameValueCache.invalidateCache(); } private static void handleMonitorCallback(
core/java/android/provider/SettingsIpcDataCache.java 0 → 100644 +315 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.provider; import static android.provider.Settings.CALL_METHOD_USER_KEY; import static android.provider.Settings.ContentProviderHolder; import static android.provider.Settings.LOCAL_LOGV; import static android.provider.Settings.NameValueCache.NAME_EQ_PLACEHOLDER; import static android.provider.Settings.NameValueCache.SELECT_VALUE_PROJECTION; import static android.provider.Settings.TAG; import android.annotation.NonNull; import android.content.ContentResolver; import android.content.IContentProvider; import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.IpcDataCache; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; import androidx.annotation.Nullable; import java.util.HashMap; import java.util.Objects; /** @hide */ final class SettingsIpcDataCache { private static final boolean DEBUG = true; private static final int NUM_MAX_ENTRIES = 2048; static class GetQuery { @NonNull final ContentResolver mContentResolver; @NonNull final String mName; GetQuery(@NonNull ContentResolver contentResolver, @NonNull String name) { mContentResolver = contentResolver; mName = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof GetQuery)) return false; GetQuery getQuery = (GetQuery) o; return mContentResolver.equals( getQuery.mContentResolver) && mName.equals(getQuery.mName); } @Override public int hashCode() { return Objects.hash(mContentResolver, mName); } } private static class GetQueryHandler extends IpcDataCache.QueryHandler<GetQuery, String> { @NonNull final ContentProviderHolder mContentProviderHolder; @NonNull final String mCallGetCommand; @NonNull final Uri mUri; final int mUserId; private GetQueryHandler( ContentProviderHolder contentProviderHolder, String callGetCommand, Uri uri) { mContentProviderHolder = contentProviderHolder; mCallGetCommand = callGetCommand; mUri = uri; mUserId = UserHandle.myUserId(); } @Nullable @Override public String apply(GetQuery query) { try { return getValueFromContentProviderCall(mContentProviderHolder, mCallGetCommand, mUri, mUserId, query); } catch (RemoteException e) { // Throw to prevent caching e.rethrowAsRuntimeException(); } return null; } } @NonNull static IpcDataCache<GetQuery, String> createValueCache( @NonNull ContentProviderHolder contentProviderHolder, @NonNull String callGetCommand, @NonNull Uri uri, @NonNull String type) { if (DEBUG) { Log.i(TAG, "Creating value cache for type:" + type); } IpcDataCache.Config config = new IpcDataCache.Config( NUM_MAX_ENTRIES, IpcDataCache.MODULE_SYSTEM, type /* apiName */); return new IpcDataCache<>(config.child("get"), new GetQueryHandler(contentProviderHolder, callGetCommand, uri)); } @Nullable private static String getValueFromContentProviderCall( @NonNull ContentProviderHolder providerHolder, @NonNull String callGetCommand, @NonNull Uri uri, int userId, @NonNull GetQuery query) throws RemoteException { final ContentResolver cr = query.mContentResolver; final String name = query.mName; return getValueFromContentProviderCall(providerHolder, callGetCommand, uri, userId, cr, name); } @Nullable static String getValueFromContentProviderCall( @NonNull ContentProviderHolder providerHolder, @NonNull String callGetCommand, @NonNull Uri uri, int userId, ContentResolver cr, String name) throws RemoteException { final IContentProvider cp = providerHolder.getProvider(cr); // Try the fast path first, not using query(). If this // fails (alternate Settings provider that doesn't support // this interface?) then we fall back to the query/table // interface. if (callGetCommand != null) { try { Bundle args = new Bundle(); if (userId != UserHandle.myUserId()) { args.putInt(CALL_METHOD_USER_KEY, userId); } Bundle b; // If we're in system server and in a binder transaction we need to clear the // calling uid. This works around code in system server that did not call // clearCallingIdentity, previously this wasn't needed because reading settings // did not do permission checking but that's no longer the case. // Long term this should be removed and callers should properly call // clearCallingIdentity or use a ContentResolver from the caller as needed. if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) { final long token = Binder.clearCallingIdentity(); try { b = cp.call(cr.getAttributionSource(), providerHolder.mUri.getAuthority(), callGetCommand, name, args); } finally { Binder.restoreCallingIdentity(token); } } else { b = cp.call(cr.getAttributionSource(), providerHolder.mUri.getAuthority(), callGetCommand, name, args); } if (b != null) { return b.getString(Settings.NameValueTable.VALUE); } // If the response Bundle is null, we fall through // to the query interface below. } catch (RemoteException e) { // Not supported by the remote side? Fall through // to query(). } } Cursor c = null; try { Bundle queryArgs = ContentResolver.createSqlQueryBundle( NAME_EQ_PLACEHOLDER, new String[]{name}, null); // Same workaround as above. if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) { final long token = Binder.clearCallingIdentity(); try { c = cp.query(cr.getAttributionSource(), uri, SELECT_VALUE_PROJECTION, queryArgs, null); } finally { Binder.restoreCallingIdentity(token); } } else { c = cp.query(cr.getAttributionSource(), uri, SELECT_VALUE_PROJECTION, queryArgs, null); } if (c == null) { Log.w(TAG, "Can't get key " + name + " from " + uri); return null; } String value = c.moveToNext() ? c.getString(0) : null; if (LOCAL_LOGV) { Log.v(TAG, "cache miss [" + uri.getLastPathSegment() + "]: " + name + " = " + (value == null ? "(null)" : value)); } return value; } finally { if (c != null) c.close(); } } static class ListQuery { @NonNull ContentResolver mContentResolver; @NonNull final String mPrefix; ListQuery(@NonNull ContentResolver contentResolver, String prefix) { mContentResolver = contentResolver; mPrefix = prefix; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof ListQuery)) return false; ListQuery listQuery = (ListQuery) o; return mContentResolver.equals(listQuery.mContentResolver) && mPrefix.equals( listQuery.mPrefix); } @Override public int hashCode() { return Objects.hash(mContentResolver, mPrefix); } } private static class ListQueryHandler extends IpcDataCache.QueryHandler<ListQuery, HashMap<String, String>> { @NonNull final ContentProviderHolder mContentProviderHolder; @NonNull final String mCallListCommand; ListQueryHandler(@NonNull ContentProviderHolder contentProviderHolder, @NonNull String callListCommand) { mContentProviderHolder = contentProviderHolder; mCallListCommand = callListCommand; } @Nullable @Override public HashMap<String, String> apply(@NonNull ListQuery query) { try { return getListFromContentProviderCall(query); } catch (RemoteException e) { // Throw to prevent caching e.rethrowAsRuntimeException(); } return null; } @Nullable private HashMap<String, String> getListFromContentProviderCall(ListQuery query) throws RemoteException { final ContentResolver cr = query.mContentResolver; final IContentProvider cp = mContentProviderHolder.getProvider(cr); final String prefix = query.mPrefix; final String namespace = prefix.substring(0, prefix.length() - 1); HashMap<String, String> keyValues = new HashMap<>(); Bundle args = new Bundle(); args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix); Bundle b; // b/252663068: if we're in system server and the caller did not call // clearCallingIdentity, the read would fail due to mismatched AttributionSources. // TODO(b/256013480): remove this bypass after fixing the callers in system server. if (namespace.equals(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER) && Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) { final long token = Binder.clearCallingIdentity(); try { // Fetch all flags for the namespace at once for caching purposes b = cp.call(cr.getAttributionSource(), mContentProviderHolder.mUri.getAuthority(), mCallListCommand, null, args); } finally { Binder.restoreCallingIdentity(token); } } else { // Fetch all flags for the namespace at once for caching purposes b = cp.call(cr.getAttributionSource(), mContentProviderHolder.mUri.getAuthority(), mCallListCommand, null, args); } if (b == null) { // Invalid response, return an empty map return keyValues; } // Cache all flags for the namespace HashMap<String, String> flagsToValues = (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, java.util.HashMap.class); return flagsToValues; } } @NonNull static IpcDataCache<ListQuery, HashMap<String, String>> createListCache( @NonNull ContentProviderHolder providerHolder, @NonNull String callListCommand, String type) { if (DEBUG) { Log.i(TAG, "Creating cache for settings type:" + type); } IpcDataCache.Config config = new IpcDataCache.Config( NUM_MAX_ENTRIES, IpcDataCache.MODULE_SYSTEM, type /* apiName */); return new IpcDataCache<>(config.child("get"), new ListQueryHandler(providerHolder, callListCommand)); } static void invalidateCache(String type) { if (DEBUG) { Log.i(TAG, "Cache invalidated for type:" + type); } IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, type); } }
packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +56 −25 File changed.Preview size limit exceeded, changes collapsed. Show changes