summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Beth Thibodeau <ethibodeau@google.com> 2020-11-23 16:21:32 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-11-23 16:21:32 +0000
commit3df5bf528f842b25875f0a17109d2427edb76ce0 (patch)
treeec979d073cad48bdead484cad448705c0f98698b
parent97f66ef47396478c5743b8410802d5dbd4b7f0f2 (diff)
parent4e16d7ac686c0c9db656e90a8bb2eca2c789e1ba (diff)
Merge "Enable secondary user to update controller status"
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java84
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java19
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);
+ }
}