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

Commit 3df5bf52 authored by Beth Thibodeau's avatar Beth Thibodeau Committed by Android (Google) Code Review
Browse files

Merge "Enable secondary user to update controller status"

parents 97f66ef4 4e16d7ac
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -134,7 +134,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
        getHost().collapsePanels();
        Intent intent = mController.getPromptIntent();
        ActivityStarter.OnDismissAction dismissAction = () -> {
            mContext.startActivity(intent);
            mHost.getUserContext().startActivity(intent);
            return false;
        };
        mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false);
+28 −3
Original line number Diff line number Diff line
@@ -54,15 +54,32 @@ public class RecordingController
    private CountDownTimer mCountDownTimer = null;
    private BroadcastDispatcher mBroadcastDispatcher;

    protected static final String INTENT_UPDATE_STATE =
            "com.android.systemui.screenrecord.UPDATE_STATE";
    protected static final String EXTRA_STATE = "extra_state";

    private ArrayList<RecordingStateChangeCallback> mListeners = new ArrayList<>();

    @VisibleForTesting
    protected final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (mStopIntent != null) {
            stopRecording();
        }
    };

    @VisibleForTesting
    protected final BroadcastReceiver mStateChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent != null && INTENT_UPDATE_STATE.equals(intent.getAction())) {
                if (intent.hasExtra(EXTRA_STATE)) {
                    boolean state = intent.getBooleanExtra(EXTRA_STATE, false);
                    updateState(state);
                } else {
                    Log.e(TAG, "Received update intent with no state");
                }
            }
        }
    };

@@ -118,6 +135,10 @@ public class RecordingController
                    IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
                    mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, userFilter, null,
                            UserHandle.ALL);

                    IntentFilter stateFilter = new IntentFilter(INTENT_UPDATE_STATE);
                    mBroadcastDispatcher.registerReceiver(mStateChangeReceiver, stateFilter, null,
                            UserHandle.ALL);
                    Log.d(TAG, "sent start intent");
                } catch (PendingIntent.CanceledException e) {
                    Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
@@ -174,7 +195,6 @@ public class RecordingController
        } catch (PendingIntent.CanceledException e) {
            Log.e(TAG, "Error stopping: " + e.getMessage());
        }
        mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
    }

    /**
@@ -182,6 +202,11 @@ public class RecordingController
     * @param isRecording
     */
    public synchronized void updateState(boolean isRecording) {
        if (!isRecording && mIsRecording) {
            // Unregister receivers if we have stopped recording
            mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
            mBroadcastDispatcher.unregisterReceiver(mStateChangeReceiver);
        }
        mIsRecording = isRecording;
        for (RecordingStateChangeCallback cb : mListeners) {
            if (isRecording) {
+68 −16
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
    private static final String ACTION_STOP_NOTIF =
            "com.android.systemui.screenrecord.STOP_FROM_NOTIF";
    private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";

    private final RecordingController mController;
    private final KeyguardDismissUtil mKeyguardDismissUtil;
@@ -120,8 +121,8 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
        String action = intent.getAction();
        Log.d(TAG, "onStartCommand " + action);

        int mCurrentUserId = mUserContextTracker.getUserContext().getUserId();
        UserHandle currentUser = new UserHandle(mCurrentUserId);
        int currentUserId = mUserContextTracker.getUserContext().getUserId();
        UserHandle currentUser = new UserHandle(currentUserId);
        switch (action) {
            case ACTION_START:
                mAudioSource = ScreenRecordingAudioSource
@@ -137,11 +138,22 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis

                mRecorder = new ScreenMediaRecorder(
                        mUserContextTracker.getUserContext(),
                        mCurrentUserId,
                        currentUserId,
                        mAudioSource,
                        this
                );
                startRecording();

                if (startRecording()) {
                    updateState(true);
                    createRecordingNotification();
                    mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
                } else {
                    updateState(false);
                    createErrorNotification();
                    stopForeground(true);
                    stopSelf();
                    return Service.START_NOT_STICKY;
                }
                break;

            case ACTION_STOP_NOTIF:
@@ -201,22 +213,63 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
        return mRecorder;
    }

    private void updateState(boolean state) {
        int userId = mUserContextTracker.getUserContext().getUserId();
        if (userId == UserHandle.USER_SYSTEM) {
            // Main user has a reference to the correct controller, so no need to use a broadcast
            mController.updateState(state);
        } else {
            Intent intent = new Intent(RecordingController.INTENT_UPDATE_STATE);
            intent.putExtra(RecordingController.EXTRA_STATE, state);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            sendBroadcast(intent, PERMISSION_SELF);
        }
    }

    /**
     * Begin the recording session
     * @return true if successful, false if something went wrong
     */
    private void startRecording() {
    private boolean startRecording() {
        try {
            getRecorder().start();
            mController.updateState(true);
            createRecordingNotification();
            mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
        } catch (IOException | RemoteException | IllegalStateException e) {
            Toast.makeText(this,
                    R.string.screenrecord_start_error, Toast.LENGTH_LONG)
                    .show();
            return true;
        } catch (IOException | RemoteException | RuntimeException e) {
            showErrorToast(R.string.screenrecord_start_error);
            e.printStackTrace();
            mController.updateState(false);
        }
        return false;
    }

    /**
     * Simple error notification, needed since startForeground must be called to avoid errors
     */
    @VisibleForTesting
    protected void createErrorNotification() {
        Resources res = getResources();
        NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                getString(R.string.screenrecord_name),
                NotificationManager.IMPORTANCE_DEFAULT);
        channel.setDescription(getString(R.string.screenrecord_channel_description));
        channel.enableVibration(true);
        mNotificationManager.createNotificationChannel(channel);

        Bundle extras = new Bundle();
        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                res.getString(R.string.screenrecord_name));
        String notificationTitle = res.getString(R.string.screenrecord_start_error);

        Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_screenrecord)
                .setContentTitle(notificationTitle)
                .addExtras(extras);
        startForeground(NOTIFICATION_RECORDING_ID, builder.build());
    }

    @VisibleForTesting
    protected void showErrorToast(int stringId) {
        Toast.makeText(this, stringId, Toast.LENGTH_LONG).show();
    }

    @VisibleForTesting
@@ -326,7 +379,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
        } else {
            Log.e(TAG, "stopRecording called, but recorder was null");
        }
        mController.updateState(false);
        updateState(false);
    }

    private void saveRecording(int userId) {
@@ -344,8 +397,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
                }
            } catch (IOException e) {
                Log.e(TAG, "Error saving screen recording: " + e.getMessage());
                Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG)
                        .show();
                showErrorToast(R.string.screenrecord_delete_error);
            } finally {
                mNotificationManager.cancelAsUser(null, NOTIFICATION_PROCESSING_ID, currentUser);
            }
+2 −2
Original line number Diff line number Diff line
@@ -94,7 +94,7 @@ public class ScreenMediaRecorder {
        mAudioSource = audioSource;
    }

    private void prepare() throws IOException, RemoteException {
    private void prepare() throws IOException, RemoteException, RuntimeException {
        //Setup media projection
        IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
        IMediaProjectionManager mediaService =
@@ -257,7 +257,7 @@ public class ScreenMediaRecorder {
    /**
    * Start screen recording
    */
    void start() throws IOException, RemoteException, IllegalStateException {
    void start() throws IOException, RemoteException, RuntimeException {
        Log.d(TAG, "start recording");
        prepare();
        mMediaRecorder.start();
+31 −0
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.systemui.screenrecord;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;

import android.app.PendingIntent;
@@ -128,6 +130,35 @@ public class RecordingControllerTest extends SysuiTestCase {
        verify(mCallback).onRecordingEnd();
    }

    // Test that broadcast will update state
    @Test
    public void testUpdateStateBroadcast() {
        if (Looper.myLooper() == null) {
            Looper.prepare();
        }

        // When a recording has started
        PendingIntent startIntent = Mockito.mock(PendingIntent.class);
        mController.startCountdown(0, 0, startIntent, null);
        verify(mCallback).onCountdownEnd();

        // then the receiver was registered
        verify(mBroadcastDispatcher).registerReceiver(eq(mController.mStateChangeReceiver),
                any(), any(), any());

        // When the receiver gets an update
        Intent intent = new Intent(RecordingController.INTENT_UPDATE_STATE);
        intent.putExtra(RecordingController.EXTRA_STATE, false);
        mController.mStateChangeReceiver.onReceive(mContext, intent);

        // then the state is updated
        assertFalse(mController.isRecording());
        verify(mCallback).onRecordingEnd();

        // and the receiver is unregistered
        verify(mBroadcastDispatcher).unregisterReceiver(eq(mController.mStateChangeReceiver));
    }

    // Test that switching users will stop an ongoing recording
    @Test
    public void testUserChange() {
Loading