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

Commit 86ed31c1 authored by Nino Jagar's avatar Nino Jagar Committed by Automerger Merge Worker
Browse files

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

parents 849ea3d7 ec13c53e
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;
        }
    }
}