/*
 * Copyright (C) 2021 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 com.android.server.appsearch.visibilitystore;

import static android.Manifest.permission.EXECUTE_APP_FUNCTIONS;
import static android.Manifest.permission.PACKAGE_USAGE_STATS;
import static android.Manifest.permission.READ_ASSISTANT_APP_SEARCH_DATA;
import static android.Manifest.permission.READ_CALENDAR;
import static android.Manifest.permission.READ_CONTACTS;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_GLOBAL_APP_SEARCH_DATA;
import static android.Manifest.permission.READ_HOME_APP_SEARCH_DATA;
import static android.Manifest.permission.READ_SMS;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;

import static com.android.appsearch.flags.Flags.FLAG_ENABLE_APP_FUNCTION_AGENT_ALLOWLIST_CHECK;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;

import android.annotation.NonNull;
import android.app.UiAutomation;
import android.app.appsearch.InternalVisibilityConfig;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.SchemaVisibilityConfig;
import android.app.appsearch.SetSchemaRequest;
import android.app.appsearch.aidl.AppSearchAttributionSource;
import android.app.appsearch.testutil.AppSearchTestUtils;
import android.app.appsearch.testutil.FakeAppSearchConfig;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.ArrayMap;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.platform.app.InstrumentationRegistry;

import com.android.appsearch.flags.Flags;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
import com.android.server.appsearch.external.localstorage.OptimizeStrategy;
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class VisibilityCheckerImplTest {
    /**
     * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class.
     */
    private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true;

    // These constants are hidden in SetSchemaRequest
    private static final int ENTERPRISE_ACCESS = 7;
    private static final int MANAGED_PROFILE_CONTACTS_ACCESS = 8;
    private static final int SET_SCHEMA_REQUEST_EXECUTE_APP_FUNCTIONS = 9;
    private static final int SET_SCHEMA_REQUEST_EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
    private static final int SET_SCHEMA_REQUEST_PACKAGE_USAGE_STATS = 11;

    @Rule public final RuleChain mRuleChain = AppSearchTestUtils.createCommonTestRules();

    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
    private final Map<UserHandle, PackageManager> mMockPackageManagers = new ArrayMap<>();
    private Context mContext;
    private VisibilityCheckerImpl mVisibilityChecker;
    private VisibilityStore mVisibilityStore;
    private AppSearchAttributionSource mAttributionSource;
    private UiAutomation mUiAutomation;

    @Before
    public void setUp() throws Exception {
        Context context = ApplicationProvider.getApplicationContext();
        mAttributionSource =
                AppSearchAttributionSource.createAttributionSource(context, /* callingPid= */ 1);
        mContext =
                new ContextWrapper(context) {
                    @Override
                    public Context createContextAsUser(UserHandle user, int flags) {
                        return new ContextWrapper(super.createContextAsUser(user, flags)) {
                            @Override
                            public PackageManager getPackageManager() {
                                return getMockPackageManager(user);
                            }
                        };
                    }

                    @Override
                    public PackageManager getPackageManager() {
                        return createContextAsUser(getUser(), /* flags= */ 0).getPackageManager();
                    }
                };
        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
        mVisibilityChecker = Mockito.spy(new VisibilityCheckerImpl(mContext));
        // Give ourselves global query permissions
        AppSearchImpl appSearchImpl =
                AppSearchImpl.create(
                        mTemporaryFolder.newFolder(),
                        new FakeAppSearchConfig(),
                        /* initStatsBuilder= */ null,
                        /* callStatsBuilder= */ null,
                        mVisibilityChecker,
                        /* revocableFileDescriptorStore= */ null,
                        /* icingSearchEngine= */ null,
                        ALWAYS_OPTIMIZE);
        mVisibilityStore =
                VisibilityStore.createDocumentVisibilityStore(
                        appSearchImpl, /* callStatsBuilder= */ null);
    }

    @Test
    public void testDoesCallerHaveSystemAccess() {
        PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
        when(mockPackageManager.checkPermission(
                        READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName()))
                .thenReturn(PERMISSION_GRANTED);
        assertThat(mVisibilityChecker.doesCallerHaveSystemAccess(mContext.getPackageName()))
                .isTrue();

        when(mockPackageManager.checkPermission(
                        READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName()))
                .thenReturn(PERMISSION_DENIED);
        assertThat(mVisibilityChecker.doesCallerHaveSystemAccess(mContext.getPackageName()))
                .isFalse();
    }

    @Test
    public void testSetVisibility_displayedBySystem() throws Exception {
        // Create two InternalVisibilityConfig that are not displayed by system.
        InternalVisibilityConfig visibilityConfig1 =
                new InternalVisibilityConfig.Builder(/* id= */ "prefix/Schema1")
                        .setNotDisplayedBySystem(true)
                        .build();
        InternalVisibilityConfig visibilityConfig2 =
                new InternalVisibilityConfig.Builder(/* id= */ "prefix/Schema2")
                        .setNotDisplayedBySystem(true)
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig1, visibilityConfig2),
                /* callStatsBuilder= */ null);

        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ true,
                                        /* isForEnterprise= */ false),
                                "package",
                                "prefix/Schema1",
                                mVisibilityStore))
                .isFalse();
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ true,
                                        /* isForEnterprise= */ false),
                                "package",
                                "prefix/Schema2",
                                mVisibilityStore))
                .isFalse();

        // Rewrite Visibility Document 1 to let it accessible to the system.
        visibilityConfig1 =
                new InternalVisibilityConfig.Builder(/* id= */ "prefix/Schema1").build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig1, visibilityConfig2),
                /* callStatsBuilder= */ null);
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ true,
                                        /* isForEnterprise= */ false),
                                "package",
                                "prefix/Schema1",
                                mVisibilityStore))
                .isTrue();
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ true,
                                        /* isForEnterprise= */ false),
                                "package",
                                "prefix/Schema2",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    public void testSetVisibility_visibleToPackages() throws Exception {
        // Values for a "foo" client
        String packageNameFoo = "packageFoo";
        byte[] sha256CertFoo = new byte[32];
        int uidFoo = 1;
        int pidFoo = 1;

        // Values for a "bar" client
        String packageNameBar = "packageBar";
        byte[] sha256CertBar = new byte[32];
        int uidBar = 2;
        int pidBar = 2;

        // Can't be the same value as uidFoo nor uidBar
        int uidNotFooOrBar = 3;

        // Grant package access
        InternalVisibilityConfig visibilityConfig1 =
                new InternalVisibilityConfig.Builder(/* id= */ "prefix/SchemaFoo")
                        .addVisibleToPackage(new PackageIdentifier(packageNameFoo, sha256CertFoo))
                        .build();
        InternalVisibilityConfig visibilityConfig2 =
                new InternalVisibilityConfig.Builder(/* id= */ "prefix/SchemaBar")
                        .addVisibleToPackage(new PackageIdentifier(packageNameBar, sha256CertBar))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig1, visibilityConfig2),
                /* callStatsBuilder= */ null);

        // Should fail if PackageManager doesn't see that it has the proper certificate
        PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
        when(mockPackageManager.getPackageUid(eq(packageNameFoo), /* flags= */ anyInt()))
                .thenReturn(uidFoo);
        when(mockPackageManager.hasSigningCertificate(
                        packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(false);
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        new AppSearchAttributionSource(
                                                packageNameFoo, uidFoo, pidFoo),
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                "prefix/SchemaFoo",
                                mVisibilityStore))
                .isFalse();

        // Should fail if PackageManager doesn't think the package belongs to the uid
        when(mockPackageManager.getPackageUid(eq(packageNameFoo), /* flags= */ anyInt()))
                .thenReturn(uidNotFooOrBar);
        when(mockPackageManager.hasSigningCertificate(
                        packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        new AppSearchAttributionSource(
                                                packageNameFoo, uidFoo, pidFoo),
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                "prefix/SchemaFoo",
                                mVisibilityStore))
                .isFalse();

        // But if uid and certificate match, then we should have access
        when(mockPackageManager.getPackageUid(eq(packageNameFoo), /* flags= */ anyInt()))
                .thenReturn(uidFoo);
        when(mockPackageManager.hasSigningCertificate(
                        packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        new AppSearchAttributionSource(
                                                packageNameFoo, uidFoo, pidFoo),
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                "prefix/SchemaFoo",
                                mVisibilityStore))
                .isTrue();

        when(mockPackageManager.getPackageUid(eq(packageNameBar), /* flags= */ anyInt()))
                .thenReturn(uidBar);
        when(mockPackageManager.hasSigningCertificate(
                        packageNameBar, sha256CertBar, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        new AppSearchAttributionSource(
                                                packageNameBar, uidBar, pidBar),
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                "prefix/SchemaBar",
                                mVisibilityStore))
                .isTrue();

        // Save default document and, then we shouldn't have access
        visibilityConfig1 =
                new InternalVisibilityConfig.Builder(/* id= */ "prefix/SchemaFoo").build();
        visibilityConfig2 =
                new InternalVisibilityConfig.Builder(/* id= */ "prefix/SchemaBar").build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig1, visibilityConfig2),
                /* callStatsBuilder= */ null);
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        new AppSearchAttributionSource(
                                                packageNameFoo, uidFoo, pidBar),
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                "prefix/SchemaFoo",
                                mVisibilityStore))
                .isFalse();
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        new AppSearchAttributionSource(
                                                packageNameBar, uidBar, pidBar),
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                "prefix/SchemaBar",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    public void testIsSchemaSearchableByCaller_noVisibilityConfig_defaultPlatformVisible() {
        String prefix = PrefixUtil.createPrefix("package", "database");
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ true,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isTrue();
    }

    @Test
    public void testIsSchemaSearchableByCaller_packageAccessibilityHandlesNameNotFoundException()
            throws Exception {
        // Values for a "foo" client
        String packageNameFoo = "packageFoo";
        byte[] sha256CertFoo = new byte[] {10};

        // Pretend we can't find the Foo package.
        PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
        when(mockPackageManager.getPackageUid(eq(packageNameFoo), /* flags= */ anyInt()))
                .thenThrow(new PackageManager.NameNotFoundException());

        InternalVisibilityConfig visibilityConfig1 =
                new InternalVisibilityConfig.Builder(/* id= */ "prefix/SchemaFoo")
                        .addVisibleToPackage(new PackageIdentifier(packageNameFoo, sha256CertFoo))
                        .build();
        // Grant package access
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig1), /* callStatsBuilder= */ null);

        // If we can't verify the Foo package that has access, assume it doesn't have access.
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                "prefix/SchemaFoo",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    public void testEmptyPrefix() throws Exception {
        // Values for a "foo" client
        String packageNameFoo = "packageFoo";
        byte[] sha256CertFoo = new byte[] {10};

        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ "$/Schema")
                        .addVisibleToPackage(new PackageIdentifier(packageNameFoo, sha256CertFoo))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // is accessible for caller who has system access.
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ true,
                                        /* isForEnterprise= */ false),
                                /* packageName= */ "",
                                "$/Schema",
                                mVisibilityStore))
                .isTrue();

        PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
        when(mockPackageManager.getPackageUid(eq(packageNameFoo), /* flags= */ anyInt()))
                .thenReturn(mAttributionSource.getUid());
        when(mockPackageManager.hasSigningCertificate(
                        packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);
        // is accessible for caller who in the allow package list.
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                /* packageName= */ "",
                                "$/Schema",
                                mVisibilityStore))
                .isTrue();
    }

    @Test
    public void testSetSchema_defaultPlatformVisible() throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema").build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ true,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isTrue();
    }

    @Test
    public void testSetSchema_enterpriseNotPlatformVisible() throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema").build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ true,
                                        /* shouldCheckEnterpriseAccess= */ true),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    public void testSetSchema_platformHidden() throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .setNotDisplayedBySystem(true)
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ true,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    public void testSetSchema_defaultNotVisibleToPackages() throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema").build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    public void testSetSchema_visibleToPackages() throws Exception {
        // Values for a "foo" client
        String packageNameFoo = "packageFoo";
        byte[] sha256CertFoo = new byte[] {10};

        // Make sure foo package will pass package manager checks.
        PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
        when(mockPackageManager.getPackageUid(eq(packageNameFoo), /* flags= */ anyInt()))
                .thenReturn(mAttributionSource.getUid());
        when(mockPackageManager.hasSigningCertificate(
                        packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);

        String prefix = PrefixUtil.createPrefix("package", "database");

        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPackage(new PackageIdentifier(packageNameFoo, sha256CertFoo))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isTrue();
    }

    @Test
    public void testSetSchema_enterpriseNotVisibleToPackages() throws Exception {
        // Values for a "foo" client
        String packageNameFoo = "packageFoo";
        byte[] sha256CertFoo = new byte[] {10};

        // Make sure foo package will pass package manager checks.
        PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
        when(mockPackageManager.getPackageUid(eq(packageNameFoo), /* flags= */ anyInt()))
                .thenReturn(mAttributionSource.getUid());
        when(mockPackageManager.hasSigningCertificate(
                        packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);

        String prefix = PrefixUtil.createPrefix("package", "database");

        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPackage(new PackageIdentifier(packageNameFoo, sha256CertFoo))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* shouldCheckEnterpriseAccess= */ true),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    public void testSetSchema_visibleToPermissions() throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc that require READ_SMS permission only.
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPermissions(ImmutableSet.of(SetSchemaRequest.READ_SMS))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // Grant the READ_SMS permission, we should able to access.
        mUiAutomation.adoptShellPermissionIdentity(READ_SMS);
        try {
            assertThat(
                            mVisibilityChecker.isSchemaSearchableByCaller(
                                    new FrameworkCallerAccess(
                                            mAttributionSource,
                                            /* callerHasSystemAccess= */ false,
                                            /* isForEnterprise= */ false),
                                    "package",
                                    prefix + "Schema",
                                    mVisibilityStore))
                    .isTrue();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }
        // Drop the READ_SMS permission, it becomes invisible.
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    @RequiresFlagsDisabled(FLAG_ENABLE_APP_FUNCTION_AGENT_ALLOWLIST_CHECK)
    public void testSetSchema_visibleToAppFunctionsPermissions() throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc that require either EXECUTE_APP_FUNCTIONS permissions only.
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPermissions(
                                ImmutableSet.of(SET_SCHEMA_REQUEST_EXECUTE_APP_FUNCTIONS))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // Grant the EXECUTE_APP_FUNCTIONS permission, we should able to access.
        doReturn(true)
                .when(mVisibilityChecker)
                .checkPermissionForDataDeliveryGranted(eq(EXECUTE_APP_FUNCTIONS), any(), any());
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isTrue();
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isTrue();
        // Ungrant the EXECUTE_APP_FUNCTIONS permsision. Can no longer access.
        doReturn(false)
                .when(mVisibilityChecker)
                .checkPermissionForDataDeliveryGranted(eq(EXECUTE_APP_FUNCTIONS), any(), any());
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    @RequiresFlagsEnabled(FLAG_ENABLE_APP_FUNCTION_AGENT_ALLOWLIST_CHECK)
    public void testSetSchema_visibleToAppFunctionsPermissions_validAgent() throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc that require either EXECUTE_APP_FUNCTIONS permissions only.
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPermissions(
                                ImmutableSet.of(SET_SCHEMA_REQUEST_EXECUTE_APP_FUNCTIONS))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // Grant the EXECUTE_APP_FUNCTIONS permission, we should able to access.
        doReturn(true)
                .when(mVisibilityChecker)
                .checkPermissionForDataDeliveryGranted(eq(EXECUTE_APP_FUNCTIONS), any(), any());
        // Make caller a valid agent
        doReturn(true)
                .when(mVisibilityChecker)
                .isValidAppFunctionAgent(eq(mAttributionSource.getPackageName()));
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isTrue();
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isTrue();

        // Make it invalid agent
        doReturn(false)
                .when(mVisibilityChecker)
                .isValidAppFunctionAgent(eq(mAttributionSource.getPackageName()));
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    public void testSetSchema_executeAppFunctionsTrusted() throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc that require either EXECUTE_APP_FUNCTIONS_TRUSTED permissions only.
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPermissions(
                                ImmutableSet.of(SET_SCHEMA_REQUEST_EXECUTE_APP_FUNCTIONS_TRUSTED))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // The permission is deprecated and so the check should always return false (without crash).
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_APP_OPEN_EVENT_INDEXER_ENABLED_V2)
    public void testSetSchema_visibleToPackageUsageStatsOnly() throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a doc that requires either PACKAGE_USAGE_STATS permissions only.
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPermissions(
                                ImmutableSet.of(SET_SCHEMA_REQUEST_PACKAGE_USAGE_STATS))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // Grant the PACKAGE_USAGE_STATS permission, we should able to access.
        doReturn(true)
                .when(mVisibilityChecker)
                .checkPermissionForDataDeliveryGranted(eq(PACKAGE_USAGE_STATS), any(), any());
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isTrue();

        // Drop the PACKAGE_USAGE_STATS permission, should not be accessible
        doReturn(false)
                .when(mVisibilityChecker)
                .checkPermissionForDataDeliveryGranted(eq(PACKAGE_USAGE_STATS), any(), any());
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    public void testSetSchema_enterpriseNotVisibleToPermissions_withoutEnterpriseAccessPermission()
            throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc that require READ_SMS permission only.
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPermissions(ImmutableSet.of(SetSchemaRequest.READ_SMS))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // Grant the READ_SMS permission, but won't be able to access.
        mUiAutomation.adoptShellPermissionIdentity(READ_SMS);
        try {
            assertThat(
                            mVisibilityChecker.isSchemaSearchableByCaller(
                                    new FrameworkCallerAccess(
                                            mAttributionSource,
                                            /* callerHasSystemAccess= */ false,
                                            /* shouldCheckEnterpriseAccess= */ true),
                                    "package",
                                    prefix + "Schema",
                                    mVisibilityStore))
                    .isFalse();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }
    }

    @Test
    public void testSetSchema_enterpriseVisibleToPermissions_withEnterpriseAccessPermission()
            throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc that requires READ_SMS and ENTERPRISE_ACCESS permission.
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPermissions(
                                ImmutableSet.of(SetSchemaRequest.READ_SMS, ENTERPRISE_ACCESS))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // Grant the READ_SMS permission, we should be able to access.
        mUiAutomation.adoptShellPermissionIdentity(READ_SMS);
        try {
            assertThat(
                            mVisibilityChecker.isSchemaSearchableByCaller(
                                    new FrameworkCallerAccess(
                                            mAttributionSource,
                                            /* callerHasSystemAccess= */ false,
                                            /* shouldCheckEnterpriseAccess= */ true),
                                    "package",
                                    prefix + "Schema",
                                    mVisibilityStore))
                    .isTrue();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }
        // Drop the READ_SMS permission, it becomes invisible.
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* shouldCheckEnterpriseAccess= */ true),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    public void testSetSchema_enterpriseVisibleToPermissions_managedProfileContactsAccess()
            throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc that requires ENTERPRISE_ACCESS and MANAGED_PROFILE_CONTACTS_ACCESS
        // permission.
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPermissions(
                                ImmutableSet.of(ENTERPRISE_ACCESS, MANAGED_PROFILE_CONTACTS_ACCESS))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // Use a policy checker without managed profile contacts access
        mVisibilityChecker =
                new VisibilityCheckerImpl(
                        mContext,
                        new PolicyChecker() {
                            @Override
                            public boolean doesCallerHaveManagedProfileContactsAccess(
                                    @NonNull String callingPackageName) {
                                return false;
                            }
                        });

        // Fails without managed profile contacts access
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* shouldCheckEnterpriseAccess= */ true),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();

        // Grant managed profile contacts access
        mVisibilityChecker =
                new VisibilityCheckerImpl(
                        mContext,
                        new PolicyChecker() {
                            @Override
                            public boolean doesCallerHaveManagedProfileContactsAccess(
                                    @NonNull String callingPackageName) {
                                return true;
                            }
                        });

        // Passes with managed profile contacts access
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* shouldCheckEnterpriseAccess= */ true),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isTrue();
    }

    @Test
    public void testSetSchema_visibleToPermissions_managedProfileAccessRequiresEnterprise()
            throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc that requires READ_SMS and ENTERPRISE_ACCESS permission.
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPermissions(
                                ImmutableSet.of(ENTERPRISE_ACCESS, MANAGED_PROFILE_CONTACTS_ACCESS))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // Use a policy checker with managed profile contacts access
        mVisibilityChecker =
                new VisibilityCheckerImpl(
                        mContext,
                        new PolicyChecker() {
                            @Override
                            public boolean doesCallerHaveManagedProfileContactsAccess(
                                    @NonNull String callingPackageName) {
                                return true;
                            }
                        });

        // Passes with enterprise access
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* shouldCheckEnterpriseAccess= */ true),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isTrue();

        // Fails without enterprise access
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* shouldCheckEnterpriseAccess= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    public void testSetSchema_visibleToPermissions_withoutEnterpriseAccessPermission()
            throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc that requires ENTERPRISE_ACCESS permission.
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPermissions(ImmutableSet.of(ENTERPRISE_ACCESS))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // We should not be able to access since the only permission set has ENTERPRISE_ACCESS
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    // TODO(b/202194495) add test for READ_HOME_APP_SEARCH_DATA and READ_ASSISTANT_APP_SEARCH_DATA
    // once they are available in Shell.
    @Test
    public void testSetSchema_visibleToPermissions_anyCombinations() throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc that require the querier to hold both READ_SMS and READ_CALENDAR, or
        // READ_CONTACTS only or READ_EXTERNAL_STORAGE only.
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPermissions(
                                ImmutableSet.of(
                                        SetSchemaRequest.READ_SMS, SetSchemaRequest.READ_CALENDAR))
                        .addVisibleToPermissions(ImmutableSet.of(SetSchemaRequest.READ_CONTACTS))
                        .addVisibleToPermissions(
                                ImmutableSet.of(SetSchemaRequest.READ_EXTERNAL_STORAGE))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // Grant the READ_SMS and READ_CALENDAR permission, we should able to access.
        mUiAutomation.adoptShellPermissionIdentity(READ_SMS, READ_CALENDAR);
        try {
            assertThat(
                            mVisibilityChecker.isSchemaSearchableByCaller(
                                    new FrameworkCallerAccess(
                                            mAttributionSource,
                                            /* callerHasSystemAccess= */ false,
                                            /* isForEnterprise= */ false),
                                    "package",
                                    prefix + "Schema",
                                    mVisibilityStore))
                    .isTrue();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }

        // Grant the READ_SMS only, it shouldn't have access.
        mUiAutomation.adoptShellPermissionIdentity(READ_SMS);
        try {
            assertThat(
                            mVisibilityChecker.isSchemaSearchableByCaller(
                                    new FrameworkCallerAccess(
                                            mAttributionSource,
                                            /* callerHasSystemAccess= */ false,
                                            /* isForEnterprise= */ false),
                                    "package",
                                    prefix + "Schema",
                                    mVisibilityStore))
                    .isFalse();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }

        // Grant the READ_SMS and READ_CALENDAR permission, we should able to access.
        mUiAutomation.adoptShellPermissionIdentity(READ_SMS, READ_CALENDAR);
        try {
            assertThat(
                            mVisibilityChecker.isSchemaSearchableByCaller(
                                    new FrameworkCallerAccess(
                                            mAttributionSource,
                                            /* callerHasSystemAccess= */ false,
                                            /* isForEnterprise= */ false),
                                    "package",
                                    prefix + "Schema",
                                    mVisibilityStore))
                    .isTrue();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }

        // Grant the READ_CONTACTS permission, we should able to access.
        mUiAutomation.adoptShellPermissionIdentity(READ_CONTACTS);
        try {
            assertThat(
                            mVisibilityChecker.isSchemaSearchableByCaller(
                                    new FrameworkCallerAccess(
                                            mAttributionSource,
                                            /* callerHasSystemAccess= */ false,
                                            /* isForEnterprise= */ false),
                                    "package",
                                    prefix + "Schema",
                                    mVisibilityStore))
                    .isTrue();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }

        // Grant the READ_EXTERNAL_STORAGE permission, we should able to access.
        mUiAutomation.adoptShellPermissionIdentity(READ_EXTERNAL_STORAGE);
        try {
            assertThat(
                            mVisibilityChecker.isSchemaSearchableByCaller(
                                    new FrameworkCallerAccess(
                                            mAttributionSource,
                                            /* callerHasSystemAccess= */ false,
                                            /* isForEnterprise= */ false),
                                    "package",
                                    prefix + "Schema",
                                    mVisibilityStore))
                    .isTrue();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }

        // Drop permissions, it becomes invisible.
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                new FrameworkCallerAccess(
                                        mAttributionSource,
                                        /* callerHasSystemAccess= */ false,
                                        /* isForEnterprise= */ false),
                                "package",
                                prefix + "Schema",
                                mVisibilityStore))
                .isFalse();
    }

    @Test
    public void testSetSchema_defaultNotVisibleToPermissions() throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc with default setting.
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema").build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // Give all supported permissions to the caller, it still cannot get the access.
        mUiAutomation.adoptShellPermissionIdentity(
                READ_SMS,
                READ_CALENDAR,
                READ_CONTACTS,
                READ_EXTERNAL_STORAGE,
                READ_HOME_APP_SEARCH_DATA,
                READ_ASSISTANT_APP_SEARCH_DATA);
        try {
            assertThat(
                            mVisibilityChecker.isSchemaSearchableByCaller(
                                    new FrameworkCallerAccess(
                                            mAttributionSource,
                                            /* callerHasSystemAccess= */ false,
                                            /* isForEnterprise= */ false),
                                    "package",
                                    prefix + "Schema",
                                    mVisibilityStore))
                    .isFalse();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }
    }

    @Test
    public void testSetSchema_visibleToPermissions_needAllPermissions() throws Exception {
        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc which requires both READ_SMS and READ_CALENDAR
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToPermissions(
                                ImmutableSet.of(
                                        SetSchemaRequest.READ_SMS, SetSchemaRequest.READ_CALENDAR))
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        mUiAutomation.adoptShellPermissionIdentity(READ_SMS);
        try {
            // Only has READ_SMS won't have access.
            assertThat(
                            mVisibilityChecker.isSchemaSearchableByCaller(
                                    new FrameworkCallerAccess(
                                            mAttributionSource,
                                            /* callerHasSystemAccess= */ false,
                                            /* isForEnterprise= */ false),
                                    "package",
                                    prefix + "Schema",
                                    mVisibilityStore))
                    .isFalse();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }

        mUiAutomation.adoptShellPermissionIdentity(READ_SMS, READ_CALENDAR);
        try {
            // has READ_SMS and READ_CALENDAR will have access.
            assertThat(
                            mVisibilityChecker.isSchemaSearchableByCaller(
                                    new FrameworkCallerAccess(
                                            mAttributionSource,
                                            /* callerHasSystemAccess= */ false,
                                            /* isForEnterprise= */ false),
                                    "package",
                                    prefix + "Schema",
                                    mVisibilityStore))
                    .isTrue();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }
    }

    @Test
    public void testSetSchema_visibleToConfig_needMatchAllSettings() throws Exception {
        // Values for a "foo" client
        String packageNameFoo = "packageFoo";
        byte[] sha256CertFoo = new byte[] {10};

        PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
        when(mockPackageManager.getPackageUid(eq(packageNameFoo), /* flags= */ anyInt()))
                .thenReturn(mAttributionSource.getUid());

        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc which requires both READ_SMS and access to packageFoo
        SchemaVisibilityConfig visibleToConfig =
                new SchemaVisibilityConfig.Builder().addAllowedPackage(
                                new PackageIdentifier(packageNameFoo, sha256CertFoo))
                        .addRequiredPermissions(ImmutableSet.of(
                                SetSchemaRequest.READ_SMS)).build();
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToConfig(visibleToConfig)
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        mUiAutomation.adoptShellPermissionIdentity(READ_SMS);
        try {
            // Only has READ_SMS won't have access.
            when(mockPackageManager.hasSigningCertificate(
                    packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                    .thenReturn(false);
            assertThat(
                    mVisibilityChecker.isSchemaSearchableByCaller(
                            new FrameworkCallerAccess(
                                    mAttributionSource,
                                    /* callerHasSystemAccess= */ false,
                                    /* isForEnterprise= */ false),
                            "package",
                            prefix + "Schema",
                            mVisibilityStore))
                    .isFalse();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }

        // Only has access to packageFoo won't have access
        when(mockPackageManager.hasSigningCertificate(
                packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);
        assertThat(
                mVisibilityChecker.isSchemaSearchableByCaller(
                        new FrameworkCallerAccess(
                                mAttributionSource,
                                /* callerHasSystemAccess= */ false,
                                /* isForEnterprise= */ false),
                        "package",
                        prefix + "Schema",
                        mVisibilityStore))
                .isFalse();

        mUiAutomation.adoptShellPermissionIdentity(READ_SMS);
        try {
            // has both READ_SMS and access to packageFoo will have access.
            when(mockPackageManager.hasSigningCertificate(
                    packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                    .thenReturn(true);
            assertThat(
                    mVisibilityChecker.isSchemaSearchableByCaller(
                            new FrameworkCallerAccess(
                                    mAttributionSource,
                                    /* callerHasSystemAccess= */ false,
                                    /* isForEnterprise= */ false),
                            "package",
                            prefix + "Schema",
                            mVisibilityStore))
                    .isTrue();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ENTERPRISE_VISIBLE_TO_CONFIG)
    public void testSetSchema_enterpriseVisibleToConfig_withEnterpriseAccessPermission()
            throws Exception {
        // Values for a "foo" client
        String packageNameFoo = "packageFoo";
        byte[] sha256CertFoo = new byte[] {10};

        PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
        when(mockPackageManager.getPackageUid(eq(packageNameFoo), /* flags= */ anyInt()))
                .thenReturn(mAttributionSource.getUid());

        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc which requires both ENTERPRISE_ACCESS and access to packageFoo
        SchemaVisibilityConfig visibleToConfig =
                new SchemaVisibilityConfig.Builder().addAllowedPackage(
                        new PackageIdentifier(packageNameFoo,
                                sha256CertFoo)).addRequiredPermissions(
                        ImmutableSet.of(ENTERPRISE_ACCESS)).build();
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToConfig(visibleToConfig)
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // Only enterprise call won't have access.
        when(mockPackageManager.hasSigningCertificate(
                packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(false);
        assertThat(
                mVisibilityChecker.isSchemaSearchableByCaller(
                        new FrameworkCallerAccess(
                                mAttributionSource,
                                /* callerHasSystemAccess= */ false,
                                /* isForEnterprise= */ true),
                        "package",
                        prefix + "Schema",
                        mVisibilityStore))
                .isFalse();

        // Only has access to packageFoo won't have access
        when(mockPackageManager.hasSigningCertificate(
                packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);
        assertThat(
                mVisibilityChecker.isSchemaSearchableByCaller(
                        new FrameworkCallerAccess(
                                mAttributionSource,
                                /* callerHasSystemAccess= */ false,
                                /* isForEnterprise= */ false),
                        "package",
                        prefix + "Schema",
                        mVisibilityStore))
                .isFalse();

        // both enterprise call and has access to packageFoo will have access.
        when(mockPackageManager.hasSigningCertificate(
                packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);
        assertThat(
                mVisibilityChecker.isSchemaSearchableByCaller(
                        new FrameworkCallerAccess(
                                mAttributionSource,
                                /* callerHasSystemAccess= */ false,
                                /* isForEnterprise= */ true),
                        "package",
                        prefix + "Schema",
                        mVisibilityStore))
                .isTrue();
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ENTERPRISE_VISIBLE_TO_CONFIG)
    public void testSetSchema_enterpriseVisibleToConfig_enterpriseAccessAndAdditionalPermissionSet()
            throws Exception {
        // Values for a "foo" client
        String packageNameFoo = "packageFoo";
        byte[] sha256CertFoo = new byte[] {10};

        PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
        when(mockPackageManager.getPackageUid(eq(packageNameFoo), /* flags= */ anyInt()))
                .thenReturn(mAttributionSource.getUid());

        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc which requires both ENTERPRISE_ACCESS and access to packageFoo
        SchemaVisibilityConfig visibleToConfig =
                new SchemaVisibilityConfig.Builder().addAllowedPackage(
                        new PackageIdentifier(packageNameFoo,
                                sha256CertFoo)).addRequiredPermissions(
                        ImmutableSet.of(ENTERPRISE_ACCESS)).addRequiredPermissions(
                        ImmutableSet.of(SetSchemaRequest.READ_SMS)).build();
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToConfig(visibleToConfig)
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        // Satisfy one permission set
        mUiAutomation.adoptShellPermissionIdentity(READ_SMS);
        try {
            // Enterprise call but no access to packageFoo won't have access.
            when(mockPackageManager.hasSigningCertificate(
                    packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                    .thenReturn(false);
            assertThat(
                    mVisibilityChecker.isSchemaSearchableByCaller(
                            new FrameworkCallerAccess(
                                    mAttributionSource,
                                    /* callerHasSystemAccess= */ false,
                                    /* isForEnterprise= */ true),
                            "package",
                            prefix + "Schema",
                            mVisibilityStore))
                    .isFalse();

            // has READ_SMS and access to packageFoo will have access.
            when(mockPackageManager.hasSigningCertificate(
                    packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                    .thenReturn(true);
            assertThat(
                    mVisibilityChecker.isSchemaSearchableByCaller(
                            new FrameworkCallerAccess(
                                    mAttributionSource,
                                    /* callerHasSystemAccess= */ false,
                                    /* isForEnterprise= */ false),
                            "package",
                            prefix + "Schema",
                            mVisibilityStore))
                    .isTrue();

            // both enterprise call and has access to packageFoo will have access.
            when(mockPackageManager.hasSigningCertificate(
                    packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                    .thenReturn(true);
            assertThat(
                    mVisibilityChecker.isSchemaSearchableByCaller(
                            new FrameworkCallerAccess(
                                    mAttributionSource,
                                    /* callerHasSystemAccess= */ false,
                                    /* isForEnterprise= */ true),
                            "package",
                            prefix + "Schema",
                            mVisibilityStore))
                    .isTrue();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }
    }

    @Test
    public void testSetSchema_enterpriseVisibleToConfig_withoutEnterpriseAccessPermission()
            throws Exception {
        // Values for a "foo" client
        String packageNameFoo = "packageFoo";
        byte[] sha256CertFoo = new byte[] {10};

        PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
        when(mockPackageManager.getPackageUid(eq(packageNameFoo), /* flags= */ anyInt()))
                .thenReturn(mAttributionSource.getUid());
        when(mockPackageManager.hasSigningCertificate(
                packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);

        String prefix = PrefixUtil.createPrefix("package", "database");

        // Create a VDoc which requires READ_SMS and access to packageFoo
        SchemaVisibilityConfig visibleToConfig =
                new SchemaVisibilityConfig.Builder().addAllowedPackage(
                        new PackageIdentifier(packageNameFoo,
                                sha256CertFoo)).addRequiredPermissions(
                        ImmutableSet.of(SetSchemaRequest.READ_SMS)).build();
        InternalVisibilityConfig visibilityConfig =
                new InternalVisibilityConfig.Builder(/* id= */ prefix + "Schema")
                        .addVisibleToConfig(visibleToConfig)
                        .build();
        mVisibilityStore.setVisibility(
                ImmutableList.of(visibilityConfig), /* callStatsBuilder= */ null);

        mUiAutomation.adoptShellPermissionIdentity(READ_SMS);
        try {
            // Enterprise call won't have access since no ENTERPRISE_ACCESS permission
            assertThat(
                    mVisibilityChecker.isSchemaSearchableByCaller(
                            new FrameworkCallerAccess(
                                    mAttributionSource,
                                    /* callerHasSystemAccess= */ false,
                                    /* isForEnterprise= */ true),
                            "package",
                            prefix + "Schema",
                            mVisibilityStore))
                    .isFalse();

            // Normal call will have access
            assertThat(
                    mVisibilityChecker.isSchemaSearchableByCaller(
                            new FrameworkCallerAccess(
                                    mAttributionSource,
                                    /* callerHasSystemAccess= */ false,
                                    /* isForEnterprise= */ false),
                            "package",
                            prefix + "Schema",
                            mVisibilityStore))
                    .isTrue();
        } finally {
            mUiAutomation.dropShellPermissionIdentity();
        }
    }

    @Test
    public void testPublicVisibility_mockPackageManager() throws Exception {
        byte[] mockSignature = new byte[32];

        PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
        // Let's pretend package A is visible to package B & C, package B is visible to package C.
        when(mockPackageManager.canPackageQuery("A", "A")).thenReturn(true);
        when(mockPackageManager.canPackageQuery("A", "B")).thenReturn(false);
        when(mockPackageManager.canPackageQuery("A", "C")).thenReturn(false);
        when(mockPackageManager.canPackageQuery("B", "A")).thenReturn(true);
        when(mockPackageManager.canPackageQuery("B", "B")).thenReturn(true);
        when(mockPackageManager.canPackageQuery("B", "C")).thenReturn(false);
        when(mockPackageManager.canPackageQuery("C", "A")).thenReturn(true);
        when(mockPackageManager.canPackageQuery("C", "B")).thenReturn(true);
        when(mockPackageManager.canPackageQuery("C", "C")).thenReturn(true);

        // Mock package certificates
        when(mockPackageManager.hasSigningCertificate(
                        "A", mockSignature, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);
        when(mockPackageManager.hasSigningCertificate(
                        "B", mockSignature, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);
        when(mockPackageManager.hasSigningCertificate(
                        "C", mockSignature, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);

        // The Android package will own the schemas, but they will be visible from other packages.
        String prefix = PrefixUtil.createPrefix("android", "database");
        String[] packageNames = {"A", "B", "C"};
        List<InternalVisibilityConfig> visibilityConfigs = new ArrayList<>(packageNames.length);

        for (String packageName : packageNames) {
            // android, database, schemaX
            String schemaName = prefix + "Schema" + packageName;

            InternalVisibilityConfig visibilityConfig =
                    new InternalVisibilityConfig.Builder(/* id= */ schemaName)
                            .setPubliclyVisibleTargetPackage(
                                    new PackageIdentifier(packageName, mockSignature))
                            .build();
            visibilityConfigs.add(visibilityConfig);
        }
        mVisibilityStore.setVisibility(visibilityConfigs, /* callStatsBuilder= */ null);

        FrameworkCallerAccess callerAccessA =
                new FrameworkCallerAccess(
                        new AppSearchAttributionSource("A", 1, /* callingPid= */ 1),
                        /* callerHasSystemAccess= */ false,
                        /* isForEnterprise= */ false);
        FrameworkCallerAccess callerAccessB =
                new FrameworkCallerAccess(
                        new AppSearchAttributionSource("B", 2, /* callingPid= */ 2),
                        /* callerHasSystemAccess= */ false,
                        /* isForEnterprise= */ false);
        FrameworkCallerAccess callerAccessC =
                new FrameworkCallerAccess(
                        new AppSearchAttributionSource("C", 3, /* callingPid= */ 3),
                        /* callerHasSystemAccess= */ false,
                        /* isForEnterprise= */ false);

        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                callerAccessA,
                                "android",
                                "android$database/SchemaA",
                                mVisibilityStore))
                .isTrue();
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                callerAccessA,
                                "android",
                                "android$database/SchemaB",
                                mVisibilityStore))
                .isFalse();
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                callerAccessA,
                                "android",
                                "android$database/SchemaC",
                                mVisibilityStore))
                .isFalse();

        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                callerAccessB,
                                "android",
                                "android$database/SchemaA",
                                mVisibilityStore))
                .isTrue();
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                callerAccessB,
                                "android",
                                "android$database/SchemaB",
                                mVisibilityStore))
                .isTrue();
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                callerAccessB,
                                "android",
                                "android$database/SchemaC",
                                mVisibilityStore))
                .isFalse();

        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                callerAccessC,
                                "android",
                                "android$database/SchemaA",
                                mVisibilityStore))
                .isTrue();
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                callerAccessC,
                                "android",
                                "android$database/SchemaB",
                                mVisibilityStore))
                .isTrue();
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                callerAccessC,
                                "android",
                                "android$database/SchemaC",
                                mVisibilityStore))
                .isTrue();
    }

    @Test
    public void testPublicVisibility_mockPackageManager_cantFindPackage() throws Exception {
        // Test to ensure public visibility returns false if the PackageManager check throws an
        // exception.
        PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
        byte[] mockSignature = new byte[32];

        // Mock package certificates
        when(mockPackageManager.hasSigningCertificate(
                        "A", mockSignature, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);
        when(mockPackageManager.hasSigningCertificate(
                        "B", mockSignature, PackageManager.CERT_INPUT_SHA256))
                .thenReturn(true);

        when(mockPackageManager.canPackageQuery("A", "B"))
                .thenThrow(new PackageManager.NameNotFoundException());
        when(mockPackageManager.canPackageQuery("B", "A"))
                .thenThrow(new PackageManager.NameNotFoundException());

        // The Android package will own the schemas, but they will be visible from other packages.
        String prefix = PrefixUtil.createPrefix("android", "database");
        String[] packageNames = {"A", "B"};
        List<InternalVisibilityConfig> visibilityConfigs = new ArrayList<>(packageNames.length);

        for (String packageName : packageNames) {
            // android, database, schemaX
            String schemaName = prefix + "Schema" + packageName;

            InternalVisibilityConfig visibilityConfig =
                    new InternalVisibilityConfig.Builder(/* id= */ schemaName)
                            .setPubliclyVisibleTargetPackage(
                                    new PackageIdentifier(packageName, mockSignature))
                            .build();
            visibilityConfigs.add(visibilityConfig);
        }
        mVisibilityStore.setVisibility(visibilityConfigs, /* callStatsBuilder= */ null);

        FrameworkCallerAccess callerAccessA =
                new FrameworkCallerAccess(
                        new AppSearchAttributionSource("A", 1, /* callingPid= */ 1),
                        /* callerHasSystemAccess= */ false,
                        /* isForEnterprise= */ false);
        FrameworkCallerAccess callerAccessB =
                new FrameworkCallerAccess(
                        new AppSearchAttributionSource("B", 2, /* callingPid= */ 2),
                        /* callerHasSystemAccess= */ false,
                        /* isForEnterprise= */ false);

        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                callerAccessA,
                                "android",
                                "android$database/SchemaB",
                                mVisibilityStore))
                .isFalse();
        assertThat(
                        mVisibilityChecker.isSchemaSearchableByCaller(
                                callerAccessB,
                                "android",
                                "android$database/SchemaA",
                                mVisibilityStore))
                .isFalse();
    }

    @NonNull
    private PackageManager getMockPackageManager(@NonNull UserHandle user) {
        PackageManager pm = mMockPackageManagers.get(user);
        if (pm == null) {
            pm = Mockito.mock(PackageManager.class);
            mMockPackageManagers.put(user, pm);
        }
        return pm;
    }
}
