【SystemUI】启动屏幕录制会自动开启投屏

发布于:2025-08-31 ⋅ 阅读:(20) ⋅ 点赞:(0)

一、问题描述

基于 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 recording
    requestScreenCapture(/* 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 notifications
            mNotificationId = 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() {
    @Override
    public 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);
    }
    @Override
    public void onStop(MediaProjectionInfo info) {
        setProjection(info, false);
    }
};

网站公告

今日签到

点亮在社区的每一天
去签到