diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java index b5e221324c978..c4d27eca02f87 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -23,8 +23,6 @@ import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; -import android.media.projection.IMediaProjection; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; @@ -86,6 +84,12 @@ public final class MediaProjectionManager { * capture request. Will be null if the result from the * startActivityForResult() is anything other than RESULT_OK. * + * Starting from Android {@link android.os.Build.VERSION_CODES#R}, if your application requests + * the {@link android.Manifest.permission#SYSTEM_ALERT_WINDOW} permission, and the + * user has not explicitly denied it, the permission will be automatically granted until the + * projection is stopped. This allows for user controls to be displayed on top of the screen + * being captured. + * * @param resultCode The result code from {@link android.app.Activity#onActivityResult(int, * int, android.content.Intent)} * @param resultData The resulting data from {@link android.app.Activity#onActivityResult(int, diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 9e509f4539211..1a749b34d85eb 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -22,6 +22,7 @@ import android.app.AppOpsManager; import android.app.IProcessObserver; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; @@ -43,6 +44,7 @@ import android.os.UserHandle; import android.util.ArrayMap; import android.util.Slog; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -399,11 +401,12 @@ public final class MediaProjectionManagerService extends SystemService public final UserHandle userHandle; private final int mTargetSdkVersion; private final boolean mIsPrivileged; + private final int mType; private IMediaProjectionCallback mCallback; private IBinder mToken; private IBinder.DeathRecipient mDeathEater; - private int mType; + private boolean mRestoreSystemAlertWindow; MediaProjection(int type, int uid, String packageName, int targetSdkVersion, boolean isPrivileged) { @@ -494,6 +497,35 @@ public final class MediaProjectionManagerService extends SystemService "MediaProjectionCallbacks must be valid, aborting MediaProjection", e); return; } + if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) { + final long token = Binder.clearCallingIdentity(); + try { + // We allow an app running a current screen capture session to use + // SYSTEM_ALERT_WINDOW for the duration of the session, to enable + // them to overlay their UX on top of what is being captured. + // We only do this if the app requests the permission, and the appop + // is in its default state (the user has neither explicitly allowed nor + // disallowed it). + final PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser( + packageName, PackageManager.GET_PERMISSIONS, + UserHandle.getUserId(uid)); + if (ArrayUtils.contains(packageInfo.requestedPermissions, + Manifest.permission.SYSTEM_ALERT_WINDOW)) { + final int currentMode = mAppOps.unsafeCheckOpRawNoThrow( + AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName); + if (currentMode == AppOpsManager.MODE_DEFAULT) { + mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, + packageName, AppOpsManager.MODE_ALLOWED); + mRestoreSystemAlertWindow = true; + } + } + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Package not found, aborting MediaProjection", e); + return; + } finally { + Binder.restoreCallingIdentity(token); + } + } startProjectionLocked(this); } } @@ -507,6 +539,24 @@ public final class MediaProjectionManagerService extends SystemService + "pid=" + Binder.getCallingPid() + ")"); return; } + if (mRestoreSystemAlertWindow) { + final long token = Binder.clearCallingIdentity(); + try { + // Put the appop back how it was, unless it has been changed from what + // we set it to. + // Note that WindowManager takes care of removing any existing overlay + // windows when we do this. + final int currentMode = mAppOps.unsafeCheckOpRawNoThrow( + AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName); + if (currentMode == AppOpsManager.MODE_ALLOWED) { + mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName, + AppOpsManager.MODE_DEFAULT); + } + mRestoreSystemAlertWindow = false; + } finally { + Binder.restoreCallingIdentity(token); + } + } stopProjectionLocked(this); mToken.unlinkToDeath(mDeathEater, 0); mToken = null;