Loading services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +51 −1 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.media.projection; import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION; import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT; import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED; import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; Loading @@ -34,10 +35,12 @@ import android.Manifest; import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.app.ActivityManagerInternal; import android.app.ActivityOptions.LaunchCookie; import android.app.AppOpsManager; import android.app.IProcessObserver; import android.app.KeyguardManager; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; Loading Loading @@ -78,6 +81,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.Watchdog; import com.android.server.wm.WindowManagerInternal; Loading Loading @@ -132,6 +136,7 @@ public final class MediaProjectionManagerService extends SystemService private final ActivityManagerInternal mActivityManagerInternal; private final PackageManager mPackageManager; private final WindowManagerInternal mWmInternal; private final KeyguardManager mKeyguardManager; private final MediaRouter mMediaRouter; private final MediaRouterCallback mMediaRouterCallback; Loading @@ -147,7 +152,9 @@ public final class MediaProjectionManagerService extends SystemService this(context, new Injector()); } @VisibleForTesting MediaProjectionManagerService(Context context, Injector injector) { @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) @VisibleForTesting MediaProjectionManagerService(Context context, Injector injector) { super(context); mContext = context; mInjector = injector; Loading @@ -163,9 +170,47 @@ public final class MediaProjectionManagerService extends SystemService mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE); mMediaRouterCallback = new MediaRouterCallback(); mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(context); mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mKeyguardManager.addKeyguardLockedStateListener( mContext.getMainExecutor(), this::onKeyguardLockedStateChanged); Watchdog.getInstance().addMonitor(this); } /** * In order to record the keyguard, the MediaProjection package must be either: * - a holder of RECORD_SENSITIVE_CONTENT permission, or * - be one of the bugreport whitelisted packages */ private boolean canCaptureKeyguard() { if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) { return true; } synchronized (mLock) { if (mProjectionGrant == null || mProjectionGrant.packageName == null) { return false; } if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT, mProjectionGrant.packageName) == PackageManager.PERMISSION_GRANTED) { return true; } return SystemConfig.getInstance().getBugreportWhitelistedPackages() .contains(mProjectionGrant.packageName); } } @VisibleForTesting void onKeyguardLockedStateChanged(boolean isKeyguardLocked) { if (!isKeyguardLocked) return; synchronized (mLock) { if (mProjectionGrant != null && !canCaptureKeyguard()) { Slog.d(TAG, "Content Recording: Stopped MediaProjection" + " due to keyguard lock"); mProjectionGrant.stop(); } } } /** Functional interface for providing time. */ @VisibleForTesting interface Clock { Loading Loading @@ -1252,6 +1297,11 @@ public final class MediaProjectionManagerService extends SystemService @Override public void notifyVirtualDisplayCreated(int displayId) { notifyVirtualDisplayCreated_enforcePermission(); if (mKeyguardManager.isKeyguardLocked() && !canCaptureKeyguard()) { Slog.w(TAG, "Content Recording: Keyguard locked, aborting MediaProjection"); stop(); return; } synchronized (mLock) { mVirtualDisplayId = displayId; Loading services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +86 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.media.projection; import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; import static android.media.projection.MediaProjectionManager.TYPE_MIRRORING; Loading Loading @@ -50,6 +51,7 @@ import static org.testng.Assert.assertThrows; import android.app.ActivityManagerInternal; import android.app.ActivityOptions.LaunchCookie; import android.app.KeyguardManager; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; Loading @@ -66,7 +68,9 @@ import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.os.test.TestLooper; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.view.ContentRecordingSession; import android.view.ContentRecordingSession.RecordContent; Loading @@ -81,6 +85,7 @@ import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; Loading Loading @@ -151,6 +156,9 @@ public class MediaProjectionManagerServiceTest { private ContentRecordingSession mWaitingDisplaySession = createDisplaySession(DEFAULT_DISPLAY); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private ActivityManagerInternal mAmInternal; @Mock Loading @@ -158,6 +166,8 @@ public class MediaProjectionManagerServiceTest { @Mock private PackageManager mPackageManager; @Mock private KeyguardManager mKeyguardManager; @Mock private IMediaProjectionWatcherCallback mWatcherCallback; @Mock private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; Loading @@ -177,6 +187,7 @@ public class MediaProjectionManagerServiceTest { mContext = spy(new ContextWrapper( InstrumentationRegistry.getInstrumentation().getTargetContext())); doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(mKeyguardManager).when(mContext).getSystemService(eq(Context.KEYGUARD_SERVICE)); mClock = new OffsettableClock.Stopped(); mWaitingDisplaySession.setWaitingForConsent(true); Loading Loading @@ -246,6 +257,39 @@ public class MediaProjectionManagerServiceTest { assertThat(stoppedCallback2).isFalse(); } @EnableFlags(android.companion.virtualdevice.flags .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked() throws Exception { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); doReturn(true).when(mKeyguardManager).isKeyguardLocked(); doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager) .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName); projection.start(mIMediaProjectionCallback); projection.notifyVirtualDisplayCreated(10); assertThat(mService.getActiveProjectionInfo()).isNull(); assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue(); } @EnableFlags(android.companion.virtualdevice.flags .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_packageAllowlisted() throws NameNotFoundException { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); doReturn(true).when(mKeyguardManager).isKeyguardLocked(); doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager) .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName); projection.start(mIMediaProjectionCallback); projection.notifyVirtualDisplayCreated(10); // The projection was started because it was allowed to capture the keyguard. assertThat(mService.getActiveProjectionInfo()).isNotNull(); } @Test public void testCreateProjection_attemptReuse_noPriorProjectionGrant() throws NameNotFoundException { Loading Loading @@ -317,6 +361,48 @@ public class MediaProjectionManagerServiceTest { assertThat(secondProjection).isNotEqualTo(projection); } @EnableFlags(android.companion.virtualdevice.flags .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testKeyguardLocked_stopsActiveProjection() throws Exception { MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(service); projection.start(mIMediaProjectionCallback); assertThat(service.getActiveProjectionInfo()).isNotNull(); doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager) .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName); service.onKeyguardLockedStateChanged(true); verify(mMediaProjectionMetricsLogger).logStopped(UID, TARGET_UID_UNKNOWN); assertThat(service.getActiveProjectionInfo()).isNull(); assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue(); } @EnableFlags(android.companion.virtualdevice.flags .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testKeyguardLocked_packageAllowlisted_doesNotStopActiveProjection() throws NameNotFoundException { MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(service); projection.start(mIMediaProjectionCallback); assertThat(service.getActiveProjectionInfo()).isNotNull(); doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission( RECORD_SENSITIVE_CONTENT, projection.packageName); service.onKeyguardLockedStateChanged(true); verifyZeroInteractions(mMediaProjectionMetricsLogger); assertThat(service.getActiveProjectionInfo()).isNotNull(); } @Test public void stop_noActiveProjections_doesNotLog() throws Exception { MediaProjectionManagerService service = Loading Loading
services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +51 −1 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.media.projection; import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION; import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT; import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED; import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; Loading @@ -34,10 +35,12 @@ import android.Manifest; import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.app.ActivityManagerInternal; import android.app.ActivityOptions.LaunchCookie; import android.app.AppOpsManager; import android.app.IProcessObserver; import android.app.KeyguardManager; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; Loading Loading @@ -78,6 +81,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.Watchdog; import com.android.server.wm.WindowManagerInternal; Loading Loading @@ -132,6 +136,7 @@ public final class MediaProjectionManagerService extends SystemService private final ActivityManagerInternal mActivityManagerInternal; private final PackageManager mPackageManager; private final WindowManagerInternal mWmInternal; private final KeyguardManager mKeyguardManager; private final MediaRouter mMediaRouter; private final MediaRouterCallback mMediaRouterCallback; Loading @@ -147,7 +152,9 @@ public final class MediaProjectionManagerService extends SystemService this(context, new Injector()); } @VisibleForTesting MediaProjectionManagerService(Context context, Injector injector) { @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) @VisibleForTesting MediaProjectionManagerService(Context context, Injector injector) { super(context); mContext = context; mInjector = injector; Loading @@ -163,9 +170,47 @@ public final class MediaProjectionManagerService extends SystemService mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE); mMediaRouterCallback = new MediaRouterCallback(); mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(context); mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mKeyguardManager.addKeyguardLockedStateListener( mContext.getMainExecutor(), this::onKeyguardLockedStateChanged); Watchdog.getInstance().addMonitor(this); } /** * In order to record the keyguard, the MediaProjection package must be either: * - a holder of RECORD_SENSITIVE_CONTENT permission, or * - be one of the bugreport whitelisted packages */ private boolean canCaptureKeyguard() { if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) { return true; } synchronized (mLock) { if (mProjectionGrant == null || mProjectionGrant.packageName == null) { return false; } if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT, mProjectionGrant.packageName) == PackageManager.PERMISSION_GRANTED) { return true; } return SystemConfig.getInstance().getBugreportWhitelistedPackages() .contains(mProjectionGrant.packageName); } } @VisibleForTesting void onKeyguardLockedStateChanged(boolean isKeyguardLocked) { if (!isKeyguardLocked) return; synchronized (mLock) { if (mProjectionGrant != null && !canCaptureKeyguard()) { Slog.d(TAG, "Content Recording: Stopped MediaProjection" + " due to keyguard lock"); mProjectionGrant.stop(); } } } /** Functional interface for providing time. */ @VisibleForTesting interface Clock { Loading Loading @@ -1252,6 +1297,11 @@ public final class MediaProjectionManagerService extends SystemService @Override public void notifyVirtualDisplayCreated(int displayId) { notifyVirtualDisplayCreated_enforcePermission(); if (mKeyguardManager.isKeyguardLocked() && !canCaptureKeyguard()) { Slog.w(TAG, "Content Recording: Keyguard locked, aborting MediaProjection"); stop(); return; } synchronized (mLock) { mVirtualDisplayId = displayId; Loading
services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +86 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.media.projection; import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; import static android.media.projection.MediaProjectionManager.TYPE_MIRRORING; Loading Loading @@ -50,6 +51,7 @@ import static org.testng.Assert.assertThrows; import android.app.ActivityManagerInternal; import android.app.ActivityOptions.LaunchCookie; import android.app.KeyguardManager; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; Loading @@ -66,7 +68,9 @@ import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.os.test.TestLooper; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.view.ContentRecordingSession; import android.view.ContentRecordingSession.RecordContent; Loading @@ -81,6 +85,7 @@ import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; Loading Loading @@ -151,6 +156,9 @@ public class MediaProjectionManagerServiceTest { private ContentRecordingSession mWaitingDisplaySession = createDisplaySession(DEFAULT_DISPLAY); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private ActivityManagerInternal mAmInternal; @Mock Loading @@ -158,6 +166,8 @@ public class MediaProjectionManagerServiceTest { @Mock private PackageManager mPackageManager; @Mock private KeyguardManager mKeyguardManager; @Mock private IMediaProjectionWatcherCallback mWatcherCallback; @Mock private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; Loading @@ -177,6 +187,7 @@ public class MediaProjectionManagerServiceTest { mContext = spy(new ContextWrapper( InstrumentationRegistry.getInstrumentation().getTargetContext())); doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(mKeyguardManager).when(mContext).getSystemService(eq(Context.KEYGUARD_SERVICE)); mClock = new OffsettableClock.Stopped(); mWaitingDisplaySession.setWaitingForConsent(true); Loading Loading @@ -246,6 +257,39 @@ public class MediaProjectionManagerServiceTest { assertThat(stoppedCallback2).isFalse(); } @EnableFlags(android.companion.virtualdevice.flags .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked() throws Exception { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); doReturn(true).when(mKeyguardManager).isKeyguardLocked(); doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager) .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName); projection.start(mIMediaProjectionCallback); projection.notifyVirtualDisplayCreated(10); assertThat(mService.getActiveProjectionInfo()).isNull(); assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue(); } @EnableFlags(android.companion.virtualdevice.flags .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testCreateProjection_keyguardLocked_packageAllowlisted() throws NameNotFoundException { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); doReturn(true).when(mKeyguardManager).isKeyguardLocked(); doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager) .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName); projection.start(mIMediaProjectionCallback); projection.notifyVirtualDisplayCreated(10); // The projection was started because it was allowed to capture the keyguard. assertThat(mService.getActiveProjectionInfo()).isNotNull(); } @Test public void testCreateProjection_attemptReuse_noPriorProjectionGrant() throws NameNotFoundException { Loading Loading @@ -317,6 +361,48 @@ public class MediaProjectionManagerServiceTest { assertThat(secondProjection).isNotEqualTo(projection); } @EnableFlags(android.companion.virtualdevice.flags .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testKeyguardLocked_stopsActiveProjection() throws Exception { MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(service); projection.start(mIMediaProjectionCallback); assertThat(service.getActiveProjectionInfo()).isNotNull(); doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager) .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName); service.onKeyguardLockedStateChanged(true); verify(mMediaProjectionMetricsLogger).logStopped(UID, TARGET_UID_UNKNOWN); assertThat(service.getActiveProjectionInfo()).isNull(); assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue(); } @EnableFlags(android.companion.virtualdevice.flags .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test public void testKeyguardLocked_packageAllowlisted_doesNotStopActiveProjection() throws NameNotFoundException { MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(service); projection.start(mIMediaProjectionCallback); assertThat(service.getActiveProjectionInfo()).isNotNull(); doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission( RECORD_SENSITIVE_CONTENT, projection.packageName); service.onKeyguardLockedStateChanged(true); verifyZeroInteractions(mMediaProjectionMetricsLogger); assertThat(service.getActiveProjectionInfo()).isNotNull(); } @Test public void stop_noActiveProjections_doesNotLog() throws Exception { MediaProjectionManagerService service = Loading