/* * Copyright (C) 2024 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; import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.role.RoleManager; import android.companion.AssociationRequest; import android.content.pm.PackageManagerInternal; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; import android.os.Binder; import android.os.Process; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.util.ArraySet; import androidx.test.filters.SmallTest; import com.android.server.wm.SensitiveContentPackages.PackageInfo; import com.android.server.wm.WindowManagerInternal; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.List; import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) @RequiresFlagsEnabled(FLAG_SENSITIVE_CONTENT_APP_PROTECTION) /** * Test {@link SensitiveContentProtectionManagerService} for sensitive on screen content * protection, the service protects sensitive content during screen share. */ public class SensitiveContentProtectionManagerServiceContentTest { private final PackageInfo mPackageInfo = new PackageInfo("test.package", 12345, new Binder()); private final String mScreenRecorderPackage = "test.screen.recorder.package"; private final String mExemptedScreenRecorderPackage = "test.exempted.screen.recorder.package"; private SensitiveContentProtectionManagerService mSensitiveContentProtectionManagerService; private MediaProjectionManager.Callback mMediaPorjectionCallback; @Mock private WindowManagerInternal mWindowManager; @Mock private MediaProjectionManager mProjectionManager; @Mock private PackageManagerInternal mPackageManagerInternal; @Mock private RoleManager mRoleManager; private MediaProjectionInfo mMediaProjectionInfo; @Captor private ArgumentCaptor mMediaProjectionCallbackCaptor; @Captor private ArgumentCaptor> mPackageInfoCaptor; @Rule public final TestableContext mContext = new TestableContext(getInstrumentation().getTargetContext(), null); @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Before public void setUp() { MockitoAnnotations.initMocks(this); mSensitiveContentProtectionManagerService = new SensitiveContentProtectionManagerService(mContext); mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager, mPackageManagerInternal, mRoleManager, new ArraySet<>(Set.of(mExemptedScreenRecorderPackage))); verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any()); mMediaPorjectionCallback = mMediaProjectionCallbackCaptor.getValue(); mMediaProjectionInfo = new MediaProjectionInfo(mScreenRecorderPackage, Process.myUserHandle(), null); } @Test public void testExemptedRecorderPackageForScreenCapture() { MediaProjectionInfo exemptedRecorderPackage = new MediaProjectionInfo( mExemptedScreenRecorderPackage, Process.myUserHandle(), null); mMediaPorjectionCallback.onStart(exemptedRecorderPackage); mSensitiveContentProtectionManagerService.setSensitiveContentProtection( mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test public void testBlockAppWindowForScreenCapture() { mMediaPorjectionCallback.onStart(mMediaProjectionInfo); mSensitiveContentProtectionManagerService.setSensitiveContentProtection( mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); verify(mWindowManager, atLeast(1)) .addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); assertThat(mPackageInfoCaptor.getValue()).containsExactly(mPackageInfo); } @Test public void testUnblockAppWindowForScreenCapture() { mMediaPorjectionCallback.onStart(mMediaProjectionInfo); mSensitiveContentProtectionManagerService.setSensitiveContentProtection( mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), false); verify(mWindowManager).removeBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); assertThat(mPackageInfoCaptor.getValue()).containsExactly(mPackageInfo); } @Test public void testAppWindowIsUnblockedBeforeScreenCapture() { // when screen sharing is not active, no app window should be blocked. mSensitiveContentProtectionManagerService.setSensitiveContentProtection( mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test public void testAppWindowsAreUnblockedOnScreenCaptureEnd() { mMediaPorjectionCallback.onStart(mMediaProjectionInfo); mSensitiveContentProtectionManagerService.setSensitiveContentProtection( mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); // when screen sharing ends, all blocked app windows should be cleared. mMediaPorjectionCallback.onStop(mMediaProjectionInfo); verify(mWindowManager).clearBlockedApps(); } @Test public void testAutofillServicePackageExemption() { String testAutofillService = mScreenRecorderPackage + "/com.example.SampleAutofillService"; int userId = Process.myUserHandle().getIdentifier(); Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, testAutofillService, userId); mMediaPorjectionCallback.onStart(mMediaProjectionInfo); mSensitiveContentProtectionManagerService.setSensitiveContentProtection( mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test public void testDeveloperOptionDisableFeature() { mockDisabledViaDeveloperOption(); mMediaProjectionCallbackCaptor.getValue().onStart(mMediaProjectionInfo); mSensitiveContentProtectionManagerService.setSensitiveContentProtection( mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test public void testAppStreamingRoleHolderExemption() { when(mRoleManager.getRoleHoldersAsUser( AssociationRequest.DEVICE_PROFILE_APP_STREAMING, mMediaProjectionInfo.getUserHandle())).thenReturn( List.of(mMediaProjectionInfo.getPackageName())); mMediaPorjectionCallback.onStart(mMediaProjectionInfo); mSensitiveContentProtectionManagerService.setSensitiveContentProtection( mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } private void mockDisabledViaDeveloperOption() { Settings.Global.putInt( mContext.getContentResolver(), Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1); } }