1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.screenrecord;
18 
19 import static android.app.Activity.RESULT_OK;
20 
21 import static com.android.systemui.media.MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER;
22 import static com.android.systemui.media.MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET;
23 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL;
24 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC;
25 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
26 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.NONE;
27 
28 import android.app.Activity;
29 import android.app.PendingIntent;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.Looper;
35 import android.os.ResultReceiver;
36 import android.view.Gravity;
37 import android.view.View;
38 import android.view.Window;
39 import android.view.WindowManager;
40 import android.widget.ArrayAdapter;
41 import android.widget.Spinner;
42 import android.widget.Switch;
43 import android.widget.TextView;
44 
45 import androidx.annotation.Nullable;
46 
47 import com.android.systemui.R;
48 import com.android.systemui.animation.ActivityLaunchAnimator;
49 import com.android.systemui.animation.DialogLaunchAnimator;
50 import com.android.systemui.flags.FeatureFlags;
51 import com.android.systemui.flags.Flags;
52 import com.android.systemui.media.MediaProjectionAppSelectorActivity;
53 import com.android.systemui.media.MediaProjectionCaptureTarget;
54 import com.android.systemui.plugins.ActivityStarter;
55 import com.android.systemui.settings.UserContextProvider;
56 import com.android.systemui.statusbar.phone.SystemUIDialog;
57 
58 import java.util.Arrays;
59 import java.util.List;
60 
61 /**
62  * Dialog to select screen recording options
63  */
64 public class ScreenRecordDialog extends SystemUIDialog {
65     private static final List<ScreenRecordingAudioSource> MODES = Arrays.asList(INTERNAL, MIC,
66             MIC_AND_INTERNAL);
67     private static final long DELAY_MS = 3000;
68     private static final long INTERVAL_MS = 1000;
69 
70     private final RecordingController mController;
71     private final UserContextProvider mUserContextProvider;
72     @Nullable
73     private final Runnable mOnStartRecordingClicked;
74     private final ActivityStarter mActivityStarter;
75     private final FeatureFlags mFlags;
76     private final DialogLaunchAnimator mDialogLaunchAnimator;
77     private Switch mTapsSwitch;
78     private Switch mAudioSwitch;
79     private Spinner mOptions;
80 
ScreenRecordDialog(Context context, RecordingController controller, ActivityStarter activityStarter, UserContextProvider userContextProvider, FeatureFlags flags, DialogLaunchAnimator dialogLaunchAnimator, @Nullable Runnable onStartRecordingClicked)81     public ScreenRecordDialog(Context context, RecordingController controller,
82             ActivityStarter activityStarter, UserContextProvider userContextProvider,
83             FeatureFlags flags, DialogLaunchAnimator dialogLaunchAnimator,
84             @Nullable Runnable onStartRecordingClicked) {
85         super(context);
86         mController = controller;
87         mUserContextProvider = userContextProvider;
88         mActivityStarter = activityStarter;
89         mDialogLaunchAnimator = dialogLaunchAnimator;
90         mFlags = flags;
91         mOnStartRecordingClicked = onStartRecordingClicked;
92     }
93 
94     @Override
onCreate(Bundle savedInstanceState)95     public void onCreate(Bundle savedInstanceState) {
96         super.onCreate(savedInstanceState);
97 
98         Window window = getWindow();
99 
100         window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
101 
102         window.setGravity(Gravity.CENTER);
103         setTitle(R.string.screenrecord_title);
104 
105         setContentView(R.layout.screen_record_dialog);
106 
107         TextView cancelBtn = findViewById(R.id.button_cancel);
108         cancelBtn.setOnClickListener(v -> dismiss());
109 
110         TextView startBtn = findViewById(R.id.button_start);
111         startBtn.setOnClickListener(v -> {
112             if (mOnStartRecordingClicked != null) {
113                 // Note that it is important to run this callback before dismissing, so that the
114                 // callback can disable the dialog exit animation if it wants to.
115                 mOnStartRecordingClicked.run();
116             }
117 
118             // Start full-screen recording
119             requestScreenCapture(/* captureTarget= */ null);
120             dismiss();
121         });
122 
123         if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
124             TextView appBtn = findViewById(R.id.button_app);
125 
126             appBtn.setVisibility(View.VISIBLE);
127             appBtn.setOnClickListener(v -> {
128                 Intent intent = new Intent(getContext(), MediaProjectionAppSelectorActivity.class);
129                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
130 
131                 // We can't start activity for result here so we use result receiver to get
132                 // the selected target to capture
133                 intent.putExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
134                         new CaptureTargetResultReceiver());
135 
136                 ActivityLaunchAnimator.Controller animationController =
137                         mDialogLaunchAnimator.createActivityLaunchController(appBtn);
138 
139                 if (animationController == null) {
140                     dismiss();
141                 }
142 
143                 mActivityStarter.startActivity(intent, /* dismissShade= */ true,
144                         animationController);
145             });
146         }
147 
148         mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
149         mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
150         mOptions = findViewById(R.id.screen_recording_options);
151         ArrayAdapter a = new ScreenRecordingAdapter(getContext().getApplicationContext(),
152                 android.R.layout.simple_spinner_dropdown_item,
153                 MODES);
154         a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
155         mOptions.setAdapter(a);
156         mOptions.setOnItemClickListenerInt((parent, view, position, id) -> {
157             mAudioSwitch.setChecked(true);
158         });
159     }
160 
161     /**
162      * Starts screen capture after some countdown
163      * @param captureTarget target to capture (could be e.g. a task) or
164      *                      null to record the whole screen
165      */
requestScreenCapture(@ullable MediaProjectionCaptureTarget captureTarget)166     private void requestScreenCapture(@Nullable MediaProjectionCaptureTarget captureTarget) {
167         Context userContext = mUserContextProvider.getUserContext();
168         boolean showTaps = mTapsSwitch.isChecked();
169         ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked()
170                 ? (ScreenRecordingAudioSource) mOptions.getSelectedItem()
171                 : NONE;
172         PendingIntent startIntent = PendingIntent.getForegroundService(userContext,
173                 RecordingService.REQUEST_CODE,
174                 RecordingService.getStartIntent(
175                         userContext, Activity.RESULT_OK,
176                         audioMode.ordinal(), showTaps, captureTarget),
177                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
178         PendingIntent stopIntent = PendingIntent.getService(userContext,
179                 RecordingService.REQUEST_CODE,
180                 RecordingService.getStopIntent(userContext),
181                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
182         mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent);
183     }
184 
185     private class CaptureTargetResultReceiver extends ResultReceiver {
186 
CaptureTargetResultReceiver()187         CaptureTargetResultReceiver() {
188             super(new Handler(Looper.getMainLooper()));
189         }
190 
191         @Override
onReceiveResult(int resultCode, Bundle resultData)192         protected void onReceiveResult(int resultCode, Bundle resultData) {
193             if (resultCode == RESULT_OK) {
194                 MediaProjectionCaptureTarget captureTarget = resultData
195                         .getParcelable(KEY_CAPTURE_TARGET, MediaProjectionCaptureTarget.class);
196 
197                 // Start recording of the selected target
198                 requestScreenCapture(captureTarget);
199             }
200         }
201     }
202 }
203