From 231ac9bcedb0133720729f9d73fe9667a5580264 Mon Sep 17 00:00:00 2001 From: Beth Thibodeau Date: Wed, 17 Jun 2020 22:34:42 -0400 Subject: [PATCH] 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 --- packages/SystemUI/AndroidManifest.xml | 1 + .../systemui/qs/tiles/ScreenRecordTile.java | 22 +++--- .../screenrecord/RecordingController.java | 32 +++++++-- .../screenrecord/RecordingService.java | 67 ++++++++++++------- .../ScreenInternalAudioRecorder.java | 15 ++--- .../screenrecord/ScreenMediaRecorder.java | 6 +- .../screenrecord/ScreenRecordDialog.java | 25 ++++--- .../statusbar/phone/PhoneStatusBarPolicy.java | 6 +- .../qs/tiles/ScreenRecordTileTest.java | 13 ++-- .../screenrecord/RecordingControllerTest.java | 35 +++++++++- .../screenrecord/RecordingServiceTest.java | 7 +- 11 files changed, 153 insertions(+), 76 deletions(-) diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 5b6155180e0ad..a7ef5e6f58f0f 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -329,6 +329,7 @@ diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index 32ef063a55be1..0c34b27d348e6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -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 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 } else if (mController.isRecording()) { stopRecording(); } else { - startCountdown(); + mUiHandler.post(() -> showPrompt()); } refreshState(); } @@ -114,11 +112,15 @@ public class ScreenRecordTile extends QSTileImpl 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() { diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index b253635e9bfa7..82ac1f6f6a335 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -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 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); } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 87597263168a1..476ec798a35f1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -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()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java index edbc3cfdece5d..df03c3e08f087 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java @@ -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(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java index 1c7d987afff29..1a9abb9cf27d4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java @@ -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(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java index 8347def2d4302..dc47ab4dff631 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java @@ -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 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); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 06d35a36e3c40..5bb8fab8a62e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index e5024595d97e2..5a68238799423 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -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(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index b877c7fa6859f..11ef3e33f9d0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -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()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java index 283a47ca3622f..e98b6b69ee768 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java @@ -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