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

Commit 14e1cffe authored by Nino Jagar's avatar Nino Jagar
Browse files

Skeleton for content protection allowlist manager

Bug: 302188278
Test: Unit tests and end-to-end from a larger commit
Change-Id: I1767b91204ebe76486f7f15c125110943a924ebe
parent e619e972
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.