Screen record user switching fixes
Allow opening the dialog in all users Stop recording when the user changes Post notifications in the correct user Still some issues with tile state and status bar but those require more work to coordinate across users, so may not make it into R (related bug: b/158004991) Fixes: 147921212 Bug: 148955577 Test: manual Test: atest ScreenRecordTileTest Test: atest com.android.systemui.screenrecord Change-Id: Idf10931c5ddf3079b903708824fae11135169c1b
This commit is contained in:
@@ -329,6 +329,7 @@
|
||||
|
||||
<activity android:name=".screenrecord.ScreenRecordDialog"
|
||||
android:theme="@style/ScreenRecord"
|
||||
android:showForAllUsers="true"
|
||||
android:excludeFromRecents="true" />
|
||||
<service android:name=".screenrecord.RecordingService" />
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.Switch;
|
||||
|
||||
import com.android.internal.logging.UiEventLogger;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.qs.QSTile;
|
||||
import com.android.systemui.qs.QSHost;
|
||||
import com.android.systemui.qs.tileimpl.QSTileImpl;
|
||||
import com.android.systemui.screenrecord.RecordingController;
|
||||
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -39,19 +39,17 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
||||
implements RecordingController.RecordingStateChangeCallback {
|
||||
private static final String TAG = "ScreenRecordTile";
|
||||
private RecordingController mController;
|
||||
private ActivityStarter mActivityStarter;
|
||||
private KeyguardDismissUtil mKeyguardDismissUtil;
|
||||
private long mMillisUntilFinished = 0;
|
||||
private Callback mCallback = new Callback();
|
||||
private UiEventLogger mUiEventLogger;
|
||||
|
||||
@Inject
|
||||
public ScreenRecordTile(QSHost host, RecordingController controller,
|
||||
ActivityStarter activityStarter, UiEventLogger uiEventLogger) {
|
||||
KeyguardDismissUtil keyguardDismissUtil) {
|
||||
super(host);
|
||||
mController = controller;
|
||||
mController.observe(this, mCallback);
|
||||
mActivityStarter = activityStarter;
|
||||
mUiEventLogger = uiEventLogger;
|
||||
mKeyguardDismissUtil = keyguardDismissUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,7 +67,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
||||
} else if (mController.isRecording()) {
|
||||
stopRecording();
|
||||
} else {
|
||||
startCountdown();
|
||||
mUiHandler.post(() -> showPrompt());
|
||||
}
|
||||
refreshState();
|
||||
}
|
||||
@@ -114,11 +112,15 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
||||
return mContext.getString(R.string.quick_settings_screen_record_label);
|
||||
}
|
||||
|
||||
private void startCountdown() {
|
||||
// Close QS, otherwise the permission dialog appears beneath it
|
||||
private void showPrompt() {
|
||||
// Close QS, otherwise the dialog appears beneath it
|
||||
getHost().collapsePanels();
|
||||
Intent intent = mController.getPromptIntent();
|
||||
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
|
||||
ActivityStarter.OnDismissAction dismissAction = () -> {
|
||||
mContext.startActivity(intent);
|
||||
return false;
|
||||
};
|
||||
mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false);
|
||||
}
|
||||
|
||||
private void cancelCountdown() {
|
||||
|
||||
@@ -17,12 +17,17 @@
|
||||
package com.android.systemui.screenrecord;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.statusbar.policy.CallbackController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -41,21 +46,30 @@ public class RecordingController
|
||||
private static final String SYSUI_SCREENRECORD_LAUNCHER =
|
||||
"com.android.systemui.screenrecord.ScreenRecordDialog";
|
||||
|
||||
private final Context mContext;
|
||||
private boolean mIsStarting;
|
||||
private boolean mIsRecording;
|
||||
private PendingIntent mStopIntent;
|
||||
private CountDownTimer mCountDownTimer = null;
|
||||
private BroadcastDispatcher mBroadcastDispatcher;
|
||||
|
||||
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
|
||||
* @param context Context for the controller
|
||||
*/
|
||||
@Inject
|
||||
public RecordingController(Context context) {
|
||||
mContext = context;
|
||||
public RecordingController(BroadcastDispatcher broadcastDispatcher) {
|
||||
mBroadcastDispatcher = broadcastDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,6 +113,9 @@ public class RecordingController
|
||||
}
|
||||
try {
|
||||
startIntent.send();
|
||||
IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
|
||||
mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, userFilter, null,
|
||||
UserHandle.ALL);
|
||||
Log.d(TAG, "sent start intent");
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
|
||||
@@ -146,11 +163,16 @@ public class RecordingController
|
||||
*/
|
||||
public void stopRecording() {
|
||||
try {
|
||||
mStopIntent.send();
|
||||
if (mStopIntent != null) {
|
||||
mStopIntent.send();
|
||||
} else {
|
||||
Log.e(TAG, "Stop intent was null");
|
||||
}
|
||||
updateState(false);
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(TAG, "Error stopping: " + e.getMessage());
|
||||
}
|
||||
mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
@@ -40,6 +41,7 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.logging.UiEventLogger;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.dagger.qualifiers.LongRunning;
|
||||
import com.android.systemui.settings.CurrentUserContextTracker;
|
||||
|
||||
import java.io.IOException;
|
||||
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 CHANNEL_ID = "screen_record";
|
||||
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_AUDIO_SOURCE = "extra_useAudio";
|
||||
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 UiEventLogger mUiEventLogger;
|
||||
private final NotificationManager mNotificationManager;
|
||||
private final CurrentUserContextTracker mUserContextTracker;
|
||||
|
||||
@Inject
|
||||
public RecordingService(RecordingController controller, @LongRunning Executor executor,
|
||||
UiEventLogger uiEventLogger, NotificationManager notificationManager) {
|
||||
UiEventLogger uiEventLogger, NotificationManager notificationManager,
|
||||
CurrentUserContextTracker userContextTracker) {
|
||||
mController = controller;
|
||||
mLongExecutor = executor;
|
||||
mUiEventLogger = uiEventLogger;
|
||||
mNotificationManager = notificationManager;
|
||||
mUserContextTracker = userContextTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,8 +99,6 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
||||
* @param context Context from the requesting activity
|
||||
* @param resultCode The result code from {@link android.app.Activity#onActivityResult(int, int,
|
||||
* 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
|
||||
* {@link com.android.systemui.screenrecord.ScreenRecordingAudioSource}
|
||||
* @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();
|
||||
Log.d(TAG, "onStartCommand " + action);
|
||||
|
||||
int mCurrentUserId = mUserContextTracker.getCurrentUserContext().getUserId();
|
||||
UserHandle currentUser = new UserHandle(mCurrentUserId);
|
||||
switch (action) {
|
||||
case ACTION_START:
|
||||
mAudioSource = ScreenRecordingAudioSource
|
||||
@@ -132,8 +136,8 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
||||
setTapsVisible(mShowTaps);
|
||||
|
||||
mRecorder = new ScreenMediaRecorder(
|
||||
getApplicationContext(),
|
||||
getUserId(),
|
||||
mUserContextTracker.getCurrentUserContext(),
|
||||
mCurrentUserId,
|
||||
mAudioSource,
|
||||
this
|
||||
);
|
||||
@@ -148,7 +152,14 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
||||
} else {
|
||||
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);
|
||||
stopSelf();
|
||||
break;
|
||||
@@ -165,7 +176,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
||||
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
|
||||
|
||||
// Remove notification
|
||||
mNotificationManager.cancel(NOTIFICATION_VIEW_ID);
|
||||
mNotificationManager.cancelAsUser(null, NOTIFICATION_VIEW_ID, currentUser);
|
||||
|
||||
startActivity(Intent.createChooser(shareIntent, shareLabel)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
@@ -184,7 +195,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
||||
Toast.LENGTH_LONG).show();
|
||||
|
||||
// Remove notification
|
||||
mNotificationManager.cancel(NOTIFICATION_VIEW_ID);
|
||||
mNotificationManager.cancelAsUser(null, NOTIFICATION_VIEW_ID, currentUser);
|
||||
Log.d(TAG, "Deleted recording " + uri);
|
||||
break;
|
||||
}
|
||||
@@ -215,11 +226,12 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
||||
mController.updateState(true);
|
||||
createRecordingNotification();
|
||||
mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
|
||||
} catch (IOException | RemoteException e) {
|
||||
} catch (IOException | RemoteException | IllegalStateException e) {
|
||||
Toast.makeText(this,
|
||||
R.string.screenrecord_start_error, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
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_and_audio);
|
||||
|
||||
|
||||
Intent stopIntent = getNotificationIntent(this);
|
||||
Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_screenrecord)
|
||||
@@ -254,7 +265,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
||||
.setOngoing(true)
|
||||
.setContentIntent(
|
||||
PendingIntent.getService(this, REQUEST_CODE, stopIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
|
||||
.addExtras(extras);
|
||||
startForeground(NOTIFICATION_RECORDING_ID, builder.build());
|
||||
}
|
||||
@@ -265,11 +276,17 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
||||
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);
|
||||
.setSmallIcon(R.drawable.ic_screenrecord)
|
||||
.addExtras(extras);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -287,7 +304,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
||||
this,
|
||||
REQUEST_CODE,
|
||||
getShareIntent(this, uri.toString()),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
|
||||
.build();
|
||||
|
||||
Notification.Action deleteAction = new Notification.Action.Builder(
|
||||
@@ -297,7 +314,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
||||
this,
|
||||
REQUEST_CODE,
|
||||
getDeleteIntent(this, uri.toString()),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
|
||||
.build();
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
@@ -328,34 +345,36 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void stopRecording() {
|
||||
private void stopRecording(int userId) {
|
||||
setTapsVisible(mOriginalShowTaps);
|
||||
if (getRecorder() != null) {
|
||||
getRecorder().end();
|
||||
saveRecording();
|
||||
saveRecording(userId);
|
||||
} else {
|
||||
Log.e(TAG, "stopRecording called, but recorder was null");
|
||||
}
|
||||
mController.updateState(false);
|
||||
}
|
||||
|
||||
private void saveRecording() {
|
||||
mNotificationManager.notify(NOTIFICATION_PROCESSING_ID, createProcessingNotification());
|
||||
private void saveRecording(int userId) {
|
||||
UserHandle currentUser = new UserHandle(userId);
|
||||
mNotificationManager.notifyAsUser(null, NOTIFICATION_PROCESSING_ID,
|
||||
createProcessingNotification(), currentUser);
|
||||
|
||||
mLongExecutor.execute(() -> {
|
||||
try {
|
||||
Log.d(TAG, "saving recording");
|
||||
Notification notification = createSaveNotification(getRecorder().save());
|
||||
if (!mController.isRecording()) {
|
||||
Log.d(TAG, "showing saved notification");
|
||||
mNotificationManager.notify(NOTIFICATION_VIEW_ID, notification);
|
||||
mNotificationManager.notifyAsUser(null, NOTIFICATION_VIEW_ID, notification,
|
||||
currentUser);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error saving screen recording: " + e.getMessage());
|
||||
Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
} 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
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.systemui.screenrecord;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioPlaybackCaptureConfiguration;
|
||||
@@ -39,7 +38,6 @@ public class ScreenInternalAudioRecorder {
|
||||
private static String TAG = "ScreenAudioRecorder";
|
||||
private static final int TIMEOUT = 500;
|
||||
private static final float MIC_VOLUME_SCALE = 1.4f;
|
||||
private final Context mContext;
|
||||
private AudioRecord mAudioRecord;
|
||||
private AudioRecord mAudioRecordMic;
|
||||
private Config mConfig = new Config();
|
||||
@@ -49,17 +47,14 @@ public class ScreenInternalAudioRecorder {
|
||||
private long mPresentationTime;
|
||||
private long mTotalBytes;
|
||||
private MediaMuxer mMuxer;
|
||||
private String mOutFile;
|
||||
private boolean mMic;
|
||||
|
||||
private int mTrackId = -1;
|
||||
|
||||
public ScreenInternalAudioRecorder(String outFile, Context context,
|
||||
MediaProjection mp, boolean includeMicInput) throws IOException {
|
||||
public ScreenInternalAudioRecorder(String outFile, MediaProjection mp, boolean includeMicInput)
|
||||
throws IOException {
|
||||
mMic = includeMicInput;
|
||||
mOutFile = outFile;
|
||||
mMuxer = new MediaMuxer(outFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
|
||||
mContext = context;
|
||||
mMediaProjection = mp;
|
||||
Log.d(TAG, "creating audio file " + outFile);
|
||||
setupSimple();
|
||||
@@ -266,8 +261,9 @@ public class ScreenInternalAudioRecorder {
|
||||
|
||||
/**
|
||||
* start recording
|
||||
* @throws IllegalStateException if recording fails to initialize
|
||||
*/
|
||||
public void start() {
|
||||
public void start() throws IllegalStateException {
|
||||
if (mThread != null) {
|
||||
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());
|
||||
mCodec.start();
|
||||
if (mAudioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
|
||||
Log.e(TAG, "Error starting audio recording");
|
||||
return;
|
||||
throw new IllegalStateException("Audio recording failed to start");
|
||||
}
|
||||
mThread.start();
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ public class ScreenMediaRecorder {
|
||||
mAudioSource == MIC_AND_INTERNAL) {
|
||||
mTempAudioFile = File.createTempFile("temp", ".aac",
|
||||
mContext.getCacheDir());
|
||||
mAudio = new ScreenInternalAudioRecorder(mTempAudioFile.getAbsolutePath(), mContext,
|
||||
mAudio = new ScreenInternalAudioRecorder(mTempAudioFile.getAbsolutePath(),
|
||||
mMediaProjection, mAudioSource == MIC_AND_INTERNAL);
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ public class ScreenMediaRecorder {
|
||||
/**
|
||||
* Start screen recording
|
||||
*/
|
||||
void start() throws IOException, RemoteException {
|
||||
void start() throws IOException, RemoteException, IllegalStateException {
|
||||
Log.d(TAG, "start recording");
|
||||
prepare();
|
||||
mMediaRecorder.start();
|
||||
@@ -205,7 +205,7 @@ public class ScreenMediaRecorder {
|
||||
}
|
||||
}
|
||||
|
||||
private void recordInternalAudio() {
|
||||
private void recordInternalAudio() throws IllegalStateException {
|
||||
if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) {
|
||||
mAudio.start();
|
||||
}
|
||||
|
||||
@@ -23,16 +23,19 @@ import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.NONE;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Switch;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.settings.CurrentUserContextTracker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -48,16 +51,17 @@ public class ScreenRecordDialog extends Activity {
|
||||
private static final String TAG = "ScreenRecordDialog";
|
||||
|
||||
private final RecordingController mController;
|
||||
private final CurrentUserContextTracker mCurrentUserContextTracker;
|
||||
private Switch mTapsSwitch;
|
||||
private Switch mAudioSwitch;
|
||||
private Spinner mOptions;
|
||||
private List<ScreenRecordingAudioSource> mModes;
|
||||
private int mSelected;
|
||||
|
||||
|
||||
@Inject
|
||||
public ScreenRecordDialog(RecordingController controller) {
|
||||
public ScreenRecordDialog(RecordingController controller,
|
||||
CurrentUserContextTracker currentUserContextTracker) {
|
||||
mController = controller;
|
||||
mCurrentUserContextTracker = currentUserContextTracker;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,6 +72,7 @@ public class ScreenRecordDialog extends Activity {
|
||||
// Inflate the decor view, so the attributes below are not overwritten by the theme.
|
||||
window.getDecorView();
|
||||
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
|
||||
window.setGravity(Gravity.TOP);
|
||||
setTitle(R.string.screenrecord_name);
|
||||
|
||||
@@ -100,24 +105,24 @@ public class ScreenRecordDialog extends Activity {
|
||||
mOptions.setOnItemClickListenerInt((parent, view, position, id) -> {
|
||||
mAudioSwitch.setChecked(true);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void requestScreenCapture() {
|
||||
Context userContext = mCurrentUserContextTracker.getCurrentUserContext();
|
||||
boolean showTaps = mTapsSwitch.isChecked();
|
||||
ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked()
|
||||
? (ScreenRecordingAudioSource) mOptions.getSelectedItem()
|
||||
: NONE;
|
||||
PendingIntent startIntent = PendingIntent.getForegroundService(this,
|
||||
PendingIntent startIntent = PendingIntent.getForegroundService(userContext,
|
||||
RecordingService.REQUEST_CODE,
|
||||
RecordingService.getStartIntent(
|
||||
ScreenRecordDialog.this, RESULT_OK,
|
||||
userContext, RESULT_OK,
|
||||
audioMode.ordinal(), showTaps),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
PendingIntent stopIntent = PendingIntent.getService(this,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
PendingIntent stopIntent = PendingIntent.getService(userContext,
|
||||
RecordingService.REQUEST_CODE,
|
||||
RecordingService.getStopIntent(this),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
RecordingService.getStopIntent(userContext),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,8 +688,8 @@ public class PhoneStatusBarPolicy
|
||||
if (DEBUG) Log.d(TAG, "screenrecord: hiding icon during countdown");
|
||||
mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
|
||||
// Reset talkback priority
|
||||
mIconController.setIconAccessibilityLiveRegion(mSlotScreenRecord,
|
||||
View.ACCESSIBILITY_LIVE_REGION_NONE);
|
||||
mHandler.post(() -> mIconController.setIconAccessibilityLiveRegion(mSlotScreenRecord,
|
||||
View.ACCESSIBILITY_LIVE_REGION_NONE));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -698,7 +698,7 @@ public class PhoneStatusBarPolicy
|
||||
mIconController.setIcon(mSlotScreenRecord,
|
||||
R.drawable.stat_sys_screen_record,
|
||||
mResources.getString(R.string.screenrecord_ongoing_screen_only));
|
||||
mIconController.setIconVisibility(mSlotScreenRecord, true);
|
||||
mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,13 +29,12 @@ import android.testing.TestableLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.logging.UiEventLogger;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.qs.QSTileHost;
|
||||
import com.android.systemui.screenrecord.RecordingController;
|
||||
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -44,18 +43,16 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
@SmallTest
|
||||
public class ScreenRecordTileTest extends SysuiTestCase {
|
||||
|
||||
@Mock
|
||||
private RecordingController mController;
|
||||
@Mock
|
||||
private ActivityStarter mActivityStarter;
|
||||
@Mock
|
||||
private QSTileHost mHost;
|
||||
@Mock
|
||||
private UiEventLogger mUiEventLogger;
|
||||
private KeyguardDismissUtil mKeyguardDismissUtil;
|
||||
|
||||
private TestableLooper mTestableLooper;
|
||||
private ScreenRecordTile mTile;
|
||||
@@ -67,11 +64,10 @@ public class ScreenRecordTileTest extends SysuiTestCase {
|
||||
mTestableLooper = TestableLooper.get(this);
|
||||
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
|
||||
mController = mDependency.injectMockDependency(RecordingController.class);
|
||||
mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
|
||||
|
||||
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
|
||||
@@ -89,6 +85,7 @@ public class ScreenRecordTileTest extends SysuiTestCase {
|
||||
mContext.getString(R.string.quick_settings_screen_record_start)));
|
||||
|
||||
mTile.handleClick();
|
||||
mTestableLooper.processAllMessages();
|
||||
verify(mController, times(1)).getPromptIntent();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,12 +22,14 @@ import static junit.framework.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.os.Looper;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -45,14 +47,18 @@ import org.mockito.MockitoAnnotations;
|
||||
public class RecordingControllerTest extends SysuiTestCase {
|
||||
|
||||
@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
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mController = new RecordingController(mContext);
|
||||
mController = new RecordingController(mBroadcastDispatcher);
|
||||
mController.addCallback(mCallback);
|
||||
}
|
||||
|
||||
@@ -121,4 +127,27 @@ public class RecordingControllerTest extends SysuiTestCase {
|
||||
assertFalse(mController.isRecording());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.logging.UiEventLogger;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.settings.CurrentUserContextTracker;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -58,6 +59,8 @@ public class RecordingServiceTest extends SysuiTestCase {
|
||||
private Notification mNotification;
|
||||
@Mock
|
||||
private Executor mExecutor;
|
||||
@Mock
|
||||
private CurrentUserContextTracker mUserContextTracker;
|
||||
|
||||
private RecordingService mRecordingService;
|
||||
|
||||
@@ -65,7 +68,7 @@ public class RecordingServiceTest extends SysuiTestCase {
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mUiEventLogger,
|
||||
mNotificationManager));
|
||||
mNotificationManager, mUserContextTracker));
|
||||
|
||||
// Return actual context info
|
||||
doReturn(mContext).when(mRecordingService).getApplicationContext();
|
||||
@@ -80,6 +83,8 @@ public class RecordingServiceTest extends SysuiTestCase {
|
||||
|
||||
doNothing().when(mRecordingService).startForeground(anyInt(), any());
|
||||
doReturn(mScreenMediaRecorder).when(mRecordingService).getRecorder();
|
||||
|
||||
doReturn(mContext).when(mUserContextTracker).getCurrentUserContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user