Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bd062f7b authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Revert "[SettingsProvider] IpcDataCache for Settings""

parents b42d1e20 ccc86168
Loading
Loading
Loading
Loading
+16 −123
Original line number Diff line number Diff line
@@ -73,7 +73,6 @@ 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;
@@ -126,9 +125,6 @@ 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
    /**
@@ -2924,8 +2920,8 @@ public final class Settings {
    public static final String AUTHORITY = "settings";
    static final String TAG = "Settings";
    static final boolean LOCAL_LOGV = false;
    private static final String TAG = "Settings";
    private static final boolean LOCAL_LOGV = false;
    // Used in system server calling uid workaround in call()
    private static boolean sInSystemServer = false;
@@ -3035,10 +3031,10 @@ public final class Settings {
        }
    }
    static final class ContentProviderHolder {
    private static final class ContentProviderHolder {
        private final Object mLock = new Object();
        final Uri mUri;
        private final Uri mUri;
        @GuardedBy("mLock")
        @UnsupportedAppUsage
        private IContentProvider mContentProvider;
@@ -3065,14 +3061,14 @@ public final class Settings {
    }
    // Thread-safe.
    static class NameValueCache {
    private static class NameValueCache {
        private static final boolean DEBUG = false;
        static final String[] SELECT_VALUE_PROJECTION = new String[] {
        private static final String[] SELECT_VALUE_PROJECTION = new String[] {
                Settings.NameValueTable.VALUE
        };
        static final String NAME_EQ_PLACEHOLDER = "name=?";
        private static final String NAME_EQ_PLACEHOLDER = "name=?";
        // Must synchronize on 'this' to access mValues and mValuesVersion.
        private final ArrayMap<String, String> mValues = new ArrayMap<>();
@@ -3093,14 +3089,6 @@ 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;
@@ -3126,11 +3114,6 @@ 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,
@@ -3229,30 +3212,6 @@ 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) {
@@ -3449,38 +3408,6 @@ 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;
@@ -3520,7 +3447,10 @@ public final class Settings {
                }
            }
            if (mCallListCommand == null) {
                // No list command specified, return empty map
                return keyValues;
            }
            IContentProvider cp = mProviderHolder.getProvider(cr);
            try {
@@ -3626,13 +3556,7 @@ public final class Settings {
            }
        }
        public void clearCachesForTest() {
            if (IPC_DATA_CACHE_ENABLED) {
                mValueCache.clear();
                mNamespaceCache.clear();
                return;
            }
            // Fall back to old cache mechanism
        public void clearGenerationTrackerForTest() {
            synchronized (NameValueCache.this) {
                if (mGenerationTracker != null) {
                    mGenerationTracker.destroy();
@@ -3641,12 +3565,6 @@ public final class Settings {
                mGenerationTracker = null;
            }
        }
        public void invalidateCache() {
            if (IPC_DATA_CACHE_ENABLED) {
                SettingsIpcDataCache.invalidateCache(mSettingsType);
            }
        }
    }
    /**
@@ -3923,12 +3841,7 @@ public final class Settings {
        /** @hide */
        public static void clearProviderForTest() {
            sProviderHolder.clearProviderForTest();
            sNameValueCache.clearCachesForTest();
        }
        /** @hide */
        public static void invalidateValueCache() {
            sNameValueCache.invalidateCache();
            sNameValueCache.clearGenerationTrackerForTest();
        }
        /** @hide */
@@ -6408,12 +6321,7 @@ public final class Settings {
        /** @hide */
        public static void clearProviderForTest() {
            sProviderHolder.clearProviderForTest();
            sNameValueCache.clearCachesForTest();
        }
        /** @hide */
        public static void invalidateValueCache() {
            sNameValueCache.invalidateCache();
            sNameValueCache.clearGenerationTrackerForTest();
        }
        /** @hide */
@@ -16700,12 +16608,7 @@ public final class Settings {
        /** @hide */
        public static void clearProviderForTest() {
            sProviderHolder.clearProviderForTest();
            sNameValueCache.clearCachesForTest();
        }
        /** @hide */
        public static void invalidateValueCache() {
            sNameValueCache.invalidateCache();
            sNameValueCache.clearGenerationTrackerForTest();
        }
        /** @hide */
@@ -19077,17 +18980,7 @@ public final class Settings {
        /** @hide */
        public static void clearProviderForTest() {
            sProviderHolder.clearProviderForTest();
            sNameValueCache.clearCachesForTest();
        }
        /** @hide */
        public static void invalidateValueCache() {
            sNameValueCache.invalidateCache();
        }
        /** @hide */
        public static void invalidateNamespaceCache() {
            sNameValueCache.invalidateCache();
            sNameValueCache.clearGenerationTrackerForTest();
        }
        private static void handleMonitorCallback(
+0 −315
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);
    }
}
+25 −56

File changed.

Preview size limit exceeded, changes collapsed.