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

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

Merge "Add blocklist manager for content protection" into udc-qpr-dev

parents 6d458fd0 d02f0471
Loading
Loading
Loading
Loading
+111 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.contentprotection;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageInfo;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Collections;
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 blocklist.
 *
 * @hide
 */
class ContentProtectionBlocklistManager {

    private static final String TAG = "ContentProtectionBlocklistManager";

    private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
            "/product/etc/res/raw/content_protection/package_name_blocklist.txt";

    @NonNull private final ContentProtectionPackageManager mContentProtectionPackageManager;

    @Nullable private Set<String> mPackageNameBlocklist;

    protected ContentProtectionBlocklistManager(
            @NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
        mContentProtectionPackageManager = contentProtectionPackageManager;
    }

    public boolean isAllowed(@NonNull String packageName) {
        if (mPackageNameBlocklist == null) {
            // List not loaded or failed to load, don't run on anything
            return false;
        }
        if (mPackageNameBlocklist.contains(packageName)) {
            return false;
        }
        PackageInfo packageInfo = mContentProtectionPackageManager.getPackageInfo(packageName);
        if (packageInfo == null) {
            return false;
        }
        if (!mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo)) {
            return false;
        }
        if (mContentProtectionPackageManager.isSystemApp(packageInfo)) {
            return false;
        }
        if (mContentProtectionPackageManager.isUpdatedSystemApp(packageInfo)) {
            return false;
        }
        return true;
    }

    public void updateBlocklist(int blocklistSize) {
        Slog.i(TAG, "Blocklist size updating to: " + blocklistSize);
        mPackageNameBlocklist = readPackageNameBlocklist(blocklistSize);
    }

    @Nullable
    private Set<String> readPackageNameBlocklist(int blocklistSize) {
        if (blocklistSize <= 0) {
            // Explicitly requested an empty blocklist
            return Collections.emptySet();
        }
        List<String> lines = readLinesFromRawFile(PACKAGE_NAME_BLOCKLIST_FILENAME);
        if (lines == null) {
            return null;
        }
        return lines.stream().limit(blocklistSize).collect(Collectors.toSet());
    }

    @VisibleForTesting
    @Nullable
    protected List<String> readLinesFromRawFile(@NonNull String filename) {
        try (FileReader fileReader = new FileReader(filename);
                BufferedReader bufferedReader = new BufferedReader(fileReader)) {
            return bufferedReader
                    .lines()
                    .map(line -> line.trim())
                    .filter(line -> !line.isBlank())
                    .collect(Collectors.toList());
        } catch (Exception ex) {
            Slog.e(TAG, "Failed to read: " + filename, ex);
            return null;
        }
    }
}
+4 −3
Original line number Diff line number Diff line
@@ -34,8 +34,9 @@ import java.util.Arrays;
 *
 * @hide
 */
final class ContentProtectionPackageManager {
    private static final String TAG = ContentProtectionPackageManager.class.getSimpleName();
public class ContentProtectionPackageManager {

    private static final String TAG = "ContentProtectionPackageManager";

    private static final PackageInfoFlags PACKAGE_INFO_FLAGS =
            PackageInfoFlags.of(PackageManager.GET_PERMISSIONS);
@@ -51,7 +52,7 @@ final class ContentProtectionPackageManager {
        try {
            return mPackageManager.getPackageInfo(packageName, PACKAGE_INFO_FLAGS);
        } catch (NameNotFoundException ex) {
            Slog.w(TAG, "Package info not found: ", ex);
            Slog.w(TAG, "Package info not found for: " + packageName, ex);
            return null;
        }
    }
+236 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.contentprotection;

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

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

import android.annotation.NonNull;
import android.content.pm.PackageInfo;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.google.common.collect.ImmutableList;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

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

/**
 * Test for {@link ContentProtectionBlocklistManager}.
 *
 * <p>Run with: {@code atest
 * FrameworksServicesTests:
 * com.android.server.contentprotection.ContentProtectionBlocklistManagerTest}
 */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ContentProtectionBlocklistManagerTest {

    private static final String FIRST_PACKAGE_NAME = "com.test.first.package.name";

    private static final String SECOND_PACKAGE_NAME = "com.test.second.package.name";

    private static final String UNLISTED_PACKAGE_NAME = "com.test.unlisted.package.name";

    private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
            "/product/etc/res/raw/content_protection/package_name_blocklist.txt";

    private static final PackageInfo PACKAGE_INFO = new PackageInfo();

    private static final List<String> LINES =
            ImmutableList.of(FIRST_PACKAGE_NAME, SECOND_PACKAGE_NAME);

    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();

    @Mock private ContentProtectionPackageManager mMockContentProtectionPackageManager;

    private final List<String> mReadRawFiles = new ArrayList<>();

    private ContentProtectionBlocklistManager mContentProtectionBlocklistManager;

    @Before
    public void setup() {
        mContentProtectionBlocklistManager = new TestContentProtectionBlocklistManager();
    }

    @Test
    public void isAllowed_blocklistNotLoaded() {
        boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);

        assertThat(actual).isFalse();
        assertThat(mReadRawFiles).isEmpty();
        verifyZeroInteractions(mMockContentProtectionPackageManager);
    }

    @Test
    public void isAllowed_inBlocklist() {
        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());

        boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);

        assertThat(actual).isFalse();
        verifyZeroInteractions(mMockContentProtectionPackageManager);
    }

    @Test
    public void isAllowed_packageInfoNotFound() {
        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
                .thenReturn(null);

        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);

        assertThat(actual).isFalse();
        verify(mMockContentProtectionPackageManager, never())
                .hasRequestedInternetPermissions(any());
        verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
    }

    @Test
    public void isAllowed_notRequestedInternet() {
        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
                .thenReturn(PACKAGE_INFO);
        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
                .thenReturn(false);

        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);

        assertThat(actual).isFalse();
        verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
    }

    @Test
    public void isAllowed_systemApp() {
        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
                .thenReturn(PACKAGE_INFO);
        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
                .thenReturn(true);
        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);

        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);

        assertThat(actual).isFalse();
        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
    }

    @Test
    public void isAllowed_updatedSystemApp() {
        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
                .thenReturn(PACKAGE_INFO);
        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
                .thenReturn(true);
        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
        when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
                .thenReturn(true);

        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);

        assertThat(actual).isFalse();
    }

    @Test
    public void isAllowed_allowed() {
        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
                .thenReturn(PACKAGE_INFO);
        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
                .thenReturn(true);
        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(false);
        when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
                .thenReturn(false);

        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);

        assertThat(actual).isTrue();
    }

    @Test
    public void updateBlocklist_negativeSize() {
        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ -1);
        assertThat(mReadRawFiles).isEmpty();

        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
        verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
    }

    @Test
    public void updateBlocklist_zeroSize() {
        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 0);
        assertThat(mReadRawFiles).isEmpty();

        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
        verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
    }

    @Test
    public void updateBlocklist_positiveSize_belowTotal() {
        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 1);
        assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);

        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
        mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);

        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
        verify(mMockContentProtectionPackageManager).getPackageInfo(SECOND_PACKAGE_NAME);
    }

    @Test
    public void updateBlocklist_positiveSize_aboveTotal() {
        mContentProtectionBlocklistManager.updateBlocklist(LINES.size() + 1);
        assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);

        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
        mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);

        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(SECOND_PACKAGE_NAME);
    }

    private final class TestContentProtectionBlocklistManager
            extends ContentProtectionBlocklistManager {

        TestContentProtectionBlocklistManager() {
            super(mMockContentProtectionPackageManager);
        }

        @Override
        protected List<String> readLinesFromRawFile(@NonNull String filename) {
            mReadRawFiles.add(filename);
            return LINES;
        }
    }
}