Merge "Screen record user switching fixes" into rvc-dev am: 602d97360d

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11976574

Change-Id: Icbbd4ecd087b9e4685fc0eb335bc4d6f3faef7d3
This commit is contained in:
TreeHugger Robot
2020-06-25 18:14:12 +00:00
committed by Automerger Merge Worker
11 changed files with 153 additions and 76 deletions

View File

@@ -329,6 +329,7 @@
<activity android:name=".screenrecord.ScreenRecordDialog" <activity android:name=".screenrecord.ScreenRecordDialog"
android:theme="@style/ScreenRecord" android:theme="@style/ScreenRecord"
android:showForAllUsers="true"
android:excludeFromRecents="true" /> android:excludeFromRecents="true" />
<service android:name=".screenrecord.RecordingService" /> <service android:name=".screenrecord.RecordingService" />

View File

@@ -22,13 +22,13 @@ import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.widget.Switch; import android.widget.Switch;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import javax.inject.Inject; import javax.inject.Inject;
@@ -39,19 +39,17 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
implements RecordingController.RecordingStateChangeCallback { implements RecordingController.RecordingStateChangeCallback {
private static final String TAG = "ScreenRecordTile"; private static final String TAG = "ScreenRecordTile";
private RecordingController mController; private RecordingController mController;
private ActivityStarter mActivityStarter; private KeyguardDismissUtil mKeyguardDismissUtil;
private long mMillisUntilFinished = 0; private long mMillisUntilFinished = 0;
private Callback mCallback = new Callback(); private Callback mCallback = new Callback();
private UiEventLogger mUiEventLogger;
@Inject @Inject
public ScreenRecordTile(QSHost host, RecordingController controller, public ScreenRecordTile(QSHost host, RecordingController controller,
ActivityStarter activityStarter, UiEventLogger uiEventLogger) { KeyguardDismissUtil keyguardDismissUtil) {
super(host); super(host);
mController = controller; mController = controller;
mController.observe(this, mCallback); mController.observe(this, mCallback);
mActivityStarter = activityStarter; mKeyguardDismissUtil = keyguardDismissUtil;
mUiEventLogger = uiEventLogger;
} }
@Override @Override
@@ -69,7 +67,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
} else if (mController.isRecording()) { } else if (mController.isRecording()) {
stopRecording(); stopRecording();
} else { } else {
startCountdown(); mUiHandler.post(() -> showPrompt());
} }
refreshState(); refreshState();
} }
@@ -114,11 +112,15 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
return mContext.getString(R.string.quick_settings_screen_record_label); return mContext.getString(R.string.quick_settings_screen_record_label);
} }
private void startCountdown() { private void showPrompt() {
// Close QS, otherwise the permission dialog appears beneath it // Close QS, otherwise the dialog appears beneath it
getHost().collapsePanels(); getHost().collapsePanels();
Intent intent = mController.getPromptIntent(); Intent intent = mController.getPromptIntent();
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); ActivityStarter.OnDismissAction dismissAction = () -> {
mContext.startActivity(intent);
return false;
};
mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false);
} }
private void cancelCountdown() { private void cancelCountdown() {

View File

@@ -17,12 +17,17 @@
package com.android.systemui.screenrecord; package com.android.systemui.screenrecord;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.os.CountDownTimer; import android.os.CountDownTimer;
import android.os.UserHandle;
import android.util.Log; import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.statusbar.policy.CallbackController;
import java.util.ArrayList; import java.util.ArrayList;
@@ -41,21 +46,30 @@ public class RecordingController
private static final String SYSUI_SCREENRECORD_LAUNCHER = private static final String SYSUI_SCREENRECORD_LAUNCHER =
"com.android.systemui.screenrecord.ScreenRecordDialog"; "com.android.systemui.screenrecord.ScreenRecordDialog";
private final Context mContext;
private boolean mIsStarting; private boolean mIsStarting;
private boolean mIsRecording; private boolean mIsRecording;
private PendingIntent mStopIntent; private PendingIntent mStopIntent;
private CountDownTimer mCountDownTimer = null; private CountDownTimer mCountDownTimer = null;
private BroadcastDispatcher mBroadcastDispatcher;
private ArrayList<RecordingStateChangeCallback> mListeners = new ArrayList<>(); private ArrayList<RecordingStateChangeCallback> mListeners = new ArrayList<>();
@VisibleForTesting
protected final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mStopIntent != null) {
stopRecording();
}
}
};
/** /**
* Create a new RecordingController * Create a new RecordingController
* @param context Context for the controller
*/ */
@Inject @Inject
public RecordingController(Context context) { public RecordingController(BroadcastDispatcher broadcastDispatcher) {
mContext = context; mBroadcastDispatcher = broadcastDispatcher;
} }
/** /**
@@ -99,6 +113,9 @@ public class RecordingController
} }
try { try {
startIntent.send(); startIntent.send();
IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, userFilter, null,
UserHandle.ALL);
Log.d(TAG, "sent start intent"); Log.d(TAG, "sent start intent");
} catch (PendingIntent.CanceledException e) { } catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Pending intent was cancelled: " + e.getMessage()); Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
@@ -146,11 +163,16 @@ public class RecordingController
*/ */
public void stopRecording() { public void stopRecording() {
try { try {
mStopIntent.send(); if (mStopIntent != null) {
mStopIntent.send();
} else {
Log.e(TAG, "Stop intent was null");
}
updateState(false); updateState(false);
} catch (PendingIntent.CanceledException e) { } catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Error stopping: " + e.getMessage()); Log.e(TAG, "Error stopping: " + e.getMessage());
} }
mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
} }
/** /**

View File

@@ -32,6 +32,7 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
@@ -40,6 +41,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.LongRunning; import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.settings.CurrentUserContextTracker;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -58,7 +60,6 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
private static final String TAG = "RecordingService"; private static final String TAG = "RecordingService";
private static final String CHANNEL_ID = "screen_record"; private static final String CHANNEL_ID = "screen_record";
private static final String EXTRA_RESULT_CODE = "extra_resultCode"; private static final String EXTRA_RESULT_CODE = "extra_resultCode";
private static final String EXTRA_DATA = "extra_data";
private static final String EXTRA_PATH = "extra_path"; private static final String EXTRA_PATH = "extra_path";
private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio"; private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
private static final String EXTRA_SHOW_TAPS = "extra_showTaps"; private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
@@ -79,14 +80,17 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
private final Executor mLongExecutor; private final Executor mLongExecutor;
private final UiEventLogger mUiEventLogger; private final UiEventLogger mUiEventLogger;
private final NotificationManager mNotificationManager; private final NotificationManager mNotificationManager;
private final CurrentUserContextTracker mUserContextTracker;
@Inject @Inject
public RecordingService(RecordingController controller, @LongRunning Executor executor, public RecordingService(RecordingController controller, @LongRunning Executor executor,
UiEventLogger uiEventLogger, NotificationManager notificationManager) { UiEventLogger uiEventLogger, NotificationManager notificationManager,
CurrentUserContextTracker userContextTracker) {
mController = controller; mController = controller;
mLongExecutor = executor; mLongExecutor = executor;
mUiEventLogger = uiEventLogger; mUiEventLogger = uiEventLogger;
mNotificationManager = notificationManager; mNotificationManager = notificationManager;
mUserContextTracker = userContextTracker;
} }
/** /**
@@ -95,8 +99,6 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
* @param context Context from the requesting activity * @param context Context from the requesting activity
* @param resultCode The result code from {@link android.app.Activity#onActivityResult(int, int, * @param resultCode The result code from {@link android.app.Activity#onActivityResult(int, int,
* android.content.Intent)} * android.content.Intent)}
* @param data The data from {@link android.app.Activity#onActivityResult(int, int,
* android.content.Intent)}
* @param audioSource The ordinal value of the audio source * @param audioSource The ordinal value of the audio source
* {@link com.android.systemui.screenrecord.ScreenRecordingAudioSource} * {@link com.android.systemui.screenrecord.ScreenRecordingAudioSource}
* @param showTaps True to make touches visible while recording * @param showTaps True to make touches visible while recording
@@ -118,6 +120,8 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
String action = intent.getAction(); String action = intent.getAction();
Log.d(TAG, "onStartCommand " + action); Log.d(TAG, "onStartCommand " + action);
int mCurrentUserId = mUserContextTracker.getCurrentUserContext().getUserId();
UserHandle currentUser = new UserHandle(mCurrentUserId);
switch (action) { switch (action) {
case ACTION_START: case ACTION_START:
mAudioSource = ScreenRecordingAudioSource mAudioSource = ScreenRecordingAudioSource
@@ -132,8 +136,8 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
setTapsVisible(mShowTaps); setTapsVisible(mShowTaps);
mRecorder = new ScreenMediaRecorder( mRecorder = new ScreenMediaRecorder(
getApplicationContext(), mUserContextTracker.getCurrentUserContext(),
getUserId(), mCurrentUserId,
mAudioSource, mAudioSource,
this this
); );
@@ -148,7 +152,14 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
} else { } else {
mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_END_QS_TILE); mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_END_QS_TILE);
} }
stopRecording(); // Check user ID - we may be getting a stop intent after user switch, in which case
// we want to post the notifications for that user, which is NOT current user
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userId == -1) {
userId = mUserContextTracker.getCurrentUserContext().getUserId();
}
Log.d(TAG, "notifying for user " + userId);
stopRecording(userId);
mNotificationManager.cancel(NOTIFICATION_RECORDING_ID); mNotificationManager.cancel(NOTIFICATION_RECORDING_ID);
stopSelf(); stopSelf();
break; break;
@@ -165,7 +176,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
// Remove notification // Remove notification
mNotificationManager.cancel(NOTIFICATION_VIEW_ID); mNotificationManager.cancelAsUser(null, NOTIFICATION_VIEW_ID, currentUser);
startActivity(Intent.createChooser(shareIntent, shareLabel) startActivity(Intent.createChooser(shareIntent, shareLabel)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
@@ -184,7 +195,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
// Remove notification // Remove notification
mNotificationManager.cancel(NOTIFICATION_VIEW_ID); mNotificationManager.cancelAsUser(null, NOTIFICATION_VIEW_ID, currentUser);
Log.d(TAG, "Deleted recording " + uri); Log.d(TAG, "Deleted recording " + uri);
break; break;
} }
@@ -215,11 +226,12 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
mController.updateState(true); mController.updateState(true);
createRecordingNotification(); createRecordingNotification();
mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START); mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
} catch (IOException | RemoteException e) { } catch (IOException | RemoteException | IllegalStateException e) {
Toast.makeText(this, Toast.makeText(this,
R.string.screenrecord_start_error, Toast.LENGTH_LONG) R.string.screenrecord_start_error, Toast.LENGTH_LONG)
.show(); .show();
e.printStackTrace(); e.printStackTrace();
mController.updateState(false);
} }
} }
@@ -242,7 +254,6 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
? res.getString(R.string.screenrecord_ongoing_screen_only) ? res.getString(R.string.screenrecord_ongoing_screen_only)
: res.getString(R.string.screenrecord_ongoing_screen_and_audio); : res.getString(R.string.screenrecord_ongoing_screen_and_audio);
Intent stopIntent = getNotificationIntent(this); Intent stopIntent = getNotificationIntent(this);
Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_screenrecord) .setSmallIcon(R.drawable.ic_screenrecord)
@@ -254,7 +265,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
.setOngoing(true) .setOngoing(true)
.setContentIntent( .setContentIntent(
PendingIntent.getService(this, REQUEST_CODE, stopIntent, PendingIntent.getService(this, REQUEST_CODE, stopIntent,
PendingIntent.FLAG_UPDATE_CURRENT)) PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.addExtras(extras); .addExtras(extras);
startForeground(NOTIFICATION_RECORDING_ID, builder.build()); startForeground(NOTIFICATION_RECORDING_ID, builder.build());
} }
@@ -265,11 +276,17 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
? res.getString(R.string.screenrecord_ongoing_screen_only) ? res.getString(R.string.screenrecord_ongoing_screen_only)
: res.getString(R.string.screenrecord_ongoing_screen_and_audio); : 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) Notification.Builder builder = new Notification.Builder(getApplicationContext(), CHANNEL_ID)
.setContentTitle(notificationTitle) .setContentTitle(notificationTitle)
.setContentText( .setContentText(
getResources().getString(R.string.screenrecord_background_processing_label)) getResources().getString(R.string.screenrecord_background_processing_label))
.setSmallIcon(R.drawable.ic_screenrecord); .setSmallIcon(R.drawable.ic_screenrecord)
.addExtras(extras);
return builder.build(); return builder.build();
} }
@@ -287,7 +304,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
this, this,
REQUEST_CODE, REQUEST_CODE,
getShareIntent(this, uri.toString()), getShareIntent(this, uri.toString()),
PendingIntent.FLAG_UPDATE_CURRENT)) PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.build(); .build();
Notification.Action deleteAction = new Notification.Action.Builder( Notification.Action deleteAction = new Notification.Action.Builder(
@@ -297,7 +314,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
this, this,
REQUEST_CODE, REQUEST_CODE,
getDeleteIntent(this, uri.toString()), getDeleteIntent(this, uri.toString()),
PendingIntent.FLAG_UPDATE_CURRENT)) PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.build(); .build();
Bundle extras = new Bundle(); Bundle extras = new Bundle();
@@ -328,34 +345,36 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
return builder.build(); return builder.build();
} }
private void stopRecording() { private void stopRecording(int userId) {
setTapsVisible(mOriginalShowTaps); setTapsVisible(mOriginalShowTaps);
if (getRecorder() != null) { if (getRecorder() != null) {
getRecorder().end(); getRecorder().end();
saveRecording(); saveRecording(userId);
} else { } else {
Log.e(TAG, "stopRecording called, but recorder was null"); Log.e(TAG, "stopRecording called, but recorder was null");
} }
mController.updateState(false); mController.updateState(false);
} }
private void saveRecording() { private void saveRecording(int userId) {
mNotificationManager.notify(NOTIFICATION_PROCESSING_ID, createProcessingNotification()); UserHandle currentUser = new UserHandle(userId);
mNotificationManager.notifyAsUser(null, NOTIFICATION_PROCESSING_ID,
createProcessingNotification(), currentUser);
mLongExecutor.execute(() -> { mLongExecutor.execute(() -> {
try { try {
Log.d(TAG, "saving recording"); Log.d(TAG, "saving recording");
Notification notification = createSaveNotification(getRecorder().save()); Notification notification = createSaveNotification(getRecorder().save());
if (!mController.isRecording()) { if (!mController.isRecording()) {
Log.d(TAG, "showing saved notification"); mNotificationManager.notifyAsUser(null, NOTIFICATION_VIEW_ID, notification,
mNotificationManager.notify(NOTIFICATION_VIEW_ID, notification); currentUser);
} }
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Error saving screen recording: " + e.getMessage()); Log.e(TAG, "Error saving screen recording: " + e.getMessage());
Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG) Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG)
.show(); .show();
} finally { } finally {
mNotificationManager.cancel(NOTIFICATION_PROCESSING_ID); mNotificationManager.cancelAsUser(null, NOTIFICATION_PROCESSING_ID, currentUser);
} }
}); });
} }
@@ -371,7 +390,9 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
* @return * @return
*/ */
public static Intent getStopIntent(Context context) { public static Intent getStopIntent(Context context) {
return new Intent(context, RecordingService.class).setAction(ACTION_STOP); return new Intent(context, RecordingService.class)
.setAction(ACTION_STOP)
.putExtra(Intent.EXTRA_USER_HANDLE, context.getUserId());
} }
/** /**

View File

@@ -16,7 +16,6 @@
package com.android.systemui.screenrecord; package com.android.systemui.screenrecord;
import android.content.Context;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.AudioPlaybackCaptureConfiguration; import android.media.AudioPlaybackCaptureConfiguration;
@@ -39,7 +38,6 @@ public class ScreenInternalAudioRecorder {
private static String TAG = "ScreenAudioRecorder"; private static String TAG = "ScreenAudioRecorder";
private static final int TIMEOUT = 500; private static final int TIMEOUT = 500;
private static final float MIC_VOLUME_SCALE = 1.4f; private static final float MIC_VOLUME_SCALE = 1.4f;
private final Context mContext;
private AudioRecord mAudioRecord; private AudioRecord mAudioRecord;
private AudioRecord mAudioRecordMic; private AudioRecord mAudioRecordMic;
private Config mConfig = new Config(); private Config mConfig = new Config();
@@ -49,17 +47,14 @@ public class ScreenInternalAudioRecorder {
private long mPresentationTime; private long mPresentationTime;
private long mTotalBytes; private long mTotalBytes;
private MediaMuxer mMuxer; private MediaMuxer mMuxer;
private String mOutFile;
private boolean mMic; private boolean mMic;
private int mTrackId = -1; private int mTrackId = -1;
public ScreenInternalAudioRecorder(String outFile, Context context, public ScreenInternalAudioRecorder(String outFile, MediaProjection mp, boolean includeMicInput)
MediaProjection mp, boolean includeMicInput) throws IOException { throws IOException {
mMic = includeMicInput; mMic = includeMicInput;
mOutFile = outFile;
mMuxer = new MediaMuxer(outFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); mMuxer = new MediaMuxer(outFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
mContext = context;
mMediaProjection = mp; mMediaProjection = mp;
Log.d(TAG, "creating audio file " + outFile); Log.d(TAG, "creating audio file " + outFile);
setupSimple(); setupSimple();
@@ -266,8 +261,9 @@ public class ScreenInternalAudioRecorder {
/** /**
* start recording * start recording
* @throws IllegalStateException if recording fails to initialize
*/ */
public void start() { public void start() throws IllegalStateException {
if (mThread != null) { if (mThread != null) {
Log.e(TAG, "a recording is being done in parallel or stop is not called"); Log.e(TAG, "a recording is being done in parallel or stop is not called");
} }
@@ -276,8 +272,7 @@ public class ScreenInternalAudioRecorder {
Log.d(TAG, "channel count " + mAudioRecord.getChannelCount()); Log.d(TAG, "channel count " + mAudioRecord.getChannelCount());
mCodec.start(); mCodec.start();
if (mAudioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) { if (mAudioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
Log.e(TAG, "Error starting audio recording"); throw new IllegalStateException("Audio recording failed to start");
return;
} }
mThread.start(); mThread.start();
} }

View File

@@ -166,7 +166,7 @@ public class ScreenMediaRecorder {
mAudioSource == MIC_AND_INTERNAL) { mAudioSource == MIC_AND_INTERNAL) {
mTempAudioFile = File.createTempFile("temp", ".aac", mTempAudioFile = File.createTempFile("temp", ".aac",
mContext.getCacheDir()); mContext.getCacheDir());
mAudio = new ScreenInternalAudioRecorder(mTempAudioFile.getAbsolutePath(), mContext, mAudio = new ScreenInternalAudioRecorder(mTempAudioFile.getAbsolutePath(),
mMediaProjection, mAudioSource == MIC_AND_INTERNAL); mMediaProjection, mAudioSource == MIC_AND_INTERNAL);
} }
@@ -175,7 +175,7 @@ public class ScreenMediaRecorder {
/** /**
* Start screen recording * Start screen recording
*/ */
void start() throws IOException, RemoteException { void start() throws IOException, RemoteException, IllegalStateException {
Log.d(TAG, "start recording"); Log.d(TAG, "start recording");
prepare(); prepare();
mMediaRecorder.start(); mMediaRecorder.start();
@@ -205,7 +205,7 @@ public class ScreenMediaRecorder {
} }
} }
private void recordInternalAudio() { private void recordInternalAudio() throws IllegalStateException {
if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) { if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) {
mAudio.start(); mAudio.start();
} }

View File

@@ -23,16 +23,19 @@ import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.NONE;
import android.app.Activity; import android.app.Activity;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.view.Gravity; import android.view.Gravity;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
import android.view.WindowManager;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.Switch; import android.widget.Switch;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.settings.CurrentUserContextTracker;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -48,16 +51,17 @@ public class ScreenRecordDialog extends Activity {
private static final String TAG = "ScreenRecordDialog"; private static final String TAG = "ScreenRecordDialog";
private final RecordingController mController; private final RecordingController mController;
private final CurrentUserContextTracker mCurrentUserContextTracker;
private Switch mTapsSwitch; private Switch mTapsSwitch;
private Switch mAudioSwitch; private Switch mAudioSwitch;
private Spinner mOptions; private Spinner mOptions;
private List<ScreenRecordingAudioSource> mModes; private List<ScreenRecordingAudioSource> mModes;
private int mSelected;
@Inject @Inject
public ScreenRecordDialog(RecordingController controller) { public ScreenRecordDialog(RecordingController controller,
CurrentUserContextTracker currentUserContextTracker) {
mController = controller; mController = controller;
mCurrentUserContextTracker = currentUserContextTracker;
} }
@Override @Override
@@ -68,6 +72,7 @@ public class ScreenRecordDialog extends Activity {
// Inflate the decor view, so the attributes below are not overwritten by the theme. // Inflate the decor view, so the attributes below are not overwritten by the theme.
window.getDecorView(); window.getDecorView();
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
window.setGravity(Gravity.TOP); window.setGravity(Gravity.TOP);
setTitle(R.string.screenrecord_name); setTitle(R.string.screenrecord_name);
@@ -100,24 +105,24 @@ public class ScreenRecordDialog extends Activity {
mOptions.setOnItemClickListenerInt((parent, view, position, id) -> { mOptions.setOnItemClickListenerInt((parent, view, position, id) -> {
mAudioSwitch.setChecked(true); mAudioSwitch.setChecked(true);
}); });
} }
private void requestScreenCapture() { private void requestScreenCapture() {
Context userContext = mCurrentUserContextTracker.getCurrentUserContext();
boolean showTaps = mTapsSwitch.isChecked(); boolean showTaps = mTapsSwitch.isChecked();
ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked() ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked()
? (ScreenRecordingAudioSource) mOptions.getSelectedItem() ? (ScreenRecordingAudioSource) mOptions.getSelectedItem()
: NONE; : NONE;
PendingIntent startIntent = PendingIntent.getForegroundService(this, PendingIntent startIntent = PendingIntent.getForegroundService(userContext,
RecordingService.REQUEST_CODE, RecordingService.REQUEST_CODE,
RecordingService.getStartIntent( RecordingService.getStartIntent(
ScreenRecordDialog.this, RESULT_OK, userContext, RESULT_OK,
audioMode.ordinal(), showTaps), audioMode.ordinal(), showTaps),
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
PendingIntent stopIntent = PendingIntent.getService(this, PendingIntent stopIntent = PendingIntent.getService(userContext,
RecordingService.REQUEST_CODE, RecordingService.REQUEST_CODE,
RecordingService.getStopIntent(this), RecordingService.getStopIntent(userContext),
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent); mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent);
} }
} }

View File

@@ -688,8 +688,8 @@ public class PhoneStatusBarPolicy
if (DEBUG) Log.d(TAG, "screenrecord: hiding icon during countdown"); if (DEBUG) Log.d(TAG, "screenrecord: hiding icon during countdown");
mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false)); mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
// Reset talkback priority // Reset talkback priority
mIconController.setIconAccessibilityLiveRegion(mSlotScreenRecord, mHandler.post(() -> mIconController.setIconAccessibilityLiveRegion(mSlotScreenRecord,
View.ACCESSIBILITY_LIVE_REGION_NONE); View.ACCESSIBILITY_LIVE_REGION_NONE));
} }
@Override @Override
@@ -698,7 +698,7 @@ public class PhoneStatusBarPolicy
mIconController.setIcon(mSlotScreenRecord, mIconController.setIcon(mSlotScreenRecord,
R.drawable.stat_sys_screen_record, R.drawable.stat_sys_screen_record,
mResources.getString(R.string.screenrecord_ongoing_screen_only)); mResources.getString(R.string.screenrecord_ongoing_screen_only));
mIconController.setIconVisibility(mSlotScreenRecord, true); mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, true));
} }
@Override @Override

View File

@@ -29,13 +29,12 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dependency; import com.android.systemui.Dependency;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.QSTileHost;
import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -44,18 +43,16 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class) @RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper @TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest @SmallTest
public class ScreenRecordTileTest extends SysuiTestCase { public class ScreenRecordTileTest extends SysuiTestCase {
@Mock @Mock
private RecordingController mController; private RecordingController mController;
@Mock @Mock
private ActivityStarter mActivityStarter;
@Mock
private QSTileHost mHost; private QSTileHost mHost;
@Mock @Mock
private UiEventLogger mUiEventLogger; private KeyguardDismissUtil mKeyguardDismissUtil;
private TestableLooper mTestableLooper; private TestableLooper mTestableLooper;
private ScreenRecordTile mTile; private ScreenRecordTile mTile;
@@ -67,11 +64,10 @@ public class ScreenRecordTileTest extends SysuiTestCase {
mTestableLooper = TestableLooper.get(this); mTestableLooper = TestableLooper.get(this);
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
mController = mDependency.injectMockDependency(RecordingController.class); mController = mDependency.injectMockDependency(RecordingController.class);
mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
when(mHost.getContext()).thenReturn(mContext); when(mHost.getContext()).thenReturn(mContext);
mTile = new ScreenRecordTile(mHost, mController, mActivityStarter, mUiEventLogger); mTile = new ScreenRecordTile(mHost, mController, mKeyguardDismissUtil);
} }
// Test that the tile is inactive and labeled correctly when the controller is neither starting // Test that the tile is inactive and labeled correctly when the controller is neither starting
@@ -89,6 +85,7 @@ public class ScreenRecordTileTest extends SysuiTestCase {
mContext.getString(R.string.quick_settings_screen_record_start))); mContext.getString(R.string.quick_settings_screen_record_start)));
mTile.handleClick(); mTile.handleClick();
mTestableLooper.processAllMessages();
verify(mController, times(1)).getPromptIntent(); verify(mController, times(1)).getPromptIntent();
} }

View File

@@ -22,12 +22,14 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Intent;
import android.os.Looper; import android.os.Looper;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -45,14 +47,18 @@ import org.mockito.MockitoAnnotations;
public class RecordingControllerTest extends SysuiTestCase { public class RecordingControllerTest extends SysuiTestCase {
@Mock @Mock
RecordingController.RecordingStateChangeCallback mCallback; private RecordingController.RecordingStateChangeCallback mCallback;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
RecordingController mController; private RecordingController mController;
private static final int USER_ID = 10;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mController = new RecordingController(mContext); mController = new RecordingController(mBroadcastDispatcher);
mController.addCallback(mCallback); mController.addCallback(mCallback);
} }
@@ -121,4 +127,27 @@ public class RecordingControllerTest extends SysuiTestCase {
assertFalse(mController.isRecording()); assertFalse(mController.isRecording());
verify(mCallback).onRecordingEnd(); verify(mCallback).onRecordingEnd();
} }
// Test that switching users will stop an ongoing recording
@Test
public void testUserChange() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
// If we are recording
PendingIntent startIntent = Mockito.mock(PendingIntent.class);
PendingIntent stopIntent = Mockito.mock(PendingIntent.class);
mController.startCountdown(0, 0, startIntent, stopIntent);
mController.updateState(true);
// and user is changed
Intent intent = new Intent(Intent.ACTION_USER_SWITCHED)
.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID);
mController.mUserChangeReceiver.onReceive(mContext, intent);
// Ensure that the recording was stopped
verify(mCallback).onRecordingEnd();
assertFalse(mController.isRecording());
}
} }

View File

@@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.settings.CurrentUserContextTracker;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -58,6 +59,8 @@ public class RecordingServiceTest extends SysuiTestCase {
private Notification mNotification; private Notification mNotification;
@Mock @Mock
private Executor mExecutor; private Executor mExecutor;
@Mock
private CurrentUserContextTracker mUserContextTracker;
private RecordingService mRecordingService; private RecordingService mRecordingService;
@@ -65,7 +68,7 @@ public class RecordingServiceTest extends SysuiTestCase {
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mUiEventLogger, mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mUiEventLogger,
mNotificationManager)); mNotificationManager, mUserContextTracker));
// Return actual context info // Return actual context info
doReturn(mContext).when(mRecordingService).getApplicationContext(); doReturn(mContext).when(mRecordingService).getApplicationContext();
@@ -80,6 +83,8 @@ public class RecordingServiceTest extends SysuiTestCase {
doNothing().when(mRecordingService).startForeground(anyInt(), any()); doNothing().when(mRecordingService).startForeground(anyInt(), any());
doReturn(mScreenMediaRecorder).when(mRecordingService).getRecorder(); doReturn(mScreenMediaRecorder).when(mRecordingService).getRecorder();
doReturn(mContext).when(mUserContextTracker).getCurrentUserContext();
} }
@Test @Test