android13#screen record

114 阅读8分钟

1.简介

简单学习下系统的屏幕录制功能,就是下拉状态栏的screenRecord,如下图

image.png

2.ScreenRecordTile.java

2.1.handleClick

    private final RecordingController mController;
    
    protected void handleClick(@Nullable View view) {
        if (mController.isStarting()) {
        //启动中,就是倒计时还没结束的时候,取消倒计时
            cancelCountdown();//见3.3
        } else if (mController.isRecording()) {
        //已经在录制中,停止录制,件3.4
            stopRecording();
        } else {
        //显示弹框,见2.2
            mUiHandler.post(() -> showPrompt(view));
        }
        //刷新tile的状态
        refreshState();
    }

2.2.showPrompt

    private void showPrompt(@Nullable View view) {
        //是否需要显示动画,锁屏界面不显示
        boolean shouldAnimateFromView = view != null && !mKeyguardStateController.isShowing();

        //开始录制的点击事件回调
        Runnable onStartRecordingClicked = () -> {
            //
            mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
            //收起下拉面板
            getHost().collapsePanels();
        };
        //创建弹框,见3.1
        Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
                mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked);
            
        ActivityStarter.OnDismissAction dismissAction = () -> {
            //显示dialog
            //非锁屏界面有动画,
            if (shouldAnimateFromView) {
                mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj(
                        InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG));
            } else {
            //锁屏界面没有动画
                dialog.show();
            }
            return false;
        };
        //解锁的条件下执行上边的dismiss操作
        mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false /* requiresShadeOpen */,
                true /* afterKeyguardDone */);
    }

3.RecordingController.java

    protected static final String INTENT_UPDATE_STATE =
            "com.android.systemui.screenrecord.UPDATE_STATE";
    protected static final String EXTRA_STATE = "extra_state";

3.1.createScreenRecordDialog

创建一个录屏对话框,我们这里看第二个

    public Dialog createScreenRecordDialog(Context context, FeatureFlags flags,
                                           DialogLaunchAnimator dialogLaunchAnimator,
                                           ActivityStarter activityStarter,
                                           @Nullable Runnable onStartRecordingClicked) {
           //部分屏幕共享可用的话,默认是false吧
        return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
                ? new ScreenRecordPermissionDialog(context, this, activityStarter,
                        dialogLaunchAnimator, mUserContextProvider, onStartRecordingClicked)
                : new ScreenRecordDialog(context, this, activityStarter,
                mUserContextProvider, flags, dialogLaunchAnimator, onStartRecordingClicked);
    }

3.2.startCountdown

开启一个倒计时,4.1.2里用到,就是点击record按钮以后会走这里

    public void startCountdown(long ms, long interval, PendingIntent startIntent,
            PendingIntent stopIntent) {
        mIsStarting = true;
        mStopIntent = stopIntent;

        mCountDownTimer = new CountDownTimer(ms, interval) {
            @Override
            public void onTick(long millisUntilFinished) {
                for (RecordingStateChangeCallback cb : mListeners) {
                    cb.onCountdown(millisUntilFinished);
                }
            }

            @Override
            public void onFinish() {
                //倒计时结束,开始录制
                mIsStarting = false;
                mIsRecording = true;
                for (RecordingStateChangeCallback cb : mListeners) {
                    cb.onCountdownEnd();
                }
                try {
                //start意图执行
                    startIntent.send();
                    //添加用户改变监听
                    mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
                    //注册更新状态广播
                    IntentFilter stateFilter = new IntentFilter(INTENT_UPDATE_STATE);
                    mBroadcastDispatcher.registerReceiver(mStateChangeReceiver, stateFilter, null,
                            UserHandle.ALL);
                    
                } 
            }
        };

        mCountDownTimer.start();
    }

>1.mStateChangeReceiver

录屏状态监听

    protected final BroadcastReceiver mStateChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent != null && INTENT_UPDATE_STATE.equals(intent.getAction())) {
                if (intent.hasExtra(EXTRA_STATE)) {
                    boolean state = intent.getBooleanExtra(EXTRA_STATE, false);
                    //见补充2,更新录屏状态
                    updateState(state);
                }
            }
        }
    };

>2.updateState

    public synchronized void updateState(boolean isRecording) {
        if (!isRecording && mIsRecording) {
            //停止录制屏幕的时候,移除监听
            mUserTracker.removeCallback(mUserChangedCallback);
            mBroadcastDispatcher.unregisterReceiver(mStateChangeReceiver);
        }
        mIsRecording = isRecording;
        for (RecordingStateChangeCallback cb : mListeners) {
        //回调更新录制状态
            if (isRecording) {
                cb.onRecordingStart();
            } else {
                cb.onRecordingEnd();
            }
        }
    }

3.3.cancelCountdown

取消倒计时

    public void cancelCountdown() {
        if (mCountDownTimer != null) {
            mCountDownTimer.cancel();
        }
        mIsStarting = false;

        for (RecordingStateChangeCallback cb : mListeners) {
            cb.onCountdownEnd();
        }
    }

3.4.stopRecording

    public void stopRecording() {
        try {
            if (mStopIntent != null) {
            //执行stop意图,数据是3.2赋值的
                mStopIntent.send();
            } else {
                Log.e(TAG, "Stop intent was null");
            }
            //录制状态设置为false
            updateState(false);
        } 
    }

4.ScreenRecordDialog

public class ScreenRecordDialog extends SystemUIDialog {

弹框效果见4.1.1

4.1.onCreate

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Window window = getWindow();

        window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);

        window.setGravity(Gravity.CENTER);
        setTitle(R.string.screenrecord_name);
        //布局见补充1
        setContentView(R.layout.screen_record_dialog);
        //点击取消按钮,隐藏对话框即可
        TextView cancelBtn = findViewById(R.id.button_cancel);
        cancelBtn.setOnClickListener(v -> dismiss());
        //开始按钮点击
        TextView startBtn = findViewById(R.id.button_start);
        startBtn.setOnClickListener(v -> {
            if (mOnStartRecordingClicked != null) {
                //回调,构造方法里传递的,具体见2.2
                mOnStartRecordingClicked.run();
            }

            //见补充2,开始请求录屏
            requestScreenCapture(/* captureTarget= */ null);
            dismiss();
        });

        mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
        mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
        mOptions = findViewById(R.id.screen_recording_options);
        //音频类型数据
        ArrayAdapter a = new ScreenRecordingAdapter(getContext().getApplicationContext(),
                android.R.layout.simple_spinner_dropdown_item,
                MODES);
        a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        mOptions.setAdapter(a);
        mOptions.setOnItemClickListenerInt((parent, view, position, id) -> {
            //选择了任何一种音频,开关自动打开
            mAudioSwitch.setChecked(true);
        });
    }

>1.screen_record_dialog.xml

image.png

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <!-- Scrollview is necessary to fit everything in landscape layout -->
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingStart="@dimen/dialog_side_padding"
            android:paddingEnd="@dimen/dialog_side_padding"
            android:paddingTop="@dimen/dialog_top_padding"
            android:paddingBottom="@dimen/dialog_bottom_padding"
            android:orientation="vertical">

            <!-- Header,图上第一部分:图片+标题+描述 -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                android:gravity="center">
                <ImageView
                    android:layout_width="@dimen/screenrecord_logo_size"
                    android:layout_height="@dimen/screenrecord_logo_size"
                    android:src="@drawable/ic_screenrecord"
                    android:tint="@color/screenrecord_icon_color"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textAppearance="?android:attr/textAppearanceLarge"
                    android:fontFamily="@*android:string/config_headlineFontFamily"
                    android:text="@string/screenrecord_start_label"
                    android:layout_marginTop="22dp"
                    android:layout_marginBottom="15dp"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/screenrecord_description"
                    android:textAppearance="?android:attr/textAppearanceSmall"
                    android:textColor="?android:textColorSecondary"
                    android:gravity="center"
                    android:layout_marginBottom="20dp"/>

                <!-- Options 图上第二部分,音频相关-->
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">
                    <ImageView
                        android:layout_width="@dimen/screenrecord_option_icon_size"
                        android:layout_height="@dimen/screenrecord_option_icon_size"
                        android:src="@drawable/ic_mic_26dp"
                        android:tint="?android:attr/textColorSecondary"
                        android:layout_gravity="center"
                        android:layout_weight="0"
                        android:layout_marginRight="@dimen/screenrecord_option_padding"/>
                    <Spinner
                        android:id="@+id/screen_recording_options"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:minHeight="48dp"
                        android:layout_weight="1"
                        android:popupBackground="@drawable/screenrecord_spinner_background"
                        android:dropDownWidth="274dp"
                        android:prompt="@string/screenrecord_audio_label"/>
                    <Switch
                        android:layout_width="wrap_content"
                        android:minWidth="48dp"
                        android:layout_height="48dp"
                        android:layout_weight="0"
                        android:layout_gravity="end"
                        android:contentDescription="@string/screenrecord_audio_label"
                        android:id="@+id/screenrecord_audio_switch"
                        style="@style/ScreenRecord.Switch"/>
                </LinearLayout>
            <!--图上第二部分,屏幕上是否显示触摸效果-->
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:layout_marginTop="@dimen/screenrecord_option_padding">
                    <ImageView
                        android:layout_width="@dimen/screenrecord_option_icon_size"
                        android:layout_height="@dimen/screenrecord_option_icon_size"
                        android:layout_weight="0"
                        android:src="@drawable/ic_touch"
                        android:tint="?android:attr/textColorSecondary"
                        android:layout_gravity="center"
                        android:layout_marginRight="@dimen/screenrecord_option_padding"/>
                    <TextView
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:minHeight="48dp"
                        android:layout_weight="1"
                        android:layout_gravity="fill_vertical"
                        android:gravity="center_vertical"
                        android:text="@string/screenrecord_taps_label"
                        android:textAppearance="?android:attr/textAppearanceMedium"
                        android:fontFamily="@*android:string/config_headlineFontFamily"
                        android:textColor="?android:attr/textColorPrimary"
                        android:importantForAccessibility="no"/>
                    <Switch
                        android:layout_width="wrap_content"
                        android:minWidth="48dp"
                        android:layout_height="48dp"
                        android:layout_weight="0"
                        android:id="@+id/screenrecord_taps_switch"
                        android:contentDescription="@string/screenrecord_taps_label"
                        style="@style/ScreenRecord.Switch"/>
                </LinearLayout>
            </LinearLayout>

            <!-- Buttons ,取消以及开始按钮-->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:layout_marginTop="36dp">
                <TextView
                    android:id="@+id/button_cancel"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="0"
                    android:layout_gravity="start"
                    android:text="@string/cancel"
                    style="@style/Widget.Dialog.Button.BorderButton" />
                <Space
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"/>
                <TextView
                    android:id="@+id/button_start"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="0"
                    android:layout_gravity="end"
                    android:text="@string/screenrecord_start"
                    style="@style/Widget.Dialog.Button" />
            </LinearLayout>
        </LinearLayout>
    </ScrollView>
</LinearLayout>

>2.requestScreenCapture

请求录屏,交给小节3处理

    private static final long DELAY_MS = 3000;//录屏开始倒计时
    private static final long INTERVAL_MS = 1000;
    
    private void requestScreenCapture(@Nullable MediaProjectionCaptureTarget captureTarget) {
        Context userContext = mUserContextProvider.getUserContext();
        //是否显示触摸效果
        boolean showTaps = mTapsSwitch.isChecked();
        //音频模式
        ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked()
                ? (ScreenRecordingAudioSource) mOptions.getSelectedItem()
                : NONE;
        //屏幕录制用到的intent
        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);
        //停止屏幕录制用到的intent
        PendingIntent stopIntent = PendingIntent.getService(userContext,
                RecordingService.REQUEST_CODE,
                RecordingService.getStopIntent(userContext),
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        //见3.2,开启倒计时
        mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent);
    }

5.RecordingService.java

5.1.getStartIntent

开始录屏用到的intent

    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);
    }

5.2.getStopIntent

停止录屏用到的intent

    public static Intent getStopIntent(Context context) {
        return new Intent(context, RecordingService.class)
                .setAction(ACTION_STOP)
                .putExtra(Intent.EXTRA_USER_HANDLE, context.getUserId());
    }

5.3.onStartCommand

这里就是录屏服务里处理上边的start,stop等action的地方

    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);

        int currentUserId = mUserContextTracker.getUserContext().getUserId();
        UserHandle currentUser = new UserHandle(currentUserId);
        switch (action) {
            case ACTION_START:
                //音频源
                mAudioSource = ScreenRecordingAudioSource
                        .values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)];
               
                mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
                MediaProjectionCaptureTarget captureTarget =
                        intent.getParcelableExtra(EXTRA_CAPTURE_TARGET,
                                MediaProjectionCaptureTarget.class);
            //先获取原本设置里的showTap,开发者模式里
                mOriginalShowTaps = Settings.System.getInt(
                        getApplicationContext().getContentResolver(),
                        Settings.System.SHOW_TOUCHES, 0) != 0;
                //再根据传递的参数修改上边的Settings.System.SHOW_TOUCHES
                setTapsVisible(mShowTaps);//见补充1
                //录屏工具类
                mRecorder = new ScreenMediaRecorder(
                        mUserContextTracker.getUserContext(),
                        mMainHandler,
                        currentUserId,
                        mAudioSource,
                        captureTarget,
                        this
                );
                //补充2
                if (startRecording()) {
                    //录屏启动成功,更新状态为true
                    updateState(true);//见补充3
                    //创建通知,见5.4
                    createRecordingNotification();
                } else {
                //启动失败,更新状态为false
                    updateState(false);
                    //创建error通知,见5.5
                    createErrorNotification();
                    //停止服务
                    stopForeground(true);
                    stopSelf();
                    return Service.START_NOT_STICKY;
                }
                break;

            case ACTION_STOP_NOTIF:
            case ACTION_STOP://停止录屏
                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_ID_NOT_SPECIFIED);
                //见5.6
                stopService(userId);
                break;

            case ACTION_SHARE://分享录制的屏幕视频
                Uri shareUri = Uri.parse(intent.getStringExtra(EXTRA_PATH));
                //创建分享意图
                Intent shareIntent = new Intent(Intent.ACTION_SEND)
                        .setType("video/mp4")
                        .putExtra(Intent.EXTRA_STREAM, shareUri);
                mKeyguardDismissUtil.executeWhenUnlocked(() -> {
                    String shareLabel = getResources().getString(R.string.screenrecord_share_label);
                    启动分享页面
                    startActivity(Intent.createChooser(shareIntent, shareLabel)
                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
                    // Remove notification
                    mNotificationManager.cancelAsUser(null, NOTIFICATION_VIEW_ID, currentUser);
                    return false;
                }, false, false);

                // Close quick shade
                sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
                break;
        }
        return Service.START_STICKY;
    }

>1.setTapsVisible

    private void setTapsVisible(boolean turnOn) {
        int value = turnOn ? 1 : 0;
        Settings.System.putInt(getContentResolver(), Settings.System.SHOW_TOUCHES, value);
    }

>2.startRecording

    private boolean startRecording() {
        try {
            getRecorder().start();
            //录屏启动正常
            return true;
        } catch (IOException | RemoteException | RuntimeException e) {
            showErrorToast(R.string.screenrecord_start_error);
            e.printStackTrace();
        }
        //录屏启动失败
        return false;
    }

>3.updateState

    private void updateState(boolean state) {
        int userId = mUserContextTracker.getUserContext().getUserId();
        if (userId == UserHandle.USER_SYSTEM) {
            //系统用户和controller关联的,直接调用即可,见3.2.2
            mController.updateState(state);
        } else {
        //否则发送广播通知
            Intent intent = new Intent(RecordingController.INTENT_UPDATE_STATE);
            intent.putExtra(RecordingController.EXTRA_STATE, state);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            sendBroadcast(intent, PERMISSION_SELF);
        }
    }

5.4.createRecordingNotification

创建录屏中的通知

    protected void createRecordingNotification() {
        Resources res = getResources();
        NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                getString(R.string.screenrecord_name),
                NotificationManager.IMPORTANCE_DEFAULT);
        channel.setDescription(getString(R.string.screenrecord_channel_description));
        channel.enableVibration(true);
        mNotificationManager.createNotificationChannel(channel);

        Bundle extras = new Bundle();
        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                res.getString(R.string.screenrecord_name));
        //有无音频,提示文字不一样
        String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
                ? res.getString(R.string.screenrecord_ongoing_screen_only)
                : res.getString(R.string.screenrecord_ongoing_screen_and_audio);

        PendingIntent pendingIntent = PendingIntent.getService(
                this,
                REQUEST_CODE,
                getNotificationIntent(this),//见补充2,停止录屏的action
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        //创建通知里stop按钮的点击事件
        Notification.Action stopAction = new Notification.Action.Builder(
                Icon.createWithResource(this, R.drawable.ic_android),
                getResources().getString(R.string.screenrecord_stop_label),
                pendingIntent).build();
        Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_screenrecord)
                .setContentTitle(notificationTitle)
                .setUsesChronometer(true)
                .setColorized(true)
                .setColor(getResources().getColor(R.color.GM2_red_700))
                .setOngoing(true)
                .setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
                .addAction(stopAction)
                .addExtras(extras);
        //开启前台通知
        startForeground(NOTIFICATION_RECORDING_ID, builder.build());
    }

>1.效果图

image.png

>2.getNotificationIntent

作用和action stop一样

    protected static Intent getNotificationIntent(Context context) {
        return new Intent(context, RecordingService.class).setAction(ACTION_STOP_NOTIF);
    }

5.5.createErrorNotification

录屏错误通知,没见过,就不贴图了。

    protected void createErrorNotification() {
        Resources res = getResources();
        NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                getString(R.string.screenrecord_name),
                NotificationManager.IMPORTANCE_DEFAULT);
        channel.setDescription(getString(R.string.screenrecord_channel_description));
        channel.enableVibration(true);
        mNotificationManager.createNotificationChannel(channel);

        Bundle extras = new Bundle();
        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                res.getString(R.string.screenrecord_name));
        String notificationTitle = res.getString(R.string.screenrecord_start_error);

        Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_screenrecord)
                .setContentTitle(notificationTitle)
                .addExtras(extras);
        startForeground(NOTIFICATION_RECORDING_ID, builder.build());
    }

5.6.stopService

    private void stopService(int userId) {
        if (userId == USER_ID_NOT_SPECIFIED) {
            userId = mUserContextTracker.getUserContext().getUserId();
        }
       //恢复原本设置里的showTap
        setTapsVisible(mOriginalShowTaps);
        if (getRecorder() != null) {
            try {
                //停止录屏
                getRecorder().end();
                //保存视频
                saveRecording(userId);
            } 
        }
        //更新录屏状态为false
        updateState(false);
        //停止服务
        stopSelf();
    }

>1.saveRecording

    private void saveRecording(int userId) {
        UserHandle currentUser = new UserHandle(userId);
        //见5.7,弹出新的通知,不带stop
        mNotificationManager.notifyAsUser(null, NOTIFICATION_PROCESSING_ID,
                createProcessingNotification(), currentUser);

        mLongExecutor.execute(() -> {
            try {
                //见5.8,弹出保存成功的通知
                Notification notification = createSaveNotification(getRecorder().save());
                if (!mController.isRecording()) {
                //确保再非录制的状态下再弹
                    mNotificationManager.notifyAsUser(null, NOTIFICATION_VIEW_ID, notification,
                            currentUser);
                }
            } catch (IOException e) {
                Log.e(TAG, "Error saving screen recording: " + e.getMessage());
                showErrorToast(R.string.screenrecord_delete_error);
            } finally {
            //取消方法开头的通知
                mNotificationManager.cancelAsUser(null, NOTIFICATION_PROCESSING_ID, currentUser);
            }
        });
    }

5.7.createProcessingNotification

和5.4差不多的通知,就是没有了stop按钮

    protected Notification createProcessingNotification() {
        Resources res = getApplicationContext().getResources();
        String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
                ? res.getString(R.string.screenrecord_ongoing_screen_only)
                : res.getString(R.string.screenrecord_ongoing_screen_and_audio);

        Bundle extras = new Bundle();
        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                res.getString(R.string.screenrecord_name));

        Notification.Builder builder = new Notification.Builder(getApplicationContext(), CHANNEL_ID)
                .setContentTitle(notificationTitle)
                .setContentText(
                        getResources().getString(R.string.screenrecord_background_processing_label))
                .setSmallIcon(R.drawable.ic_screenrecord)
                .addExtras(extras);
        return builder.build();
    }

5.8.createSaveNotification

录屏保存成功以后弹的通知

    protected Notification createSaveNotification(ScreenMediaRecorder.SavedRecording recording) {
        Uri uri = recording.getUri();
        //预览intent,可以看到,跳转的是支持视频浏览的页面
        Intent viewIntent = new Intent(Intent.ACTION_VIEW)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION)
                .setDataAndType(uri, "video/mp4");
        //share按钮事件
        Notification.Action shareAction = new Notification.Action.Builder(
                Icon.createWithResource(this, R.drawable.ic_screenrecord),
                getResources().getString(R.string.screenrecord_share_label),
                PendingIntent.getService(
                        this,
                        REQUEST_CODE,
                        getShareIntent(this, uri.toString()),
                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                .build();

        Bundle extras = new Bundle();
        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                getResources().getString(R.string.screenrecord_name));

        Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_screenrecord)
                .setContentTitle(getResources().getString(R.string.screenrecord_save_title))
                .setContentText(getResources().getString(R.string.screenrecord_save_text))
                //内容区域点击事件
                .setContentIntent(PendingIntent.getActivity(
                        this,
                        REQUEST_CODE,
                        viewIntent,
                        PendingIntent.FLAG_IMMUTABLE))
                .addAction(shareAction)
                .setAutoCancel(true)//点击后通知取消
                .addExtras(extras);

        // 预览图
        Bitmap thumbnailBitmap = recording.getThumbnail();
        if (thumbnailBitmap != null) {
            Notification.BigPictureStyle pictureStyle = new Notification.BigPictureStyle()
                    .bigPicture(thumbnailBitmap)
                    .showBigPictureWhenCollapsed(true);
            //设置大图预览
            builder.setStyle(pictureStyle);
        }
        return builder.build();
    }

>1.效果图

image.png

6.PhoneStatusBarPolicy.java

        mRecordingController.addCallback(this);

6.1.onRecordingStart

监听录屏状态,在状态栏显示或者隐藏图标

    public void onRecordingStart() {
        mIconController.setIcon(mSlotScreenRecord,
                R.drawable.stat_sys_screen_record,
                mResources.getString(R.string.screenrecord_ongoing_screen_only));
        mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, true));
    }

    @Override
    public void onRecordingEnd() {
        mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
    }

7.SystemUIDialog.java

系统ui对话框,小节4的父类。

7.1.构造方法

    public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
            SystemUIDialogManager dialogManager, SysUiState sysUiState,
            BroadcastDispatcher broadcastDispatcher, DialogLaunchAnimator dialogLaunchAnimator) {
        super(context, theme);
        mContext = context;

        applyFlags(this);
        WindowManager.LayoutParams attrs = getWindow().getAttributes();
        attrs.setTitle(getClass().getSimpleName());
        getWindow().setAttributes(attrs);

        mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this, broadcastDispatcher,
                dialogLaunchAnimator) : null;
        mDialogManager = dialogManager;
        mSysUiState = sysUiState;
    }

>1.applyFlags

    public static AlertDialog applyFlags(AlertDialog dialog) {
        final Window window = dialog.getWindow();
        //类型
        window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
        //flag
        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
        //去除导致窗口缩进的类型 :statusbar
        window.getAttributes().setFitInsetsTypes(
                window.getAttributes().getFitInsetsTypes() & ~Type.statusBars());
        return dialog;
    }

7.2.onStart

    protected void onStart() {
        super.onStart();

        if (mDismissReceiver != null) {
            mDismissReceiver.register();
        }
        ViewRootImpl.addConfigCallback(this);
        mDialogManager.setShowing(this, true);
        mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true)
                .commitUpdate(mContext.getDisplayId());
    }

7.3.onStop

    protected void onStop() {
        super.onStop();

        if (mDismissReceiver != null) {
            mDismissReceiver.unregister();
        }

        ViewRootImpl.removeConfigCallback(this);
        mDialogManager.setShowing(this, false);
        mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, false)
                .commitUpdate(mContext.getDisplayId());
    }

8.ScreenMediaRecorder

这个暂时不看,以后有空再说。