diff options
5 files changed, 239 insertions, 43 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 d7dd6f24ceb3..32ef063a55be 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.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; @@ -41,14 +42,16 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> 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 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> } 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 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> } 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 000000000000..9dede4848914 --- /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 2ddd6aaf4c40..f2e8599536ca 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.provider.Settings; 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 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis 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 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis 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 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis 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 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis 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 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis 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 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis 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 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis } } - private void createRecordingNotification() { + @VisibleForTesting + protected void createRecordingNotification() { Resources res = getResources(); NotificationChannel channel = new NotificationChannel( CHANNEL_ID, @@ -214,9 +232,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis 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 @@ 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); - 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 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis .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 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis 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 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis 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 8a8d2272d563..e5024595d97e 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 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; @@ -53,6 +54,8 @@ public class ScreenRecordTileTest extends SysuiTestCase { private ActivityStarter mActivityStarter; @Mock private QSTileHost mHost; + @Mock + private UiEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private ScreenRecordTile mTile; @@ -68,7 +71,7 @@ public class ScreenRecordTileTest extends SysuiTestCase { 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 000000000000..283a47ca3622 --- /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); + } +} |