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

Commit 2fa38077 authored by Nino Jagar's avatar Nino Jagar Committed by Android (Google) Code Review
Browse files

Merge "Skeleton for content protection allowlist manager" into main

parents d5873fc5 14e1cffe
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -395,6 +395,22 @@ public final class ContentCaptureManager {
    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD =
            "content_protection_optional_groups_threshold";

    /**
     * Sets the initial delay for fetching content protection allowlist in milliseconds.
     *
     * @hide
     */
    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS =
            "content_protection_allowlist_delay_ms";

    /**
     * Sets the timeout for fetching content protection allowlist in milliseconds.
     *
     * @hide
     */
    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS =
            "content_protection_allowlist_timeout_ms";

    /** @hide */
    @TestApi
    public static final int LOGGING_LEVEL_OFF = 0;
@@ -445,6 +461,10 @@ public final class ContentCaptureManager {
    public static final String DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = "";
    /** @hide */
    public static final int DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = 0;
    /** @hide */
    public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS = 30000;
    /** @hide */
    public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS = 250;

    private final Object mLock = new Object();

+122 −57
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import static android.Manifest.permission.MANAGE_CONTENT_CAPTURE;
import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE;
import static android.service.contentcapture.ContentCaptureService.setClientState;
import static android.view.contentcapture.ContentCaptureHelper.toList;
import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS;
import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS;
import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG;
import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD;
import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG;
@@ -219,6 +221,12 @@ public class ContentCaptureManagerService extends
    @GuardedBy("mLock")
    int mDevCfgContentProtectionOptionalGroupsThreshold;

    @GuardedBy("mLock")
    long mDevCfgContentProtectionAllowlistDelayMs;

    @GuardedBy("mLock")
    long mDevCfgContentProtectionAllowlistTimeoutMs;

    private final Executor mDataShareExecutor = Executors.newCachedThreadPool();
    private final Handler mHandler = new Handler(Looper.getMainLooper());

@@ -231,11 +239,17 @@ public class ContentCaptureManagerService extends
    final GlobalContentCaptureOptions mGlobalContentCaptureOptions =
            new GlobalContentCaptureOptions();

    @Nullable private final ComponentName mContentProtectionServiceComponentName;
    @GuardedBy("mLock")
    @Nullable
    private ComponentName mContentProtectionServiceComponentName;

    @Nullable private final ContentProtectionAllowlistManager mContentProtectionAllowlistManager;
    @GuardedBy("mLock")
    @Nullable
    private ContentProtectionAllowlistManager mContentProtectionAllowlistManager;

    @Nullable private final ContentProtectionConsentManager mContentProtectionConsentManager;
    @GuardedBy("mLock")
    @Nullable
    private ContentProtectionConsentManager mContentProtectionConsentManager;

    public ContentCaptureManagerService(@NonNull Context context) {
        super(context, new FrameworkResourcesServiceNameResolver(context,
@@ -279,21 +293,6 @@ public class ContentCaptureManagerService extends
                    mServiceNameResolver.getServiceName(userId),
                    mServiceNameResolver.isTemporary(userId));
        }

        if (getEnableContentProtectionReceiverLocked()) {
            mContentProtectionServiceComponentName = getContentProtectionServiceComponentName();
            if (mContentProtectionServiceComponentName != null) {
                mContentProtectionAllowlistManager = createContentProtectionAllowlistManager();
                mContentProtectionConsentManager = createContentProtectionConsentManager();
            } else {
                mContentProtectionAllowlistManager = null;
                mContentProtectionConsentManager = null;
            }
        } else {
            mContentProtectionServiceComponentName = null;
            mContentProtectionAllowlistManager = null;
            mContentProtectionConsentManager = null;
        }
    }

    @Override // from AbstractMasterSystemService
@@ -442,6 +441,8 @@ public class ContentCaptureManagerService extends
                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG:
                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG:
                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD:
                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS:
                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS:
                    setFineTuneParamsFromDeviceConfig();
                    return;
                default:
@@ -453,8 +454,15 @@ public class ContentCaptureManagerService extends
    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    protected void setFineTuneParamsFromDeviceConfig() {
        boolean enableContentProtectionReceiverOld;
        boolean enableContentProtectionReceiverNew;
        String contentProtectionRequiredGroupsConfig;
        String contentProtectionOptionalGroupsConfig;
        int contentProtectionOptionalGroupsThreshold;
        long contentProtectionAllowlistDelayMs;
        long contentProtectionAllowlistTimeoutMs;
        ContentProtectionAllowlistManager contentProtectionAllowlistManagerOld;

        synchronized (mLock) {
            mDevCfgMaxBufferSize =
                    DeviceConfig.getInt(
@@ -488,12 +496,9 @@ public class ContentCaptureManagerService extends
                            ContentCaptureManager
                                    .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
                            false);
            mDevCfgEnableContentProtectionReceiver =
                    DeviceConfig.getBoolean(
                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                            ContentCaptureManager
                                    .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
                            ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);

            enableContentProtectionReceiverOld = mDevCfgEnableContentProtectionReceiver;
            enableContentProtectionReceiverNew = getDeviceConfigEnableContentProtectionReceiver();
            mDevCfgContentProtectionBufferSize =
                    DeviceConfig.getInt(
                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
@@ -512,12 +517,25 @@ public class ContentCaptureManagerService extends
                            DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG,
                            ContentCaptureManager
                                    .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG);
            mDevCfgContentProtectionOptionalGroupsThreshold =
            contentProtectionOptionalGroupsThreshold =
                    DeviceConfig.getInt(
                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                            DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD,
                            ContentCaptureManager
                                    .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD);
            contentProtectionAllowlistDelayMs =
                    DeviceConfig.getLong(
                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                            DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS,
                            ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS);
            contentProtectionAllowlistTimeoutMs =
                    DeviceConfig.getLong(
                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                            DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS,
                            ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS);

            contentProtectionAllowlistManagerOld = mContentProtectionAllowlistManager;

            if (verbose) {
                Slog.v(
                        TAG,
@@ -535,7 +553,7 @@ public class ContentCaptureManagerService extends
                                + ", disableFlushForViewTreeAppearing="
                                + mDevCfgDisableFlushForViewTreeAppearing
                                + ", enableContentProtectionReceiver="
                                + mDevCfgEnableContentProtectionReceiver
                                + enableContentProtectionReceiverNew
                                + ", contentProtectionBufferSize="
                                + mDevCfgContentProtectionBufferSize
                                + ", contentProtectionRequiredGroupsConfig="
@@ -543,7 +561,11 @@ public class ContentCaptureManagerService extends
                                + ", contentProtectionOptionalGroupsConfig="
                                + contentProtectionOptionalGroupsConfig
                                + ", contentProtectionOptionalGroupsThreshold="
                                + mDevCfgContentProtectionOptionalGroupsThreshold);
                                + contentProtectionOptionalGroupsThreshold
                                + ", contentProtectionAllowlistDelayMs="
                                + contentProtectionAllowlistDelayMs
                                + ", contentProtectionAllowlistTimeoutMs="
                                + contentProtectionAllowlistTimeoutMs);
            }
        }

@@ -551,9 +573,37 @@ public class ContentCaptureManagerService extends
                parseContentProtectionGroupsConfig(contentProtectionRequiredGroupsConfig);
        List<List<String>> contentProtectionOptionalGroups =
                parseContentProtectionGroupsConfig(contentProtectionOptionalGroupsConfig);
        ComponentName contentProtectionServiceComponentNameNew = null;
        ContentProtectionAllowlistManager contentProtectionAllowlistManagerNew = null;
        ContentProtectionConsentManager contentProtectionConsentManagerNew = null;

        if (contentProtectionAllowlistManagerOld != null && !enableContentProtectionReceiverNew) {
            contentProtectionAllowlistManagerOld.stop();
        }
        if (!enableContentProtectionReceiverOld && enableContentProtectionReceiverNew) {
            contentProtectionServiceComponentNameNew = getContentProtectionServiceComponentName();
            if (contentProtectionServiceComponentNameNew != null) {
                contentProtectionAllowlistManagerNew =
                        createContentProtectionAllowlistManager(
                                contentProtectionAllowlistTimeoutMs);
                contentProtectionAllowlistManagerNew.start(contentProtectionAllowlistDelayMs);
                contentProtectionConsentManagerNew = createContentProtectionConsentManager();
            }
        }

        synchronized (mLock) {
            mDevCfgEnableContentProtectionReceiver = enableContentProtectionReceiverNew;
            mDevCfgContentProtectionRequiredGroups = contentProtectionRequiredGroups;
            mDevCfgContentProtectionOptionalGroups = contentProtectionOptionalGroups;
            mDevCfgContentProtectionOptionalGroupsThreshold =
                    contentProtectionOptionalGroupsThreshold;
            mDevCfgContentProtectionAllowlistDelayMs = contentProtectionAllowlistDelayMs;

            if (enableContentProtectionReceiverOld ^ enableContentProtectionReceiverNew) {
                mContentProtectionServiceComponentName = contentProtectionServiceComponentNameNew;
                mContentProtectionAllowlistManager = contentProtectionAllowlistManagerNew;
                mContentProtectionConsentManager = contentProtectionConsentManagerNew;
            }
        }
    }

@@ -837,27 +887,34 @@ public class ContentCaptureManagerService extends
        pw.print(prefix2);
        pw.print("contentProtectionOptionalGroupsThreshold: ");
        pw.println(mDevCfgContentProtectionOptionalGroupsThreshold);
        pw.print(prefix2);
        pw.print("contentProtectionAllowlistDelayMs: ");
        pw.println(mDevCfgContentProtectionAllowlistDelayMs);
        pw.print(prefix2);
        pw.print("contentProtectionAllowlistTimeoutMs: ");
        pw.println(mDevCfgContentProtectionAllowlistTimeoutMs);
        pw.print(prefix);
        pw.println("Global Options:");
        mGlobalContentCaptureOptions.dump(prefix2, pw);
    }

    /**
     * Used by the constructor in order to be able to override the value in the tests.
     *
     * @hide
     */
    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @GuardedBy("mLock")
    protected boolean getEnableContentProtectionReceiverLocked() {
        return mDevCfgEnableContentProtectionReceiver;
    protected boolean getDeviceConfigEnableContentProtectionReceiver() {
        return DeviceConfig.getBoolean(
                DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
                ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);
    }

    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @NonNull
    protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager() {
        return new ContentProtectionAllowlistManager();
    protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager(
            long timeoutMs) {
        // Same handler as used by AbstractMasterSystemService
        return new ContentProtectionAllowlistManager(
                this, BackgroundThread.getHandler(), timeoutMs);
    }

    /** @hide */
@@ -874,6 +931,9 @@ public class ContentCaptureManagerService extends
    @Nullable
    private ComponentName getContentProtectionServiceComponentName() {
        String flatComponentName = getContentProtectionServiceFlatComponentName();
        if (flatComponentName == null) {
            return null;
        }
        return ComponentName.unflattenFromString(flatComponentName);
    }

@@ -898,27 +958,27 @@ public class ContentCaptureManagerService extends
                getContext(), componentName, /* isTemp= */ false, UserHandle.getCallingUserId());
    }

    /** @hide */
    @Nullable
    private RemoteContentProtectionService createRemoteContentProtectionService() {
        if (mContentProtectionServiceComponentName == null) {
            // This case should not be possible but make sure
            return null;
        }
    public RemoteContentProtectionService createRemoteContentProtectionService() {
        ComponentName componentName;
        synchronized (mLock) {
            if (!mDevCfgEnableContentProtectionReceiver) {
            if (!mDevCfgEnableContentProtectionReceiver
                    || mContentProtectionServiceComponentName == null) {
                return null;
            }
            componentName = mContentProtectionServiceComponentName;
        }

        // Check permissions by trying to construct {@link ContentCaptureServiceInfo}
        try {
            createContentProtectionServiceInfo(mContentProtectionServiceComponentName);
            createContentProtectionServiceInfo(componentName);
        } catch (Exception ex) {
            // Swallow, exception was already logged
            return null;
        }

        return createRemoteContentProtectionService(mContentProtectionServiceComponentName);
        return createRemoteContentProtectionService(componentName);
    }

    /** @hide */
@@ -976,6 +1036,16 @@ public class ContentCaptureManagerService extends
                .toList();
    }

    @GuardedBy("mLock")
    private boolean isContentProtectionEnabledLocked() {
        return mDevCfgEnableContentProtectionReceiver
                && mContentProtectionServiceComponentName != null
                && mContentProtectionAllowlistManager != null
                && mContentProtectionConsentManager != null
                && !(mDevCfgContentProtectionRequiredGroups.isEmpty()
                        && mDevCfgContentProtectionOptionalGroups.isEmpty());
    }

    final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {

        @Override
@@ -1406,22 +1476,17 @@ public class ContentCaptureManagerService extends

        private boolean isContentProtectionReceiverEnabled(
                @UserIdInt int userId, @NonNull String packageName) {
            if (mContentProtectionServiceComponentName == null
                    || mContentProtectionAllowlistManager == null
                    || mContentProtectionConsentManager == null) {
                return false;
            }
            ContentProtectionConsentManager consentManager;
            ContentProtectionAllowlistManager allowlistManager;
            synchronized (mLock) {
                if (!mDevCfgEnableContentProtectionReceiver) {
                    return false;
                }
                if (mDevCfgContentProtectionRequiredGroups.isEmpty()
                        && mDevCfgContentProtectionOptionalGroups.isEmpty()) {
                if (!isContentProtectionEnabledLocked()) {
                    return false;
                }
                consentManager = mContentProtectionConsentManager;
                allowlistManager = mContentProtectionAllowlistManager;
            }
            return mContentProtectionConsentManager.isConsentGranted(userId)
                    && mContentProtectionAllowlistManager.isAllowed(packageName);
            return consentManager.isConsentGranted(userId)
                    && allowlistManager.isAllowed(packageName);
        }
    }

+131 −4
Original line number Diff line number Diff line
@@ -16,8 +16,22 @@

package com.android.server.contentprotection;

import static android.view.contentprotection.flags.Flags.blocklistUpdateEnabled;

import android.annotation.NonNull;
import android.util.Slog;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.UserHandle;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.server.contentcapture.ContentCaptureManagerService;

import java.time.Instant;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Manages whether the content protection is enabled for an app using a allowlist.
@@ -28,11 +42,124 @@ public class ContentProtectionAllowlistManager {

    private static final String TAG = "ContentProtectionAllowlistManager";

    public ContentProtectionAllowlistManager() {}
    @NonNull private final ContentCaptureManagerService mContentCaptureManagerService;

    @NonNull private final Handler mHandler;

    private final long mTimeoutMs;

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @NonNull
    final PackageMonitor mPackageMonitor;

    private final Object mHandlerToken = new Object();

    private final Object mLock = new Object();

    // Used outside of the handler
    private boolean mStarted;

    // Used inside the handler
    @Nullable private Instant mUpdatePendingUntil;

    @NonNull
    @GuardedBy("mLock")
    private Set<String> mAllowedPackages = Set.of();

    public ContentProtectionAllowlistManager(
            @NonNull ContentCaptureManagerService contentCaptureManagerService,
            @NonNull Handler handler,
            long timeoutMs) {
        mContentCaptureManagerService = contentCaptureManagerService;
        mHandler = handler;
        mTimeoutMs = timeoutMs;
        mPackageMonitor = createPackageMonitor();
    }

    /** Starts the manager. */
    public void start(long delayMs) {
        if (mStarted) {
            return;
        }
        mStarted = true;
        mHandler.postDelayed(this::handleInitialUpdate, mHandlerToken, delayMs);
        // PackageMonitor will be registered inside handleInitialUpdate to respect the initial delay
    }

    /** Stops the manager. */
    public void stop() {
        try {
            mPackageMonitor.unregister();
        } catch (IllegalStateException ex) {
            // Swallow, throws if not registered
        }
        mHandler.removeCallbacksAndMessages(mHandlerToken);
        mUpdatePendingUntil = null;
        mStarted = false;
    }

    /** Returns true if the package is allowed. */
    public boolean isAllowed(@NonNull String packageName) {
        Slog.v(TAG, packageName);
        return false;
        Set<String> allowedPackages;
        synchronized (mLock) {
            allowedPackages = mAllowedPackages;
        }
        return allowedPackages.contains(packageName);
    }

    private void setAllowlist(@NonNull List<String> packages) {
        synchronized (mLock) {
            mAllowedPackages = packages.stream().collect(Collectors.toUnmodifiableSet());
        }
        mUpdatePendingUntil = null;
    }

    private void handleInitialUpdate() {
        handleUpdate();

        // Initial update done, start listening to package updates now
        mPackageMonitor.register(
                mContentCaptureManagerService.getContext(), UserHandle.ALL, mHandler);
    }

    private void handleUpdate() {
        if (!blocklistUpdateEnabled()) {
            return;
        }

        /**
         * PackageMonitor callback can be invoked more than once in a matter of milliseconds on the
         * same monitor instance for the same package (eg: b/295969873). This check acts both as a
         * simple generic rate limit and as a mitigation for this quirk.
         */
        if (mUpdatePendingUntil != null && Instant.now().isBefore(mUpdatePendingUntil)) {
            return;
        }

        RemoteContentProtectionService remoteContentProtectionService =
                mContentCaptureManagerService.createRemoteContentProtectionService();
        if (remoteContentProtectionService == null) {
            return;
        }

        // If there are any pending updates queued already, they can be removed immediately
        mHandler.removeCallbacksAndMessages(mHandlerToken);
        mUpdatePendingUntil = Instant.now().plusMillis(mTimeoutMs);
    }

    /** @hide */
    @NonNull
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    protected PackageMonitor createPackageMonitor() {
        return new ContentProtectionPackageMonitor();
    }

    private final class ContentProtectionPackageMonitor extends PackageMonitor {

        // This callback might be invoked multiple times, for more info refer to the comment above
        @Override
        public void onSomePackagesChanged() {
            handleUpdate();
        }
    }
}
+131 −49

File changed.

Preview size limit exceeded, changes collapsed.

+325 −1

File changed.

Preview size limit exceeded, changes collapsed.