blob: 82ac1f6f6a33563fe4809de1e4dea0da464dbef4 [file] [log] [blame]
Beth Thibodeau77c25452020-01-09 14:33:47 -05001/*
2 * Copyright (C) 2020 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
17package com.android.systemui.screenrecord;
18
19import android.app.PendingIntent;
Beth Thibodeau231ac9b2020-06-17 22:34:42 -040020import android.content.BroadcastReceiver;
Beth Thibodeau77c25452020-01-09 14:33:47 -050021import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
Beth Thibodeau231ac9b2020-06-17 22:34:42 -040024import android.content.IntentFilter;
Beth Thibodeau77c25452020-01-09 14:33:47 -050025import android.os.CountDownTimer;
Beth Thibodeau231ac9b2020-06-17 22:34:42 -040026import android.os.UserHandle;
Beth Thibodeau77c25452020-01-09 14:33:47 -050027import android.util.Log;
28
Beth Thibodeau231ac9b2020-06-17 22:34:42 -040029import com.android.internal.annotations.VisibleForTesting;
30import com.android.systemui.broadcast.BroadcastDispatcher;
Beth Thibodeau8bfbf1f2020-02-13 13:55:37 -050031import com.android.systemui.statusbar.policy.CallbackController;
32
33import java.util.ArrayList;
Beth Thibodeau77c25452020-01-09 14:33:47 -050034
35import javax.inject.Inject;
36import javax.inject.Singleton;
37
38/**
39 * Helper class to initiate a screen recording
40 */
41@Singleton
Beth Thibodeau8bfbf1f2020-02-13 13:55:37 -050042public class RecordingController
43 implements CallbackController<RecordingController.RecordingStateChangeCallback> {
Beth Thibodeau77c25452020-01-09 14:33:47 -050044 private static final String TAG = "RecordingController";
45 private static final String SYSUI_PACKAGE = "com.android.systemui";
46 private static final String SYSUI_SCREENRECORD_LAUNCHER =
47 "com.android.systemui.screenrecord.ScreenRecordDialog";
48
Beth Thibodeau77c25452020-01-09 14:33:47 -050049 private boolean mIsStarting;
50 private boolean mIsRecording;
Beth Thibodeau77c25452020-01-09 14:33:47 -050051 private PendingIntent mStopIntent;
52 private CountDownTimer mCountDownTimer = null;
Beth Thibodeau231ac9b2020-06-17 22:34:42 -040053 private BroadcastDispatcher mBroadcastDispatcher;
Beth Thibodeau77c25452020-01-09 14:33:47 -050054
Beth Thibodeau8bfbf1f2020-02-13 13:55:37 -050055 private ArrayList<RecordingStateChangeCallback> mListeners = new ArrayList<>();
56
Beth Thibodeau231ac9b2020-06-17 22:34:42 -040057 @VisibleForTesting
58 protected final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
59 @Override
60 public void onReceive(Context context, Intent intent) {
61 if (mStopIntent != null) {
62 stopRecording();
63 }
64 }
65 };
66
Beth Thibodeau77c25452020-01-09 14:33:47 -050067 /**
68 * Create a new RecordingController
Beth Thibodeau77c25452020-01-09 14:33:47 -050069 */
70 @Inject
Beth Thibodeau231ac9b2020-06-17 22:34:42 -040071 public RecordingController(BroadcastDispatcher broadcastDispatcher) {
72 mBroadcastDispatcher = broadcastDispatcher;
Beth Thibodeau77c25452020-01-09 14:33:47 -050073 }
74
75 /**
Beth Thibodeauf54cace2020-04-23 12:46:55 -040076 * Get an intent to show screen recording options to the user.
Beth Thibodeau77c25452020-01-09 14:33:47 -050077 */
Beth Thibodeauf54cace2020-04-23 12:46:55 -040078 public Intent getPromptIntent() {
Beth Thibodeau77c25452020-01-09 14:33:47 -050079 final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
80 SYSUI_SCREENRECORD_LAUNCHER);
81 final Intent intent = new Intent();
82 intent.setComponent(launcherComponent);
83 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Beth Thibodeauf54cace2020-04-23 12:46:55 -040084 return intent;
Beth Thibodeau77c25452020-01-09 14:33:47 -050085 }
86
87 /**
88 * Start counting down in preparation to start a recording
Beth Thibodeau58772782020-02-18 20:55:38 -050089 * @param ms Total time in ms to wait before starting
90 * @param interval Time in ms per countdown step
Beth Thibodeau77c25452020-01-09 14:33:47 -050091 * @param startIntent Intent to start a recording
92 * @param stopIntent Intent to stop a recording
93 */
Beth Thibodeau58772782020-02-18 20:55:38 -050094 public void startCountdown(long ms, long interval, PendingIntent startIntent,
95 PendingIntent stopIntent) {
Beth Thibodeau77c25452020-01-09 14:33:47 -050096 mIsStarting = true;
97 mStopIntent = stopIntent;
98
Beth Thibodeau58772782020-02-18 20:55:38 -050099 mCountDownTimer = new CountDownTimer(ms, interval) {
Beth Thibodeau77c25452020-01-09 14:33:47 -0500100 @Override
101 public void onTick(long millisUntilFinished) {
Beth Thibodeau8bfbf1f2020-02-13 13:55:37 -0500102 for (RecordingStateChangeCallback cb : mListeners) {
103 cb.onCountdown(millisUntilFinished);
104 }
Beth Thibodeau77c25452020-01-09 14:33:47 -0500105 }
106
107 @Override
108 public void onFinish() {
109 mIsStarting = false;
110 mIsRecording = true;
Beth Thibodeau8bfbf1f2020-02-13 13:55:37 -0500111 for (RecordingStateChangeCallback cb : mListeners) {
Beth Thibodeau58772782020-02-18 20:55:38 -0500112 cb.onCountdownEnd();
Beth Thibodeau8bfbf1f2020-02-13 13:55:37 -0500113 }
Beth Thibodeau77c25452020-01-09 14:33:47 -0500114 try {
115 startIntent.send();
Beth Thibodeau231ac9b2020-06-17 22:34:42 -0400116 IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
117 mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, userFilter, null,
118 UserHandle.ALL);
Beth Thibodeau8bfbf1f2020-02-13 13:55:37 -0500119 Log.d(TAG, "sent start intent");
Beth Thibodeau77c25452020-01-09 14:33:47 -0500120 } catch (PendingIntent.CanceledException e) {
121 Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
122 }
123 }
124 };
125
126 mCountDownTimer.start();
127 }
128
Beth Thibodeau77c25452020-01-09 14:33:47 -0500129 /**
130 * Cancel a countdown in progress. This will not stop the recording if it already started.
131 */
132 public void cancelCountdown() {
133 if (mCountDownTimer != null) {
134 mCountDownTimer.cancel();
135 } else {
136 Log.e(TAG, "Timer was null");
137 }
138 mIsStarting = false;
Beth Thibodeau8bfbf1f2020-02-13 13:55:37 -0500139
140 for (RecordingStateChangeCallback cb : mListeners) {
Beth Thibodeau58772782020-02-18 20:55:38 -0500141 cb.onCountdownEnd();
Beth Thibodeau8bfbf1f2020-02-13 13:55:37 -0500142 }
Beth Thibodeau77c25452020-01-09 14:33:47 -0500143 }
144
145 /**
146 * Check if the recording is currently counting down to begin
147 * @return
148 */
149 public boolean isStarting() {
150 return mIsStarting;
151 }
152
153 /**
154 * Check if the recording is ongoing
155 * @return
156 */
Jay Aliomer0bd491a2020-03-16 14:34:10 -0400157 public synchronized boolean isRecording() {
Beth Thibodeau77c25452020-01-09 14:33:47 -0500158 return mIsRecording;
159 }
160
161 /**
162 * Stop the recording
163 */
164 public void stopRecording() {
Beth Thibodeau77c25452020-01-09 14:33:47 -0500165 try {
Beth Thibodeau231ac9b2020-06-17 22:34:42 -0400166 if (mStopIntent != null) {
167 mStopIntent.send();
168 } else {
169 Log.e(TAG, "Stop intent was null");
170 }
Beth Thibodeau58772782020-02-18 20:55:38 -0500171 updateState(false);
Beth Thibodeau77c25452020-01-09 14:33:47 -0500172 } catch (PendingIntent.CanceledException e) {
173 Log.e(TAG, "Error stopping: " + e.getMessage());
174 }
Beth Thibodeau231ac9b2020-06-17 22:34:42 -0400175 mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
Beth Thibodeau77c25452020-01-09 14:33:47 -0500176 }
177
178 /**
179 * Update the current status
180 * @param isRecording
181 */
Jay Aliomer0bd491a2020-03-16 14:34:10 -0400182 public synchronized void updateState(boolean isRecording) {
Beth Thibodeau77c25452020-01-09 14:33:47 -0500183 mIsRecording = isRecording;
Beth Thibodeau8bfbf1f2020-02-13 13:55:37 -0500184 for (RecordingStateChangeCallback cb : mListeners) {
185 if (isRecording) {
186 cb.onRecordingStart();
187 } else {
188 cb.onRecordingEnd();
189 }
190 }
191 }
192
193 @Override
194 public void addCallback(RecordingStateChangeCallback listener) {
195 mListeners.add(listener);
196 }
197
198 @Override
199 public void removeCallback(RecordingStateChangeCallback listener) {
200 mListeners.remove(listener);
201 }
202
203 /**
204 * A callback for changes in the screen recording state
205 */
206 public interface RecordingStateChangeCallback {
207 /**
208 * Called when a countdown to recording has updated
209 *
210 * @param millisUntilFinished Time in ms remaining in the countdown
211 */
212 default void onCountdown(long millisUntilFinished) {}
213
214 /**
Beth Thibodeau58772782020-02-18 20:55:38 -0500215 * Called when a countdown to recording has ended. This is a separate method so that if
216 * needed, listeners can handle cases where recording fails to start
217 */
218 default void onCountdownEnd() {}
219
220 /**
Beth Thibodeau8bfbf1f2020-02-13 13:55:37 -0500221 * Called when a screen recording has started
222 */
223 default void onRecordingStart() {}
224
225 /**
226 * Called when a screen recording has ended
227 */
228 default void onRecordingEnd() {}
Beth Thibodeau77c25452020-01-09 14:33:47 -0500229 }
230}