Loading packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +4 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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(); Loading @@ -125,7 +127,6 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> } private void stopRecording() { Log.d(TAG, "Stopping recording from tile"); mController.stopRecording(); } Loading packages/SystemUI/src/com/android/systemui/screenrecord/Events.java 0 → 100644 +45 −0 Original line number Diff line number Diff line /* * 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; } } } packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +72 −39 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } /** Loading Loading @@ -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 Loading @@ -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; Loading @@ -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)); Loading @@ -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; } Loading @@ -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) Loading @@ -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, Loading @@ -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, Loading @@ -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)) Loading @@ -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()); } @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(); } private Notification createSaveNotification(ScreenMediaRecorder.SavedRecording recording) { @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) Loading Loading @@ -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); } /** Loading @@ -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); Loading packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +4 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java 0 → 100644 +114 −0 Original line number Diff line number Diff line /* * 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); } } Loading
packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +4 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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(); Loading @@ -125,7 +127,6 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> } private void stopRecording() { Log.d(TAG, "Stopping recording from tile"); mController.stopRecording(); } Loading
packages/SystemUI/src/com/android/systemui/screenrecord/Events.java 0 → 100644 +45 −0 Original line number Diff line number Diff line /* * 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; } } }
packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +72 −39 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } /** Loading Loading @@ -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 Loading @@ -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; Loading @@ -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)); Loading @@ -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; } Loading @@ -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) Loading @@ -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, Loading @@ -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, Loading @@ -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)) Loading @@ -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()); } @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(); } private Notification createSaveNotification(ScreenMediaRecorder.SavedRecording recording) { @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) Loading Loading @@ -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); } /** Loading @@ -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); Loading
packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +4 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading
packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java 0 → 100644 +114 −0 Original line number Diff line number Diff line /* * 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); } }