diff options
| author | 2020-11-23 16:21:32 +0000 | |
|---|---|---|
| committer | 2020-11-23 16:21:32 +0000 | |
| commit | 3df5bf528f842b25875f0a17109d2427edb76ce0 (patch) | |
| tree | ec979d073cad48bdead484cad448705c0f98698b | |
| parent | 97f66ef47396478c5743b8410802d5dbd4b7f0f2 (diff) | |
| parent | 4e16d7ac686c0c9db656e90a8bb2eca2c789e1ba (diff) | |
Merge "Enable secondary user to update controller status"
6 files changed, 149 insertions, 22 deletions
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 d7a2975a4b04..0f0a9a2766f1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -134,7 +134,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> getHost().collapsePanels(); Intent intent = mController.getPromptIntent(); ActivityStarter.OnDismissAction dismissAction = () -> { - mContext.startActivity(intent); + mHost.getUserContext().startActivity(intent); return false; }; mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false); diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index 10a44ddb17f2..7ca82774f737 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -54,14 +54,31 @@ public class RecordingController private CountDownTimer mCountDownTimer = null; private BroadcastDispatcher mBroadcastDispatcher; + protected static final String INTENT_UPDATE_STATE = + "com.android.systemui.screenrecord.UPDATE_STATE"; + protected static final String EXTRA_STATE = "extra_state"; + 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(); + stopRecording(); + } + }; + + @VisibleForTesting + protected final BroadcastReceiver mStateChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null && INTENT_UPDATE_STATE.equals(intent.getAction())) { + if (intent.hasExtra(EXTRA_STATE)) { + boolean state = intent.getBooleanExtra(EXTRA_STATE, false); + updateState(state); + } else { + Log.e(TAG, "Received update intent with no state"); + } } } }; @@ -118,6 +135,10 @@ public class RecordingController IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_SWITCHED); mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, userFilter, null, UserHandle.ALL); + + IntentFilter stateFilter = new IntentFilter(INTENT_UPDATE_STATE); + mBroadcastDispatcher.registerReceiver(mStateChangeReceiver, stateFilter, null, + UserHandle.ALL); Log.d(TAG, "sent start intent"); } catch (PendingIntent.CanceledException e) { Log.e(TAG, "Pending intent was cancelled: " + e.getMessage()); @@ -174,7 +195,6 @@ public class RecordingController } catch (PendingIntent.CanceledException e) { Log.e(TAG, "Error stopping: " + e.getMessage()); } - mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver); } /** @@ -182,6 +202,11 @@ public class RecordingController * @param isRecording */ public synchronized void updateState(boolean isRecording) { + if (!isRecording && mIsRecording) { + // Unregister receivers if we have stopped recording + mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver); + mBroadcastDispatcher.unregisterReceiver(mStateChangeReceiver); + } mIsRecording = isRecording; for (RecordingStateChangeCallback cb : mListeners) { if (isRecording) { diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 3bf118d1f39f..c3a435747c6e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -69,6 +69,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis private static final String ACTION_STOP_NOTIF = "com.android.systemui.screenrecord.STOP_FROM_NOTIF"; private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE"; + private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; private final RecordingController mController; private final KeyguardDismissUtil mKeyguardDismissUtil; @@ -120,8 +121,8 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis String action = intent.getAction(); Log.d(TAG, "onStartCommand " + action); - int mCurrentUserId = mUserContextTracker.getUserContext().getUserId(); - UserHandle currentUser = new UserHandle(mCurrentUserId); + int currentUserId = mUserContextTracker.getUserContext().getUserId(); + UserHandle currentUser = new UserHandle(currentUserId); switch (action) { case ACTION_START: mAudioSource = ScreenRecordingAudioSource @@ -137,11 +138,22 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis mRecorder = new ScreenMediaRecorder( mUserContextTracker.getUserContext(), - mCurrentUserId, + currentUserId, mAudioSource, this ); - startRecording(); + + if (startRecording()) { + updateState(true); + createRecordingNotification(); + mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START); + } else { + updateState(false); + createErrorNotification(); + stopForeground(true); + stopSelf(); + return Service.START_NOT_STICKY; + } break; case ACTION_STOP_NOTIF: @@ -201,22 +213,63 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis return mRecorder; } + private void updateState(boolean state) { + int userId = mUserContextTracker.getUserContext().getUserId(); + if (userId == UserHandle.USER_SYSTEM) { + // Main user has a reference to the correct controller, so no need to use a broadcast + mController.updateState(state); + } else { + Intent intent = new Intent(RecordingController.INTENT_UPDATE_STATE); + intent.putExtra(RecordingController.EXTRA_STATE, state); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + sendBroadcast(intent, PERMISSION_SELF); + } + } + /** * Begin the recording session + * @return true if successful, false if something went wrong */ - private void startRecording() { + private boolean startRecording() { try { getRecorder().start(); - mController.updateState(true); - createRecordingNotification(); - mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START); - } catch (IOException | RemoteException | IllegalStateException e) { - Toast.makeText(this, - R.string.screenrecord_start_error, Toast.LENGTH_LONG) - .show(); + return true; + } catch (IOException | RemoteException | RuntimeException e) { + showErrorToast(R.string.screenrecord_start_error); e.printStackTrace(); - mController.updateState(false); } + return false; + } + + /** + * Simple error notification, needed since startForeground must be called to avoid errors + */ + @VisibleForTesting + protected void createErrorNotification() { + Resources res = getResources(); + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID, + getString(R.string.screenrecord_name), + NotificationManager.IMPORTANCE_DEFAULT); + channel.setDescription(getString(R.string.screenrecord_channel_description)); + channel.enableVibration(true); + mNotificationManager.createNotificationChannel(channel); + + Bundle extras = new Bundle(); + extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, + res.getString(R.string.screenrecord_name)); + String notificationTitle = res.getString(R.string.screenrecord_start_error); + + Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_screenrecord) + .setContentTitle(notificationTitle) + .addExtras(extras); + startForeground(NOTIFICATION_RECORDING_ID, builder.build()); + } + + @VisibleForTesting + protected void showErrorToast(int stringId) { + Toast.makeText(this, stringId, Toast.LENGTH_LONG).show(); } @VisibleForTesting @@ -326,7 +379,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis } else { Log.e(TAG, "stopRecording called, but recorder was null"); } - mController.updateState(false); + updateState(false); } private void saveRecording(int userId) { @@ -344,8 +397,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis } } catch (IOException e) { Log.e(TAG, "Error saving screen recording: " + e.getMessage()); - Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG) - .show(); + showErrorToast(R.string.screenrecord_delete_error); } finally { mNotificationManager.cancelAsUser(null, NOTIFICATION_PROCESSING_ID, currentUser); } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java index 45564b0bfa6b..9037192e2ddc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java @@ -94,7 +94,7 @@ public class ScreenMediaRecorder { mAudioSource = audioSource; } - private void prepare() throws IOException, RemoteException { + private void prepare() throws IOException, RemoteException, RuntimeException { //Setup media projection IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE); IMediaProjectionManager mediaService = @@ -257,7 +257,7 @@ public class ScreenMediaRecorder { /** * Start screen recording */ - void start() throws IOException, RemoteException, IllegalStateException { + void start() throws IOException, RemoteException, RuntimeException { Log.d(TAG, "start recording"); prepare(); mMediaRecorder.start(); 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 11ef3e33f9d0..b7cc651dc24b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -19,6 +19,8 @@ package com.android.systemui.screenrecord; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import android.app.PendingIntent; @@ -128,6 +130,35 @@ public class RecordingControllerTest extends SysuiTestCase { verify(mCallback).onRecordingEnd(); } + // Test that broadcast will update state + @Test + public void testUpdateStateBroadcast() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + // When a recording has started + PendingIntent startIntent = Mockito.mock(PendingIntent.class); + mController.startCountdown(0, 0, startIntent, null); + verify(mCallback).onCountdownEnd(); + + // then the receiver was registered + verify(mBroadcastDispatcher).registerReceiver(eq(mController.mStateChangeReceiver), + any(), any(), any()); + + // When the receiver gets an update + Intent intent = new Intent(RecordingController.INTENT_UPDATE_STATE); + intent.putExtra(RecordingController.EXTRA_STATE, false); + mController.mStateChangeReceiver.onReceive(mContext, intent); + + // then the state is updated + assertFalse(mController.isRecording()); + verify(mCallback).onRecordingEnd(); + + // and the receiver is unregistered + verify(mBroadcastDispatcher).unregisterReceiver(eq(mController.mStateChangeReceiver)); + } + // Test that switching users will stop an ongoing recording @Test public void testUserChange() { 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 3e37fde84544..91cafead596c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java @@ -17,15 +17,18 @@ package com.android.systemui.screenrecord; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.Notification; import android.app.NotificationManager; import android.content.Intent; +import android.os.RemoteException; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; @@ -43,6 +46,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.io.IOException; import java.util.concurrent.Executor; @RunWith(AndroidTestingRunner.class) @@ -88,6 +92,9 @@ public class RecordingServiceTest extends SysuiTestCase { doNothing().when(mRecordingService).createRecordingNotification(); doReturn(mNotification).when(mRecordingService).createProcessingNotification(); doReturn(mNotification).when(mRecordingService).createSaveNotification(any()); + doNothing().when(mRecordingService).createErrorNotification(); + doNothing().when(mRecordingService).showErrorToast(anyInt()); + doNothing().when(mRecordingService).stopForeground(anyBoolean()); doNothing().when(mRecordingService).startForeground(anyInt(), any()); doReturn(mScreenMediaRecorder).when(mRecordingService).getRecorder(); @@ -124,4 +131,16 @@ public class RecordingServiceTest extends SysuiTestCase { .log(Events.ScreenRecordEvent.SCREEN_RECORD_END_NOTIFICATION); verify(mUiEventLogger, times(0)).log(Events.ScreenRecordEvent.SCREEN_RECORD_END_QS_TILE); } + + @Test + public void testErrorUpdatesState() throws IOException, RemoteException { + // When the screen recording does not start properly + doThrow(new RuntimeException("fail")).when(mScreenMediaRecorder).start(); + + Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false); + mRecordingService.onStartCommand(startIntent, 0, 0); + + // Then the state is set to not recording + verify(mController).updateState(false); + } } |