Merge "Add logging for screen recording" into rvc-dev
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 d7dd6f2..32ef063 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -22,6 +22,7 @@
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;
@@ -41,14 +42,16 @@
private ActivityStarter mActivityStarter;
private long mMillisUntilFinished = 0;
private Callback mCallback = new Callback();
+ private UiEventLogger mUiEventLogger;
@Inject
public ScreenRecordTile(QSHost host, RecordingController controller,
- ActivityStarter activityStarter) {
+ ActivityStarter activityStarter, UiEventLogger uiEventLogger) {
super(host);
mController = controller;
mController.observe(this, mCallback);
mActivityStarter = activityStarter;
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -112,7 +115,6 @@
}
private void startCountdown() {
- Log.d(TAG, "Starting countdown");
// Close QS, otherwise the permission dialog appears beneath it
getHost().collapsePanels();
Intent intent = mController.getPromptIntent();
@@ -125,7 +127,6 @@
}
private void stopRecording() {
- Log.d(TAG, "Stopping recording from tile");
mController.stopRecording();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/Events.java b/packages/SystemUI/src/com/android/systemui/screenrecord/Events.java
new file mode 100644
index 0000000..9dede48
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/Events.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+/**
+ * Events related to the SystemUI screen recorder
+ */
+public class Events {
+
+ public enum ScreenRecordEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Screen recording was started")
+ SCREEN_RECORD_START(299),
+ @UiEvent(doc = "Screen recording was stopped from the quick settings tile")
+ SCREEN_RECORD_END_QS_TILE(300),
+ @UiEvent(doc = "Screen recording was stopped from the notification")
+ SCREEN_RECORD_END_NOTIFICATION(301);
+
+ private final int mId;
+ ScreenRecordEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 2ddd6aa..f2e8599 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -36,6 +36,8 @@
import android.util.Log;
import android.widget.Toast;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.LongRunning;
@@ -63,22 +65,28 @@
private static final String ACTION_START = "com.android.systemui.screenrecord.START";
private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
+ 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 ACTION_DELETE = "com.android.systemui.screenrecord.DELETE";
private final RecordingController mController;
- private Notification.Builder mRecordingNotificationBuilder;
private ScreenRecordingAudioSource mAudioSource;
private boolean mShowTaps;
private boolean mOriginalShowTaps;
private ScreenMediaRecorder mRecorder;
private final Executor mLongExecutor;
+ private final UiEventLogger mUiEventLogger;
+ private final NotificationManager mNotificationManager;
@Inject
- public RecordingService(RecordingController controller, @LongRunning Executor executor) {
+ public RecordingService(RecordingController controller, @LongRunning Executor executor,
+ UiEventLogger uiEventLogger, NotificationManager notificationManager) {
mController = controller;
mLongExecutor = executor;
+ mUiEventLogger = uiEventLogger;
+ mNotificationManager = notificationManager;
}
/**
@@ -110,9 +118,6 @@
String action = intent.getAction();
Log.d(TAG, "onStartCommand " + action);
- NotificationManager notificationManager =
- (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-
switch (action) {
case ACTION_START:
mAudioSource = ScreenRecordingAudioSource
@@ -135,10 +140,16 @@
startRecording();
break;
+ case ACTION_STOP_NOTIF:
case ACTION_STOP:
+ // only difference for actions is the log event
+ if (ACTION_STOP_NOTIF.equals(action)) {
+ mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_END_NOTIFICATION);
+ } else {
+ mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_END_QS_TILE);
+ }
stopRecording();
- notificationManager.cancel(NOTIFICATION_RECORDING_ID);
- saveRecording(notificationManager);
+ mNotificationManager.cancel(NOTIFICATION_RECORDING_ID);
stopSelf();
break;
@@ -154,7 +165,7 @@
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
// Remove notification
- notificationManager.cancel(NOTIFICATION_VIEW_ID);
+ mNotificationManager.cancel(NOTIFICATION_VIEW_ID);
startActivity(Intent.createChooser(shareIntent, shareLabel)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
@@ -173,7 +184,7 @@
Toast.LENGTH_LONG).show();
// Remove notification
- notificationManager.cancel(NOTIFICATION_VIEW_ID);
+ mNotificationManager.cancel(NOTIFICATION_VIEW_ID);
Log.d(TAG, "Deleted recording " + uri);
break;
}
@@ -190,14 +201,20 @@
super.onCreate();
}
+ @VisibleForTesting
+ protected ScreenMediaRecorder getRecorder() {
+ return mRecorder;
+ }
+
/**
* Begin the recording session
*/
private void startRecording() {
try {
- mRecorder.start();
+ getRecorder().start();
mController.updateState(true);
createRecordingNotification();
+ mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
} catch (IOException | RemoteException e) {
Toast.makeText(this,
R.string.screenrecord_start_error, Toast.LENGTH_LONG)
@@ -206,7 +223,8 @@
}
}
- private void createRecordingNotification() {
+ @VisibleForTesting
+ protected void createRecordingNotification() {
Resources res = getResources();
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
@@ -214,9 +232,7 @@
NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription(getString(R.string.screenrecord_channel_description));
channel.enableVibration(true);
- NotificationManager notificationManager =
- (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.createNotificationChannel(channel);
+ mNotificationManager.createNotificationChannel(channel);
Bundle extras = new Bundle();
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
@@ -226,7 +242,9 @@
? res.getString(R.string.screenrecord_ongoing_screen_only)
: res.getString(R.string.screenrecord_ongoing_screen_and_audio);
- mRecordingNotificationBuilder = new Notification.Builder(this, CHANNEL_ID)
+
+ Intent stopIntent = getNotificationIntent(this);
+ Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_screenrecord)
.setContentTitle(notificationTitle)
.setContentText(getResources().getString(R.string.screenrecord_stop_text))
@@ -235,17 +253,28 @@
.setColor(getResources().getColor(R.color.GM2_red_700))
.setOngoing(true)
.setContentIntent(
- PendingIntent.getService(
- this, REQUEST_CODE, getStopIntent(this),
+ PendingIntent.getService(this, REQUEST_CODE, stopIntent,
PendingIntent.FLAG_UPDATE_CURRENT))
.addExtras(extras);
- notificationManager.notify(NOTIFICATION_RECORDING_ID,
- mRecordingNotificationBuilder.build());
- Notification notification = mRecordingNotificationBuilder.build();
- startForeground(NOTIFICATION_RECORDING_ID, notification);
+ startForeground(NOTIFICATION_RECORDING_ID, builder.build());
}
- private Notification createSaveNotification(ScreenMediaRecorder.SavedRecording recording) {
+ @VisibleForTesting
+ protected Notification createProcessingNotification() {
+ Resources res = getApplicationContext().getResources();
+ String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
+ ? res.getString(R.string.screenrecord_ongoing_screen_only)
+ : res.getString(R.string.screenrecord_ongoing_screen_and_audio);
+ 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);
+ return builder.build();
+ }
+
+ @VisibleForTesting
+ protected Notification createSaveNotification(ScreenMediaRecorder.SavedRecording recording) {
Uri uri = recording.getUri();
Intent viewIntent = new Intent(Intent.ACTION_VIEW)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION)
@@ -301,44 +330,39 @@
private void stopRecording() {
setTapsVisible(mOriginalShowTaps);
- mRecorder.end();
+ if (getRecorder() != null) {
+ getRecorder().end();
+ saveRecording();
+ } else {
+ Log.e(TAG, "stopRecording called, but recorder was null");
+ }
mController.updateState(false);
}
- private void saveRecording(NotificationManager notificationManager) {
- Resources res = getApplicationContext().getResources();
- String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
- ? res.getString(R.string.screenrecord_ongoing_screen_only)
- : res.getString(R.string.screenrecord_ongoing_screen_and_audio);
- 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);
- notificationManager.notify(NOTIFICATION_PROCESSING_ID, builder.build());
+ private void saveRecording() {
+ mNotificationManager.notify(NOTIFICATION_PROCESSING_ID, createProcessingNotification());
mLongExecutor.execute(() -> {
try {
Log.d(TAG, "saving recording");
- Notification notification = createSaveNotification(mRecorder.save());
+ Notification notification = createSaveNotification(getRecorder().save());
if (!mController.isRecording()) {
Log.d(TAG, "showing saved notification");
- notificationManager.notify(NOTIFICATION_VIEW_ID, notification);
+ mNotificationManager.notify(NOTIFICATION_VIEW_ID, notification);
}
} 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 {
- notificationManager.cancel(NOTIFICATION_PROCESSING_ID);
+ mNotificationManager.cancel(NOTIFICATION_PROCESSING_ID);
}
});
}
private void setTapsVisible(boolean turnOn) {
int value = turnOn ? 1 : 0;
- Settings.System.putInt(getApplicationContext().getContentResolver(),
- Settings.System.SHOW_TOUCHES, value);
+ Settings.System.putInt(getContentResolver(), Settings.System.SHOW_TOUCHES, value);
}
/**
@@ -350,6 +374,15 @@
return new Intent(context, RecordingService.class).setAction(ACTION_STOP);
}
+ /**
+ * Get the recording notification content intent
+ * @param context
+ * @return
+ */
+ protected static Intent getNotificationIntent(Context context) {
+ return new Intent(context, RecordingService.class).setAction(ACTION_STOP_NOTIF);
+ }
+
private static Intent getShareIntent(Context context, String path) {
return new Intent(context, RecordingService.class).setAction(ACTION_SHARE)
.putExtra(EXTRA_PATH, path);
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 8a8d227..e502459 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,6 +29,7 @@
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;
@@ -53,6 +54,8 @@
private ActivityStarter mActivityStarter;
@Mock
private QSTileHost mHost;
+ @Mock
+ private UiEventLogger mUiEventLogger;
private TestableLooper mTestableLooper;
private ScreenRecordTile mTile;
@@ -68,7 +71,7 @@
when(mHost.getContext()).thenReturn(mContext);
- mTile = new ScreenRecordTile(mHost, mController, mActivityStarter);
+ mTile = new ScreenRecordTile(mHost, mController, mActivityStarter, mUiEventLogger);
}
// Test that the tile is inactive and labeled correctly when the controller is neither starting
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
new file mode 100644
index 0000000..283a47c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+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.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class RecordingServiceTest extends SysuiTestCase {
+
+ @Mock
+ private UiEventLogger mUiEventLogger;
+ @Mock
+ private RecordingController mController;
+ @Mock
+ private NotificationManager mNotificationManager;
+ @Mock
+ private ScreenMediaRecorder mScreenMediaRecorder;
+ @Mock
+ private Notification mNotification;
+ @Mock
+ private Executor mExecutor;
+
+ private RecordingService mRecordingService;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mUiEventLogger,
+ mNotificationManager));
+
+ // Return actual context info
+ doReturn(mContext).when(mRecordingService).getApplicationContext();
+ doReturn(mContext.getUserId()).when(mRecordingService).getUserId();
+ doReturn(mContext.getPackageName()).when(mRecordingService).getPackageName();
+ doReturn(mContext.getContentResolver()).when(mRecordingService).getContentResolver();
+
+ // Mock notifications
+ doNothing().when(mRecordingService).createRecordingNotification();
+ doReturn(mNotification).when(mRecordingService).createProcessingNotification();
+ doReturn(mNotification).when(mRecordingService).createSaveNotification(any());
+
+ doNothing().when(mRecordingService).startForeground(anyInt(), any());
+ doReturn(mScreenMediaRecorder).when(mRecordingService).getRecorder();
+ }
+
+ @Test
+ public void testLogStartRecording() {
+ Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false);
+ mRecordingService.onStartCommand(startIntent, 0, 0);
+
+ verify(mUiEventLogger, times(1)).log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
+ }
+
+ @Test
+ public void testLogStopFromQsTile() {
+ Intent stopIntent = RecordingService.getStopIntent(mContext);
+ mRecordingService.onStartCommand(stopIntent, 0, 0);
+
+ // Verify that we log the correct event
+ verify(mUiEventLogger, times(1)).log(Events.ScreenRecordEvent.SCREEN_RECORD_END_QS_TILE);
+ verify(mUiEventLogger, times(0))
+ .log(Events.ScreenRecordEvent.SCREEN_RECORD_END_NOTIFICATION);
+ }
+
+ @Test
+ public void testLogStopFromNotificationIntent() {
+ Intent stopIntent = RecordingService.getNotificationIntent(mContext);
+ mRecordingService.onStartCommand(stopIntent, 0, 0);
+
+ // Verify that we log the correct event
+ verify(mUiEventLogger, times(1))
+ .log(Events.ScreenRecordEvent.SCREEN_RECORD_END_NOTIFICATION);
+ verify(mUiEventLogger, times(0)).log(Events.ScreenRecordEvent.SCREEN_RECORD_END_QS_TILE);
+ }
+}