【SystemUI】启动屏幕录制会自动开启投屏
一、问题描述
基于 Android 14平台,下拉快速设置中打开屏幕录制,在 3 秒倒计时完成后,屏幕录制 Tile 变成 STATE_ACTIVE 状态,同时投屏 Tile 也变成STATE_ACTIVE 状态,会造成功能混乱的感觉,需要修改功能单独控制。
https://issuetracker.google.com/issues/328539170
二、问题分析
这个问题的核心在于,系统无法区分屏幕录制发起的 MediaProjection
(屏幕内容捕获请求)和真正的投屏发起的 MediaProjection。当屏幕录制开始时,CastController
(投屏控制器) 错误地认为一个投屏会话已经开始,因此点亮了投屏磁贴。投屏发起者一般是其他应用,包名不是 com.android.systemui
。
ScreenRecordTile
-> RecordingController
-> ScreenRecordDialog
开始录制点击事件
src/com/android/systemui/screenrecord/ScreenRecordDialog.java
TextView startBtn = findViewById(R.id.button_start);
startBtn.setOnClickListener(v -> {if (mOnStartRecordingClicked != null) {// Note that it is important to run this callback before dismissing, so that the// callback can disable the dialog exit animation if it wants to.mOnStartRecordingClicked.run();}// Start full-screen recordingrequestScreenCapture(/* captureTarget= */ null);dismiss();
});
开启屏幕录制请求
/*** Starts screen capture after some countdown* @param captureTarget target to capture (could be e.g. a task) or* null to record the whole screen*/
private void requestScreenCapture(@Nullable MediaProjectionCaptureTarget captureTarget) {Context userContext = mUserContextProvider.getUserContext();boolean showTaps = mTapsSwitch.isChecked();ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked()? (ScreenRecordingAudioSource) mOptions.getSelectedItem(): NONE;PendingIntent startIntent = PendingIntent.getForegroundService(userContext,RecordingService.REQUEST_CODE,RecordingService.getStartIntent(userContext, Activity.RESULT_OK,audioMode.ordinal(), showTaps, captureTarget),PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);PendingIntent stopIntent = PendingIntent.getService(userContext,RecordingService.REQUEST_CODE,RecordingService.getStopIntent(userContext),PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent);
}
src/com/android/systemui/screenrecord/RecordingService.java
/*** Get an intent to start the recording service.** @param context Context from the requesting activity* @param resultCode The result code from {@link android.app.Activity#onActivityResult(int, int,* android.content.Intent)}* @param audioSource The ordinal value of the audio source* {@link com.android.systemui.screenrecord.ScreenRecordingAudioSource}* @param showTaps True to make touches visible while recording* @param captureTarget pass this parameter to capture a specific part instead* of the full screen*/
public static Intent getStartIntent(Context context, int resultCode,int audioSource, boolean showTaps,@Nullable MediaProjectionCaptureTarget captureTarget) {return new Intent(context, RecordingService.class).setAction(ACTION_START).putExtra(EXTRA_RESULT_CODE, resultCode).putExtra(EXTRA_AUDIO_SOURCE, audioSource).putExtra(EXTRA_SHOW_TAPS, showTaps).putExtra(EXTRA_CAPTURE_TARGET, captureTarget);
}
倒计时 3 秒后开始录制执行 startIntent
try {startIntent.send(mInteractiveBroadcastOption);mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);IntentFilter stateFilter = new IntentFilter(INTENT_UPDATE_STATE);mBroadcastDispatcher.registerReceiver(mStateChangeReceiver, stateFilter, null,UserHandle.ALL);Log.d(TAG, "sent start intent");
} catch (PendingIntent.CanceledException e) {Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
}
在 RecordingService
执行具体的录制流程
@Override
public int onStartCommand(Intent intent, int flags, int startId) {if (intent == null) {return Service.START_NOT_STICKY;}String action = intent.getAction();Log.d(TAG, "onStartCommand " + action);NotificationChannel channel = new NotificationChannel(CHANNEL_ID,getString(R.string.screenrecord_title),NotificationManager.IMPORTANCE_DEFAULT);channel.setDescription(getString(R.string.screenrecord_channel_description));channel.enableVibration(true);mNotificationManager.createNotificationChannel(channel);int currentUserId = mUserContextTracker.getUserContext().getUserId();UserHandle currentUser = new UserHandle(currentUserId);switch (action) {case ACTION_START:// Get a unique ID for this recording's notificationsmNotificationId = NOTIF_BASE_ID + (int) SystemClock.uptimeMillis();mAudioSource = ScreenRecordingAudioSource.values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)];Log.d(TAG, "recording with audio source " + mAudioSource);mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);MediaProjectionCaptureTarget captureTarget =intent.getParcelableExtra(EXTRA_CAPTURE_TARGET,MediaProjectionCaptureTarget.class);mOriginalShowTaps = Settings.System.getInt(getApplicationContext().getContentResolver(),Settings.System.SHOW_TOUCHES, 0) != 0;setTapsVisible(mShowTaps);mRecorder = new ScreenMediaRecorder(mUserContextTracker.getUserContext(),mMainHandler,currentUserId,mAudioSource,captureTarget,this);if (startRecording()) {updateState(true);createRecordingNotification();mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);} else {updateState(false);createErrorNotification();stopForeground(STOP_FOREGROUND_DETACH);stopSelf();return Service.START_NOT_STICKY;}break;...
}
三、解决方案
在投屏启动前根据包名进行拦截处理
src/com/android/systemui/statusbar/policy/CastControllerImpl.java
private final MediaProjectionManager.Callback mProjectionCallback= new MediaProjectionManager.Callback() {@Overridepublic void onStart(MediaProjectionInfo info) {+ if (info != null && "com.android.systemui".equals(info.getPackageName())) {+ if (DEBUG) Log.d(TAG, "Ignoring projection from screen recording (com.android.systemui)");+ return;+ }setProjection(info, true);}@Overridepublic void onStop(MediaProjectionInfo info) {setProjection(info, false);}
};