Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b927882b authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Add logging for screen recording" into rvc-dev am: 8a9a2f80

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/10502589

Change-Id: I721cf10e5bed0fd4c7204dab43a2c1178102d664
parents ba6d0439 8a9a2f80
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -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();
    }

+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;
        }
    }
}
+72 −39
Original line number Diff line number Diff line
@@ -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());
    }

    @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)
@@ -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);
+4 −1
Original line number Diff line number Diff line
@@ -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
+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);
    }
}