chromium/components/content_capture/android/java/src/org/chromium/components/content_capture/PlatformContentCaptureController.java

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.content_capture;

import android.content.ComponentName;
import android.content.Context;
import android.content.LocusId;
import android.os.Build;
import android.view.contentcapture.ContentCaptureCondition;
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.DataRemovalRequest;

import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;

import org.chromium.base.BuildInfo;
import org.chromium.base.Log;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * The class talks to the ContentCaptureManager to verify ContentCaptureService is Aiai and provide
 * the methods to check if the given urls shall be captured and delete the ContentCapture history.
 */
@RequiresApi(Build.VERSION_CODES.Q)
public class PlatformContentCaptureController {
    private static final String TAG = "ContentCapture";
    private static final String AIAI_PACKAGE_NAME = "com.google.android.as";

    private static PlatformContentCaptureController sContentCaptureController;

    private boolean mShouldStartCapture;
    private boolean mIsAiai;
    private UrlAllowlist mAllowlist;
    private ContentCaptureManager mContentCaptureManager;

    public static void init(Context context) {
        sContentCaptureController = new PlatformContentCaptureController(context);
    }

    public static PlatformContentCaptureController getInstance() {
        return sContentCaptureController;
    }

    public PlatformContentCaptureController(Context context) {
        mContentCaptureManager = context.getSystemService(ContentCaptureManager.class);
        verifyService();
        pullAllowlist();
    }

    public boolean shouldStartCapture() {
        return mShouldStartCapture;
    }

    private void verifyService() {
        if (mContentCaptureManager == null) {
            log("ContentCaptureManager isn't available.");
            return;
        }

        ComponentName componentName = null;
        try {
            componentName = mContentCaptureManager.getServiceComponentName();
        } catch (RuntimeException e) {
            Log.e(TAG, "Error to get component name", e);
        }
        if (componentName == null) {
            log("Service isn't available.");
            return;
        }

        mIsAiai = AIAI_PACKAGE_NAME.equals(componentName.getPackageName());
        if (!mIsAiai) {
            log(
                    "Package doesn't match, current one is "
                            + mContentCaptureManager.getServiceComponentName().getPackageName());
            // Disable the ContentCapture if there is no testing flag.
            if (!BuildInfo.isDebugAndroid() && !ContentCaptureFeatures.isDumpForTestingEnabled()) {
                return;
            }
        }

        mShouldStartCapture = mContentCaptureManager.isContentCaptureEnabled();
        if (!mShouldStartCapture) {
            log("ContentCapture disabled.");
        }
    }

    private void pullAllowlist() {
        if (mContentCaptureManager == null) {
            // Nothing shall be captured.
            mAllowlist = new UrlAllowlist(null, null);
            return;
        }
        Set<ContentCaptureCondition> conditions =
                mContentCaptureManager.getContentCaptureConditions();
        if (conditions == null) return;

        HashSet<String> allowedUrls = null;
        ArrayList<Pattern> allowedRe = null;
        for (ContentCaptureCondition c : conditions) {
            if ((c.getFlags() & ContentCaptureCondition.FLAG_IS_REGEX) != 0) {
                if (allowedRe == null) allowedRe = new ArrayList<Pattern>();
                allowedRe.add(Pattern.compile(c.getLocusId().getId()));
            } else {
                if (allowedUrls == null) allowedUrls = new HashSet<String>();
                allowedUrls.add(c.getLocusId().getId());
            }
        }
        mAllowlist = new UrlAllowlist(allowedUrls, allowedRe);
    }

    private void log(String msg) {
        if (!ContentCaptureFeatures.isDumpForTestingEnabled()) return;
        Log.i(TAG, msg);
    }

    public void clearAllContentCaptureData() {
        if (mContentCaptureManager == null) return;

        mContentCaptureManager.removeData(new DataRemovalRequest.Builder().forEverything().build());
    }

    public void clearContentCaptureDataForURLs(String[] urlsToDelete) {
        if (mContentCaptureManager == null) return;

        DataRemovalRequest.Builder builder = new DataRemovalRequest.Builder();
        for (String url : urlsToDelete) {
            builder =
                    builder.addLocusId(
                            new LocusId(url), /* Signals that we aren't using extra flags */ 0);
        }
        mContentCaptureManager.removeData(builder.build());
    }

    /** @return  @return if any of the given allows to be captured. */
    public boolean shouldCapture(String[] urls) {
        if (mAllowlist == null) return true;
        return mAllowlist.isAllowed(urls);
    }

    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
    public boolean isAiai() {
        return mIsAiai;
    }
}