[MediaProjection] Re-show permission dialog when token reuse
Regain user's consent if a token is being reuse, and update the consent to the server accordingly Fixes: 253218374 Test: manual Change-Id: Ic4cdc1f903ee55ad8eef861811f2d896eb345b25
This commit is contained in:
@@ -20,7 +20,10 @@ import android.content.Intent
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.media.projection.IMediaProjection
|
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.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.Binder
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
@@ -67,6 +70,11 @@ class MediaProjectionAppSelectorActivity(
|
|||||||
private lateinit var controller: MediaProjectionAppSelectorController
|
private lateinit var controller: MediaProjectionAppSelectorController
|
||||||
private lateinit var recentsViewController: MediaProjectionRecentsViewController
|
private lateinit var recentsViewController: MediaProjectionRecentsViewController
|
||||||
private lateinit var component: MediaProjectionAppSelectorComponent
|
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
|
override fun getLayoutResource() = R.layout.media_projection_app_selector
|
||||||
|
|
||||||
@@ -85,6 +93,9 @@ class MediaProjectionAppSelectorActivity(
|
|||||||
component.personalProfileUserHandle
|
component.personalProfileUserHandle
|
||||||
)
|
)
|
||||||
|
|
||||||
|
reviewGrantedConsentRequired =
|
||||||
|
intent.getBooleanExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, false)
|
||||||
|
|
||||||
super.onCreate(bundle)
|
super.onCreate(bundle)
|
||||||
controller.init()
|
controller.init()
|
||||||
}
|
}
|
||||||
@@ -149,6 +160,16 @@ class MediaProjectionAppSelectorActivity(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
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()
|
activityLauncher.destroy()
|
||||||
controller.destroy()
|
controller.destroy()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
@@ -163,6 +184,7 @@ class MediaProjectionAppSelectorActivity(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun returnSelectedApp(launchCookie: IBinder) {
|
override fun returnSelectedApp(launchCookie: IBinder) {
|
||||||
|
taskSelected = true
|
||||||
if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
|
if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
|
||||||
// The client requested to return the result in the result receiver instead of
|
// 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
|
// activity result, let's send the media projection to the result receiver
|
||||||
@@ -174,7 +196,11 @@ class MediaProjectionAppSelectorActivity(
|
|||||||
val captureRegion = MediaProjectionCaptureTarget(launchCookie)
|
val captureRegion = MediaProjectionCaptureTarget(launchCookie)
|
||||||
val data = Bundle().apply { putParcelable(KEY_CAPTURE_TARGET, captureRegion) }
|
val data = Bundle().apply { putParcelable(KEY_CAPTURE_TARGET, captureRegion) }
|
||||||
resultReceiver.send(RESULT_OK, data)
|
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 {
|
} else {
|
||||||
|
// TODO(b/272010156): Return result to PermissionActivity and update service there
|
||||||
// Return the media projection instance as activity result
|
// Return the media projection instance as activity result
|
||||||
val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
|
val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
|
||||||
val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
|
val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
|
||||||
@@ -185,6 +211,11 @@ class MediaProjectionAppSelectorActivity(
|
|||||||
intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
|
intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
|
||||||
setResult(RESULT_OK, intent)
|
setResult(RESULT_OK, intent)
|
||||||
setForceSendResultForMediaProjection()
|
setForceSendResultForMediaProjection()
|
||||||
|
MediaProjectionServiceHelper.setReviewedConsentIfNeeded(
|
||||||
|
RECORD_CONTENT_TASK,
|
||||||
|
reviewGrantedConsentRequired,
|
||||||
|
projection
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
finish()
|
finish()
|
||||||
|
|||||||
@@ -16,11 +16,16 @@
|
|||||||
|
|
||||||
package com.android.systemui.media;
|
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 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.ENTIRE_SCREEN;
|
||||||
import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
|
import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
@@ -30,12 +35,10 @@ import android.content.pm.ApplicationInfo;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.media.projection.IMediaProjection;
|
import android.media.projection.IMediaProjection;
|
||||||
import android.media.projection.IMediaProjectionManager;
|
|
||||||
import android.media.projection.MediaProjectionManager;
|
import android.media.projection.MediaProjectionManager;
|
||||||
|
import android.media.projection.ReviewGrantedConsentResult;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.ServiceManager;
|
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.text.BidiFormatter;
|
import android.text.BidiFormatter;
|
||||||
import android.text.SpannableString;
|
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.statusbar.phone.SystemUIDialog;
|
||||||
import com.android.systemui.util.Utils;
|
import com.android.systemui.util.Utils;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
public class MediaProjectionPermissionActivity extends Activity
|
public class MediaProjectionPermissionActivity extends Activity
|
||||||
implements DialogInterface.OnClickListener {
|
implements DialogInterface.OnClickListener {
|
||||||
private static final String TAG = "MediaProjectionPermissionActivity";
|
private static final String TAG = "MediaProjectionPermissionActivity";
|
||||||
@@ -70,10 +73,13 @@ public class MediaProjectionPermissionActivity extends Activity
|
|||||||
|
|
||||||
private String mPackageName;
|
private String mPackageName;
|
||||||
private int mUid;
|
private int mUid;
|
||||||
private IMediaProjectionManager mService;
|
|
||||||
|
|
||||||
private AlertDialog mDialog;
|
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
|
@Inject
|
||||||
public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
|
public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
|
||||||
Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
|
Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
|
||||||
@@ -85,14 +91,24 @@ public class MediaProjectionPermissionActivity extends Activity
|
|||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
super.onCreate(icicle);
|
super.onCreate(icicle);
|
||||||
|
|
||||||
mPackageName = getCallingPackage();
|
final Intent launchingIntent = getIntent();
|
||||||
IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
|
mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra(
|
||||||
mService = IMediaProjectionManager.Stub.asInterface(b);
|
EXTRA_USER_REVIEW_GRANTED_CONSENT, false);
|
||||||
|
|
||||||
|
mPackageName = getCallingPackage();
|
||||||
|
|
||||||
|
// 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) {
|
if (mPackageName == null) {
|
||||||
finish();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PackageManager packageManager = getPackageManager();
|
PackageManager packageManager = getPackageManager();
|
||||||
ApplicationInfo aInfo;
|
ApplicationInfo aInfo;
|
||||||
@@ -100,25 +116,36 @@ public class MediaProjectionPermissionActivity extends Activity
|
|||||||
aInfo = packageManager.getApplicationInfo(mPackageName, 0);
|
aInfo = packageManager.getApplicationInfo(mPackageName, 0);
|
||||||
mUid = aInfo.uid;
|
mUid = aInfo.uid;
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
Log.e(TAG, "unable to look up package name", e);
|
Log.e(TAG, "Unable to look up package name", e);
|
||||||
finish();
|
setResult(RESULT_CANCELED);
|
||||||
|
finish(RECORD_CANCEL, /* projection= */ null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (mService.hasProjectionPermission(mUid, mPackageName)) {
|
if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) {
|
||||||
setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
|
final IMediaProjection projection =
|
||||||
finish();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Log.e(TAG, "Error checking projection permissions", e);
|
Log.e(TAG, "Error checking projection permissions", e);
|
||||||
finish();
|
setResult(RESULT_CANCELED);
|
||||||
|
finish(RECORD_CANCEL, /* projection= */ null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
|
if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
|
||||||
if (showScreenCaptureDisabledDialogIfNeeded()) {
|
if (showScreenCaptureDisabledDialogIfNeeded()) {
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
finish(RECORD_CANCEL, /* projection= */ null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +205,7 @@ public class MediaProjectionPermissionActivity extends Activity
|
|||||||
ScreenShareOption selectedOption =
|
ScreenShareOption selectedOption =
|
||||||
((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption();
|
((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption();
|
||||||
grantMediaProjectionPermission(selectedOption.getMode());
|
grantMediaProjectionPermission(selectedOption.getMode());
|
||||||
}, appName);
|
}, () -> finish(RECORD_CANCEL, /* projection= */ null), appName);
|
||||||
} else {
|
} else {
|
||||||
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
|
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
|
||||||
R.style.Theme_SystemUI_Dialog)
|
R.style.Theme_SystemUI_Dialog)
|
||||||
@@ -191,7 +218,6 @@ public class MediaProjectionPermissionActivity extends Activity
|
|||||||
}
|
}
|
||||||
|
|
||||||
setUpDialog(mDialog);
|
setUpDialog(mDialog);
|
||||||
|
|
||||||
mDialog.show();
|
mDialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +233,12 @@ public class MediaProjectionPermissionActivity extends Activity
|
|||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
if (which == AlertDialog.BUTTON_POSITIVE) {
|
if (which == AlertDialog.BUTTON_POSITIVE) {
|
||||||
grantMediaProjectionPermission(ENTIRE_SCREEN);
|
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) {
|
private void grantMediaProjectionPermission(int screenShareMode) {
|
||||||
try {
|
try {
|
||||||
if (screenShareMode == ENTIRE_SCREEN) {
|
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) {
|
if (isPartialScreenSharingEnabled() && screenShareMode == SINGLE_APP) {
|
||||||
IMediaProjection projection = createProjection(mUid, mPackageName);
|
IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(
|
||||||
final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
|
mUid, mPackageName, mReviewGrantedConsentRequired);
|
||||||
|
final Intent intent = new Intent(this,
|
||||||
|
MediaProjectionAppSelectorActivity.class);
|
||||||
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
|
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
|
||||||
projection.asBinder());
|
projection.asBinder());
|
||||||
intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
|
intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
|
||||||
getHostUserHandle());
|
getHostUserHandle());
|
||||||
|
intent.putExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, mReviewGrantedConsentRequired);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||||
|
|
||||||
// Start activity from the current foreground user to avoid creating a separate
|
// Start activity from the current foreground user to avoid creating a separate
|
||||||
@@ -259,11 +301,11 @@ public class MediaProjectionPermissionActivity extends Activity
|
|||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Log.e(TAG, "Error granting projection permission", e);
|
Log.e(TAG, "Error granting projection permission", e);
|
||||||
setResult(RESULT_CANCELED);
|
setResult(RESULT_CANCELED);
|
||||||
|
finish(RECORD_CANCEL, /* projection= */ null);
|
||||||
} finally {
|
} finally {
|
||||||
if (mDialog != null) {
|
if (mDialog != null) {
|
||||||
mDialog.dismiss();
|
mDialog.dismiss();
|
||||||
}
|
}
|
||||||
finish();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,22 +313,22 @@ public class MediaProjectionPermissionActivity extends Activity
|
|||||||
return UserHandle.getUserHandleForUid(getLaunchedFromUid());
|
return UserHandle.getUserHandleForUid(getLaunchedFromUid());
|
||||||
}
|
}
|
||||||
|
|
||||||
private IMediaProjection createProjection(int uid, String packageName) throws RemoteException {
|
@Override
|
||||||
return mService.createProjection(uid, packageName,
|
public void finish() {
|
||||||
MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
|
// Default to cancelling recording when user needs to review consent.
|
||||||
|
finish(RECORD_CANCEL, /* projection= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent getMediaProjectionIntent(int uid, String packageName)
|
private void finish(@ReviewGrantedConsentResult int consentResult,
|
||||||
throws RemoteException {
|
@Nullable IMediaProjection projection) {
|
||||||
IMediaProjection projection = createProjection(uid, packageName);
|
MediaProjectionServiceHelper.setReviewedConsentIfNeeded(
|
||||||
Intent intent = new Intent();
|
consentResult, mReviewGrantedConsentRequired, projection);
|
||||||
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
|
super.finish();
|
||||||
return intent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDialogDismissedOrCancelled(DialogInterface dialogInterface) {
|
private void onDialogDismissedOrCancelled(DialogInterface dialogInterface) {
|
||||||
if (!isFinishing()) {
|
if (!isFinishing()) {
|
||||||
finish();
|
finish(RECORD_CANCEL, /* projection= */ null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,7 @@ open class BaseScreenSharePermissionDialog(
|
|||||||
) : SystemUIDialog(context), AdapterView.OnItemSelectedListener {
|
) : SystemUIDialog(context), AdapterView.OnItemSelectedListener {
|
||||||
private lateinit var dialogTitle: TextView
|
private lateinit var dialogTitle: TextView
|
||||||
private lateinit var startButton: TextView
|
private lateinit var startButton: TextView
|
||||||
|
private lateinit var cancelButton: TextView
|
||||||
private lateinit var warning: TextView
|
private lateinit var warning: TextView
|
||||||
private lateinit var screenShareModeSpinner: Spinner
|
private lateinit var screenShareModeSpinner: Spinner
|
||||||
var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
|
var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
|
||||||
@@ -57,7 +58,7 @@ open class BaseScreenSharePermissionDialog(
|
|||||||
dialogTitle = findViewById(R.id.screen_share_dialog_title)
|
dialogTitle = findViewById(R.id.screen_share_dialog_title)
|
||||||
warning = findViewById(R.id.text_warning)
|
warning = findViewById(R.id.text_warning)
|
||||||
startButton = findViewById(R.id.button_start)
|
startButton = findViewById(R.id.button_start)
|
||||||
findViewById<TextView>(R.id.button_cancel).setOnClickListener { dismiss() }
|
cancelButton = findViewById(R.id.button_cancel)
|
||||||
updateIcon()
|
updateIcon()
|
||||||
initScreenShareOptions()
|
initScreenShareOptions()
|
||||||
createOptionsView(getOptionsViewLayoutId())
|
createOptionsView(getOptionsViewLayoutId())
|
||||||
@@ -117,6 +118,10 @@ open class BaseScreenSharePermissionDialog(
|
|||||||
startButton.setOnClickListener(listener)
|
startButton.setOnClickListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun setCancelButtonOnClickListener(listener: View.OnClickListener?) {
|
||||||
|
cancelButton.setOnClickListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
// Create additional options that is shown under the share mode spinner
|
// Create additional options that is shown under the share mode spinner
|
||||||
// Eg. the audio and tap toggles in SysUI Recorder
|
// Eg. the audio and tap toggles in SysUI Recorder
|
||||||
@LayoutRes protected open fun getOptionsViewLayoutId(): Int? = null
|
@LayoutRes protected open fun getOptionsViewLayoutId(): Int? = null
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.android.systemui.R
|
|||||||
class MediaProjectionPermissionDialog(
|
class MediaProjectionPermissionDialog(
|
||||||
context: Context?,
|
context: Context?,
|
||||||
private val onStartRecordingClicked: Runnable,
|
private val onStartRecordingClicked: Runnable,
|
||||||
|
private val onCancelClicked: Runnable,
|
||||||
private val appName: String?
|
private val appName: String?
|
||||||
) : BaseScreenSharePermissionDialog(context, createOptionList(appName), appName) {
|
) : BaseScreenSharePermissionDialog(context, createOptionList(appName), appName) {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -39,6 +40,10 @@ class MediaProjectionPermissionDialog(
|
|||||||
onStartRecordingClicked.run()
|
onStartRecordingClicked.run()
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
setCancelButtonOnClickListener {
|
||||||
|
onCancelClicked.run()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
Reference in New Issue
Block a user