summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt5
5 files changed, 209 insertions, 33 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 52d417140e04..0860c207ef20 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -20,7 +20,10 @@ import android.content.Intent
import android.content.res.Configuration
import android.content.res.Resources
import android.media.projection.IMediaProjection
+import android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT
import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
+import android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL
+import android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_TASK
import android.os.Binder
import android.os.Bundle
import android.os.IBinder
@@ -67,6 +70,11 @@ class MediaProjectionAppSelectorActivity(
private lateinit var controller: MediaProjectionAppSelectorController
private lateinit var recentsViewController: MediaProjectionRecentsViewController
private lateinit var component: MediaProjectionAppSelectorComponent
+ // Indicate if we are under the media projection security flow
+ // i.e. when a host app reuses consent token, review the permission and update it to the service
+ private var reviewGrantedConsentRequired = false
+ // If an app is selected, set to true so that we don't send RECORD_CANCEL in onDestroy
+ private var taskSelected = false
override fun getLayoutResource() = R.layout.media_projection_app_selector
@@ -85,6 +93,9 @@ class MediaProjectionAppSelectorActivity(
component.personalProfileUserHandle
)
+ reviewGrantedConsentRequired =
+ intent.getBooleanExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, false)
+
super.onCreate(bundle)
controller.init()
}
@@ -149,6 +160,16 @@ class MediaProjectionAppSelectorActivity(
}
override fun onDestroy() {
+ // onDestroy is also called when an app is selected, in that case we only want to send
+ // RECORD_CONTENT_TASK but not RECORD_CANCEL
+ if (!taskSelected) {
+ // TODO(b/272010156): Return result to PermissionActivity and update service there
+ MediaProjectionServiceHelper.setReviewedConsentIfNeeded(
+ RECORD_CANCEL,
+ reviewGrantedConsentRequired,
+ /* projection= */ null
+ )
+ }
activityLauncher.destroy()
controller.destroy()
super.onDestroy()
@@ -163,6 +184,7 @@ class MediaProjectionAppSelectorActivity(
}
override fun returnSelectedApp(launchCookie: IBinder) {
+ taskSelected = true
if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
// The client requested to return the result in the result receiver instead of
// activity result, let's send the media projection to the result receiver
@@ -174,7 +196,11 @@ class MediaProjectionAppSelectorActivity(
val captureRegion = MediaProjectionCaptureTarget(launchCookie)
val data = Bundle().apply { putParcelable(KEY_CAPTURE_TARGET, captureRegion) }
resultReceiver.send(RESULT_OK, data)
+ // TODO(b/279175710): Ensure consent result is always set here. Skipping this for now
+ // in ScreenMediaRecorder, since we know the permission grant (projection) is never
+ // reused in that scenario.
} else {
+ // TODO(b/272010156): Return result to PermissionActivity and update service there
// Return the media projection instance as activity result
val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
@@ -185,6 +211,11 @@ class MediaProjectionAppSelectorActivity(
intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
setResult(RESULT_OK, intent)
setForceSendResultForMediaProjection()
+ MediaProjectionServiceHelper.setReviewedConsentIfNeeded(
+ RECORD_CONTENT_TASK,
+ reviewGrantedConsentRequired,
+ projection
+ )
}
finish()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index ccddd1d359b7..e217e36d1051 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -16,11 +16,16 @@
package com.android.systemui.media;
+import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
+import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
+import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
+import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static com.android.systemui.screenrecord.ScreenShareOptionKt.ENTIRE_SCREEN;
import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
@@ -30,12 +35,10 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.media.projection.IMediaProjection;
-import android.media.projection.IMediaProjectionManager;
import android.media.projection.MediaProjectionManager;
+import android.media.projection.ReviewGrantedConsentResult;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.text.BidiFormatter;
import android.text.SpannableString;
@@ -55,10 +58,10 @@ import com.android.systemui.screenrecord.ScreenShareOption;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.Utils;
-import javax.inject.Inject;
-
import dagger.Lazy;
+import javax.inject.Inject;
+
public class MediaProjectionPermissionActivity extends Activity
implements DialogInterface.OnClickListener {
private static final String TAG = "MediaProjectionPermissionActivity";
@@ -70,10 +73,13 @@ public class MediaProjectionPermissionActivity extends Activity
private String mPackageName;
private int mUid;
- private IMediaProjectionManager mService;
private AlertDialog mDialog;
+ // Indicates if user must review already-granted consent that the MediaProjection app is
+ // attempting to re-use.
+ private boolean mReviewGrantedConsentRequired = false;
+
@Inject
public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
@@ -85,13 +91,23 @@ public class MediaProjectionPermissionActivity extends Activity
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ final Intent launchingIntent = getIntent();
+ mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra(
+ EXTRA_USER_REVIEW_GRANTED_CONSENT, false);
+
mPackageName = getCallingPackage();
- IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
- mService = IMediaProjectionManager.Stub.asInterface(b);
+ // This activity is launched directly by an app, or system server. System server provides
+ // the package name through the intent if so.
if (mPackageName == null) {
- finish();
- return;
+ if (launchingIntent.hasExtra(EXTRA_PACKAGE_REUSING_GRANTED_CONSENT)) {
+ mPackageName = launchingIntent.getStringExtra(
+ EXTRA_PACKAGE_REUSING_GRANTED_CONSENT);
+ } else {
+ setResult(RESULT_CANCELED);
+ finish(RECORD_CANCEL, /* projection= */ null);
+ return;
+ }
}
PackageManager packageManager = getPackageManager();
@@ -100,25 +116,36 @@ public class MediaProjectionPermissionActivity extends Activity
aInfo = packageManager.getApplicationInfo(mPackageName, 0);
mUid = aInfo.uid;
} catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "unable to look up package name", e);
- finish();
+ Log.e(TAG, "Unable to look up package name", e);
+ setResult(RESULT_CANCELED);
+ finish(RECORD_CANCEL, /* projection= */ null);
return;
}
try {
- if (mService.hasProjectionPermission(mUid, mPackageName)) {
- setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
- finish();
+ if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) {
+ final IMediaProjection projection =
+ MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
+ mReviewGrantedConsentRequired);
+ // Automatically grant consent if a system-privileged component is recording.
+ final Intent intent = new Intent();
+ intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
+ projection.asBinder());
+ setResult(RESULT_OK, intent);
+ finish(RECORD_CONTENT_DISPLAY, projection);
return;
}
} catch (RemoteException e) {
Log.e(TAG, "Error checking projection permissions", e);
- finish();
+ setResult(RESULT_CANCELED);
+ finish(RECORD_CANCEL, /* projection= */ null);
return;
}
if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
if (showScreenCaptureDisabledDialogIfNeeded()) {
+ setResult(RESULT_CANCELED);
+ finish(RECORD_CANCEL, /* projection= */ null);
return;
}
}
@@ -178,7 +205,7 @@ public class MediaProjectionPermissionActivity extends Activity
ScreenShareOption selectedOption =
((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption();
grantMediaProjectionPermission(selectedOption.getMode());
- }, appName);
+ }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName);
} else {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
R.style.Theme_SystemUI_Dialog)
@@ -191,7 +218,6 @@ public class MediaProjectionPermissionActivity extends Activity
}
setUpDialog(mDialog);
-
mDialog.show();
}
@@ -207,6 +233,12 @@ public class MediaProjectionPermissionActivity extends Activity
public void onClick(DialogInterface dialog, int which) {
if (which == AlertDialog.BUTTON_POSITIVE) {
grantMediaProjectionPermission(ENTIRE_SCREEN);
+ } else {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ setResult(RESULT_CANCELED);
+ finish(RECORD_CANCEL, /* projection= */ null);
}
}
@@ -240,15 +272,25 @@ public class MediaProjectionPermissionActivity extends Activity
private void grantMediaProjectionPermission(int screenShareMode) {
try {
if (screenShareMode == ENTIRE_SCREEN) {
- setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
+ final IMediaProjection projection =
+ MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
+ mReviewGrantedConsentRequired);
+ final Intent intent = new Intent();
+ intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
+ projection.asBinder());
+ setResult(RESULT_OK, intent);
+ finish(RECORD_CONTENT_DISPLAY, projection);
}
if (isPartialScreenSharingEnabled() && screenShareMode == SINGLE_APP) {
- IMediaProjection projection = createProjection(mUid, mPackageName);
- final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
+ IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(
+ mUid, mPackageName, mReviewGrantedConsentRequired);
+ final Intent intent = new Intent(this,
+ MediaProjectionAppSelectorActivity.class);
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
projection.asBinder());
intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
getHostUserHandle());
+ intent.putExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, mReviewGrantedConsentRequired);
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
// Start activity from the current foreground user to avoid creating a separate
@@ -259,11 +301,11 @@ public class MediaProjectionPermissionActivity extends Activity
} catch (RemoteException e) {
Log.e(TAG, "Error granting projection permission", e);
setResult(RESULT_CANCELED);
+ finish(RECORD_CANCEL, /* projection= */ null);
} finally {
if (mDialog != null) {
mDialog.dismiss();
}
- finish();
}
}
@@ -271,22 +313,22 @@ public class MediaProjectionPermissionActivity extends Activity
return UserHandle.getUserHandleForUid(getLaunchedFromUid());
}
- private IMediaProjection createProjection(int uid, String packageName) throws RemoteException {
- return mService.createProjection(uid, packageName,
- MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
+ @Override
+ public void finish() {
+ // Default to cancelling recording when user needs to review consent.
+ finish(RECORD_CANCEL, /* projection= */ null);
}
- private Intent getMediaProjectionIntent(int uid, String packageName)
- throws RemoteException {
- IMediaProjection projection = createProjection(uid, packageName);
- Intent intent = new Intent();
- intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
- return intent;
+ private void finish(@ReviewGrantedConsentResult int consentResult,
+ @Nullable IMediaProjection projection) {
+ MediaProjectionServiceHelper.setReviewedConsentIfNeeded(
+ consentResult, mReviewGrantedConsentRequired, projection);
+ super.finish();
}
private void onDialogDismissedOrCancelled(DialogInterface dialogInterface) {
if (!isFinishing()) {
- finish();
+ finish(RECORD_CANCEL, /* projection= */ null);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt
new file mode 100644
index 000000000000..9e616e2355e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.systemui.media
+
+import android.content.Context
+import android.media.projection.IMediaProjection
+import android.media.projection.IMediaProjectionManager
+import android.media.projection.MediaProjectionManager
+import android.media.projection.ReviewGrantedConsentResult
+import android.os.RemoteException
+import android.os.ServiceManager
+import android.util.Log
+
+/**
+ * Helper class that handles the media projection service related actions. It simplifies invoking
+ * the MediaProjectionManagerService and updating the permission consent.
+ */
+class MediaProjectionServiceHelper {
+ companion object {
+ private const val TAG = "MediaProjectionServiceHelper"
+ private val service =
+ IMediaProjectionManager.Stub.asInterface(
+ ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)
+ )
+
+ @JvmStatic
+ @Throws(RemoteException::class)
+ fun hasProjectionPermission(uid: Int, packageName: String) =
+ service.hasProjectionPermission(uid, packageName)
+
+ @JvmStatic
+ @Throws(RemoteException::class)
+ fun createOrReuseProjection(
+ uid: Int,
+ packageName: String,
+ reviewGrantedConsentRequired: Boolean
+ ): IMediaProjection {
+ val existingProjection =
+ if (reviewGrantedConsentRequired) service.getProjection(uid, packageName) else null
+ return existingProjection
+ ?: service.createProjection(
+ uid,
+ packageName,
+ MediaProjectionManager.TYPE_SCREEN_CAPTURE,
+ false /* permanentGrant */
+ )
+ }
+
+ /**
+ * This method is called when a host app reuses the consent token. If the token is being
+ * used more than once, ask the user to review their consent and send the reviewed result.
+ *
+ * @param consentResult consent result to update
+ * @param reviewGrantedConsentRequired if user must review already-granted consent that the
+ * host app is attempting to reuse
+ * @param projection projection token associated with the consent result, or null if the
+ * result is for cancelling.
+ */
+ @JvmStatic
+ fun setReviewedConsentIfNeeded(
+ @ReviewGrantedConsentResult consentResult: Int,
+ reviewGrantedConsentRequired: Boolean,
+ projection: IMediaProjection?
+ ) {
+ // Only send the result to the server, when the user needed to review the re-used
+ // consent token.
+ if (
+ reviewGrantedConsentRequired && consentResult != ReviewGrantedConsentResult.UNKNOWN
+ ) {
+ try {
+ service.setUserReviewGrantedConsentResult(consentResult, projection)
+ } catch (e: RemoteException) {
+ // If we are unable to pass back the result, capture continues with blank frames
+ Log.e(TAG, "Unable to set required consent result for token re-use", e)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
index db0052a4d99e..f63bf07a5a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
@@ -43,6 +43,7 @@ open class BaseScreenSharePermissionDialog(
) : SystemUIDialog(context), AdapterView.OnItemSelectedListener {
private lateinit var dialogTitle: TextView
private lateinit var startButton: TextView
+ private lateinit var cancelButton: TextView
private lateinit var warning: TextView
private lateinit var screenShareModeSpinner: Spinner
var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
@@ -57,7 +58,7 @@ open class BaseScreenSharePermissionDialog(
dialogTitle = findViewById(R.id.screen_share_dialog_title)
warning = findViewById(R.id.text_warning)
startButton = findViewById(R.id.button_start)
- findViewById<TextView>(R.id.button_cancel).setOnClickListener { dismiss() }
+ cancelButton = findViewById(R.id.button_cancel)
updateIcon()
initScreenShareOptions()
createOptionsView(getOptionsViewLayoutId())
@@ -117,6 +118,10 @@ open class BaseScreenSharePermissionDialog(
startButton.setOnClickListener(listener)
}
+ protected fun setCancelButtonOnClickListener(listener: View.OnClickListener?) {
+ cancelButton.setOnClickListener(listener)
+ }
+
// Create additional options that is shown under the share mode spinner
// Eg. the audio and tap toggles in SysUI Recorder
@LayoutRes protected open fun getOptionsViewLayoutId(): Int? = null
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
index c5a82ce110de..201557c03e48 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
@@ -23,6 +23,7 @@ import com.android.systemui.R
class MediaProjectionPermissionDialog(
context: Context?,
private val onStartRecordingClicked: Runnable,
+ private val onCancelClicked: Runnable,
private val appName: String?
) : BaseScreenSharePermissionDialog(context, createOptionList(appName), appName) {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -39,6 +40,10 @@ class MediaProjectionPermissionDialog(
onStartRecordingClicked.run()
dismiss()
}
+ setCancelButtonOnClickListener {
+ onCancelClicked.run()
+ dismiss()
+ }
}
companion object {