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:
Beth Thibodeau
2020-06-17 22:34:42 -04:00
parent 2ba2ce1282
commit 231ac9bced
11 changed files with 153 additions and 76 deletions

View File

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

View File

@@ -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() {

View File

@@ -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);
}
/**

View File

@@ -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());
}
/**

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

@@ -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