Merge "[Partial screen sharing] Handle system-requested media projection stop in SysUI screen recorder" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
91066c7d3c
@@ -29,6 +29,7 @@ import android.graphics.drawable.Icon;
|
|||||||
import android.media.MediaRecorder;
|
import android.media.MediaRecorder;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
@@ -40,6 +41,8 @@ import com.android.internal.annotations.VisibleForTesting;
|
|||||||
import com.android.internal.logging.UiEventLogger;
|
import com.android.internal.logging.UiEventLogger;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.dagger.qualifiers.LongRunning;
|
import com.android.systemui.dagger.qualifiers.LongRunning;
|
||||||
|
import com.android.systemui.dagger.qualifiers.Main;
|
||||||
|
import com.android.systemui.screenrecord.ScreenMediaRecorder.ScreenMediaRecorderListener;
|
||||||
import com.android.systemui.settings.UserContextProvider;
|
import com.android.systemui.settings.UserContextProvider;
|
||||||
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
||||||
|
|
||||||
@@ -51,9 +54,10 @@ import javax.inject.Inject;
|
|||||||
/**
|
/**
|
||||||
* A service which records the device screen and optionally microphone input.
|
* A service which records the device screen and optionally microphone input.
|
||||||
*/
|
*/
|
||||||
public class RecordingService extends Service implements MediaRecorder.OnInfoListener {
|
public class RecordingService extends Service implements ScreenMediaRecorderListener {
|
||||||
public static final int REQUEST_CODE = 2;
|
public static final int REQUEST_CODE = 2;
|
||||||
|
|
||||||
|
private static final int USER_ID_NOT_SPECIFIED = -1;
|
||||||
private static final int NOTIFICATION_RECORDING_ID = 4274;
|
private static final int NOTIFICATION_RECORDING_ID = 4274;
|
||||||
private static final int NOTIFICATION_PROCESSING_ID = 4275;
|
private static final int NOTIFICATION_PROCESSING_ID = 4275;
|
||||||
private static final int NOTIFICATION_VIEW_ID = 4273;
|
private static final int NOTIFICATION_VIEW_ID = 4273;
|
||||||
@@ -73,6 +77,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
|||||||
|
|
||||||
private final RecordingController mController;
|
private final RecordingController mController;
|
||||||
private final KeyguardDismissUtil mKeyguardDismissUtil;
|
private final KeyguardDismissUtil mKeyguardDismissUtil;
|
||||||
|
private final Handler mMainHandler;
|
||||||
private ScreenRecordingAudioSource mAudioSource;
|
private ScreenRecordingAudioSource mAudioSource;
|
||||||
private boolean mShowTaps;
|
private boolean mShowTaps;
|
||||||
private boolean mOriginalShowTaps;
|
private boolean mOriginalShowTaps;
|
||||||
@@ -84,10 +89,12 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RecordingService(RecordingController controller, @LongRunning Executor executor,
|
public RecordingService(RecordingController controller, @LongRunning Executor executor,
|
||||||
UiEventLogger uiEventLogger, NotificationManager notificationManager,
|
@Main Handler handler, UiEventLogger uiEventLogger,
|
||||||
|
NotificationManager notificationManager,
|
||||||
UserContextProvider userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
|
UserContextProvider userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
|
||||||
mController = controller;
|
mController = controller;
|
||||||
mLongExecutor = executor;
|
mLongExecutor = executor;
|
||||||
|
mMainHandler = handler;
|
||||||
mUiEventLogger = uiEventLogger;
|
mUiEventLogger = uiEventLogger;
|
||||||
mNotificationManager = notificationManager;
|
mNotificationManager = notificationManager;
|
||||||
mUserContextTracker = userContextTracker;
|
mUserContextTracker = userContextTracker;
|
||||||
@@ -138,6 +145,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
|||||||
|
|
||||||
mRecorder = new ScreenMediaRecorder(
|
mRecorder = new ScreenMediaRecorder(
|
||||||
mUserContextTracker.getUserContext(),
|
mUserContextTracker.getUserContext(),
|
||||||
|
mMainHandler,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
mAudioSource,
|
mAudioSource,
|
||||||
this
|
this
|
||||||
@@ -166,14 +174,8 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
|||||||
}
|
}
|
||||||
// Check user ID - we may be getting a stop intent after user switch, in which case
|
// Check user ID - we may be getting a stop intent after user switch, in which case
|
||||||
// we want to post the notifications for that user, which is NOT current user
|
// we want to post the notifications for that user, which is NOT current user
|
||||||
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
|
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_ID_NOT_SPECIFIED);
|
||||||
if (userId == -1) {
|
stopService(userId);
|
||||||
userId = mUserContextTracker.getUserContext().getUserId();
|
|
||||||
}
|
|
||||||
Log.d(TAG, "notifying for user " + userId);
|
|
||||||
stopRecording(userId);
|
|
||||||
mNotificationManager.cancel(NOTIFICATION_RECORDING_ID);
|
|
||||||
stopSelf();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ACTION_SHARE:
|
case ACTION_SHARE:
|
||||||
@@ -378,15 +380,39 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopRecording(int userId) {
|
private void stopService() {
|
||||||
|
stopService(USER_ID_NOT_SPECIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopService(int userId) {
|
||||||
|
if (userId == USER_ID_NOT_SPECIFIED) {
|
||||||
|
userId = mUserContextTracker.getUserContext().getUserId();
|
||||||
|
}
|
||||||
|
Log.d(TAG, "notifying for user " + userId);
|
||||||
setTapsVisible(mOriginalShowTaps);
|
setTapsVisible(mOriginalShowTaps);
|
||||||
if (getRecorder() != null) {
|
if (getRecorder() != null) {
|
||||||
|
try {
|
||||||
getRecorder().end();
|
getRecorder().end();
|
||||||
saveRecording(userId);
|
saveRecording(userId);
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
// RuntimeException could happen if the recording stopped immediately after starting
|
||||||
|
// let's release the recorder and delete all temporary files in this case
|
||||||
|
getRecorder().release();
|
||||||
|
showErrorToast(R.string.screenrecord_start_error);
|
||||||
|
Log.e(TAG, "stopRecording called, but there was an error when ending"
|
||||||
|
+ "recording");
|
||||||
|
exception.printStackTrace();
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
// Something unexpected happen, SystemUI will crash but let's delete
|
||||||
|
// the temporary files anyway
|
||||||
|
getRecorder().release();
|
||||||
|
throw new RuntimeException(throwable);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "stopRecording called, but recorder was null");
|
Log.e(TAG, "stopRecording called, but recorder was null");
|
||||||
}
|
}
|
||||||
updateState(false);
|
updateState(false);
|
||||||
|
stopSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveRecording(int userId) {
|
private void saveRecording(int userId) {
|
||||||
@@ -446,4 +472,12 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
|
|||||||
Log.d(TAG, "Media recorder info: " + what);
|
Log.d(TAG, "Media recorder info: " + what);
|
||||||
onStartCommand(getStopIntent(this), 0, 0);
|
onStartCommand(getStopIntent(this), 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopped() {
|
||||||
|
if (mController.isRecording()) {
|
||||||
|
Log.d(TAG, "Stopping recording because the system requested the stop");
|
||||||
|
stopService();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import android.media.projection.IMediaProjectionManager;
|
|||||||
import android.media.projection.MediaProjection;
|
import android.media.projection.MediaProjection;
|
||||||
import android.media.projection.MediaProjectionManager;
|
import android.media.projection.MediaProjectionManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.ServiceManager;
|
import android.os.ServiceManager;
|
||||||
@@ -51,16 +52,19 @@ import android.view.Surface;
|
|||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recording screen and mic/internal audio
|
* Recording screen and mic/internal audio
|
||||||
*/
|
*/
|
||||||
public class ScreenMediaRecorder {
|
public class ScreenMediaRecorder extends MediaProjection.Callback {
|
||||||
private static final int TOTAL_NUM_TRACKS = 1;
|
private static final int TOTAL_NUM_TRACKS = 1;
|
||||||
private static final int VIDEO_FRAME_RATE = 30;
|
private static final int VIDEO_FRAME_RATE = 30;
|
||||||
private static final int VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO = 6;
|
private static final int VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO = 6;
|
||||||
@@ -81,14 +85,16 @@ public class ScreenMediaRecorder {
|
|||||||
private ScreenRecordingMuxer mMuxer;
|
private ScreenRecordingMuxer mMuxer;
|
||||||
private ScreenInternalAudioRecorder mAudio;
|
private ScreenInternalAudioRecorder mAudio;
|
||||||
private ScreenRecordingAudioSource mAudioSource;
|
private ScreenRecordingAudioSource mAudioSource;
|
||||||
|
private final Handler mHandler;
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
MediaRecorder.OnInfoListener mListener;
|
ScreenMediaRecorderListener mListener;
|
||||||
|
|
||||||
public ScreenMediaRecorder(Context context,
|
public ScreenMediaRecorder(Context context, Handler handler,
|
||||||
int user, ScreenRecordingAudioSource audioSource,
|
int user, ScreenRecordingAudioSource audioSource,
|
||||||
MediaRecorder.OnInfoListener listener) {
|
ScreenMediaRecorderListener listener) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
mHandler = handler;
|
||||||
mUser = user;
|
mUser = user;
|
||||||
mListener = listener;
|
mListener = listener;
|
||||||
mAudioSource = audioSource;
|
mAudioSource = audioSource;
|
||||||
@@ -105,6 +111,7 @@ public class ScreenMediaRecorder {
|
|||||||
IBinder projection = proj.asBinder();
|
IBinder projection = proj.asBinder();
|
||||||
mMediaProjection = new MediaProjection(mContext,
|
mMediaProjection = new MediaProjection(mContext,
|
||||||
IMediaProjection.Stub.asInterface(projection));
|
IMediaProjection.Stub.asInterface(projection));
|
||||||
|
mMediaProjection.registerCallback(this, mHandler);
|
||||||
|
|
||||||
File cacheDir = mContext.getCacheDir();
|
File cacheDir = mContext.getCacheDir();
|
||||||
cacheDir.mkdirs();
|
cacheDir.mkdirs();
|
||||||
@@ -162,10 +169,15 @@ public class ScreenMediaRecorder {
|
|||||||
metrics.densityDpi,
|
metrics.densityDpi,
|
||||||
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
|
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
|
||||||
mInputSurface,
|
mInputSurface,
|
||||||
null,
|
new VirtualDisplay.Callback() {
|
||||||
null);
|
@Override
|
||||||
|
public void onStopped() {
|
||||||
|
onStop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mHandler);
|
||||||
|
|
||||||
mMediaRecorder.setOnInfoListener(mListener);
|
mMediaRecorder.setOnInfoListener((mr, what, extra) -> mListener.onInfo(mr, what, extra));
|
||||||
if (mAudioSource == INTERNAL ||
|
if (mAudioSource == INTERNAL ||
|
||||||
mAudioSource == MIC_AND_INTERNAL) {
|
mAudioSource == MIC_AND_INTERNAL) {
|
||||||
mTempAudioFile = File.createTempFile("temp", ".aac",
|
mTempAudioFile = File.createTempFile("temp", ".aac",
|
||||||
@@ -259,21 +271,34 @@ public class ScreenMediaRecorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* End screen recording
|
* End screen recording, throws an exception if stopping recording failed
|
||||||
*/
|
*/
|
||||||
void end() {
|
void end() throws IOException {
|
||||||
mMediaRecorder.stop();
|
Closer closer = new Closer();
|
||||||
mMediaRecorder.release();
|
|
||||||
mInputSurface.release();
|
// MediaRecorder might throw RuntimeException if stopped immediately after starting
|
||||||
mVirtualDisplay.release();
|
// We should remove the recording in this case as it will be invalid
|
||||||
mMediaProjection.stop();
|
closer.register(mMediaRecorder::stop);
|
||||||
|
closer.register(mMediaRecorder::release);
|
||||||
|
closer.register(mInputSurface::release);
|
||||||
|
closer.register(mVirtualDisplay::release);
|
||||||
|
closer.register(mMediaProjection::stop);
|
||||||
|
closer.register(this::stopInternalAudioRecording);
|
||||||
|
|
||||||
|
closer.close();
|
||||||
|
|
||||||
mMediaRecorder = null;
|
mMediaRecorder = null;
|
||||||
mMediaProjection = null;
|
mMediaProjection = null;
|
||||||
stopInternalAudioRecording();
|
|
||||||
|
|
||||||
Log.d(TAG, "end recording");
|
Log.d(TAG, "end recording");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
Log.d(TAG, "The system notified about stopping the projection");
|
||||||
|
mListener.onStopped();
|
||||||
|
}
|
||||||
|
|
||||||
private void stopInternalAudioRecording() {
|
private void stopInternalAudioRecording() {
|
||||||
if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) {
|
if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) {
|
||||||
mAudio.end();
|
mAudio.end();
|
||||||
@@ -336,6 +361,18 @@ public class ScreenMediaRecorder {
|
|||||||
return recording;
|
return recording;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release the resources without saving the data
|
||||||
|
*/
|
||||||
|
protected void release() {
|
||||||
|
if (mTempVideoFile != null) {
|
||||||
|
mTempVideoFile.delete();
|
||||||
|
}
|
||||||
|
if (mTempAudioFile != null) {
|
||||||
|
mTempAudioFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object representing the recording
|
* Object representing the recording
|
||||||
*/
|
*/
|
||||||
@@ -362,4 +399,66 @@ public class ScreenMediaRecorder {
|
|||||||
return mThumbnailBitmap;
|
return mThumbnailBitmap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ScreenMediaRecorderListener {
|
||||||
|
/**
|
||||||
|
* Called to indicate an info or a warning during recording.
|
||||||
|
* See {@link MediaRecorder.OnInfoListener} for the full description.
|
||||||
|
*/
|
||||||
|
void onInfo(MediaRecorder mr, int what, int extra);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the recording stopped by the system.
|
||||||
|
* For example, this might happen when doing partial screen sharing of an app
|
||||||
|
* and the app that is being captured is closed.
|
||||||
|
*/
|
||||||
|
void onStopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to register multiple {@link Closeable} objects and close them all by calling
|
||||||
|
* {@link Closer#close}. If there is an exception thrown during closing of one
|
||||||
|
* of the registered closeables it will continue trying closing the rest closeables.
|
||||||
|
* If there are one or more exceptions thrown they will be re-thrown at the end.
|
||||||
|
* In case of multiple exceptions only the first one will be thrown and all the rest
|
||||||
|
* will be printed.
|
||||||
|
*/
|
||||||
|
private static class Closer implements Closeable {
|
||||||
|
private final List<Closeable> mCloseables = new ArrayList<>();
|
||||||
|
|
||||||
|
void register(Closeable closeable) {
|
||||||
|
mCloseables.add(closeable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
Throwable throwable = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < mCloseables.size(); i++) {
|
||||||
|
Closeable closeable = mCloseables.get(i);
|
||||||
|
|
||||||
|
try {
|
||||||
|
closeable.close();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (throwable == null) {
|
||||||
|
throwable = e;
|
||||||
|
} else {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throwable != null) {
|
||||||
|
if (throwable instanceof IOException) {
|
||||||
|
throw (IOException) throwable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throwable instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException) throwable;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw (Error) throwable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,18 +16,21 @@
|
|||||||
|
|
||||||
package com.android.systemui.screenrecord;
|
package com.android.systemui.screenrecord;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.doNothing;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.doThrow;
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.testing.AndroidTestingRunner;
|
import android.testing.AndroidTestingRunner;
|
||||||
|
|
||||||
@@ -66,6 +69,8 @@ public class RecordingServiceTest extends SysuiTestCase {
|
|||||||
@Mock
|
@Mock
|
||||||
private Executor mExecutor;
|
private Executor mExecutor;
|
||||||
@Mock
|
@Mock
|
||||||
|
private Handler mHandler;
|
||||||
|
@Mock
|
||||||
private UserContextProvider mUserContextTracker;
|
private UserContextProvider mUserContextTracker;
|
||||||
private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() {
|
private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() {
|
||||||
public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
|
public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
|
||||||
@@ -79,8 +84,8 @@ public class RecordingServiceTest extends SysuiTestCase {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mUiEventLogger,
|
mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mHandler,
|
||||||
mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
|
mUiEventLogger, mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
|
||||||
|
|
||||||
// Return actual context info
|
// Return actual context info
|
||||||
doReturn(mContext).when(mRecordingService).getApplicationContext();
|
doReturn(mContext).when(mRecordingService).getApplicationContext();
|
||||||
@@ -143,4 +148,54 @@ public class RecordingServiceTest extends SysuiTestCase {
|
|||||||
// Then the state is set to not recording
|
// Then the state is set to not recording
|
||||||
verify(mController).updateState(false);
|
verify(mController).updateState(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnSystemRequestedStop_recordingInProgress_endsRecording() throws IOException {
|
||||||
|
doReturn(true).when(mController).isRecording();
|
||||||
|
|
||||||
|
mRecordingService.onStopped();
|
||||||
|
|
||||||
|
verify(mScreenMediaRecorder).end();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnSystemRequestedStop_recordingInProgress_updatesState() {
|
||||||
|
doReturn(true).when(mController).isRecording();
|
||||||
|
|
||||||
|
mRecordingService.onStopped();
|
||||||
|
|
||||||
|
verify(mController).updateState(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnSystemRequestedStop_recordingIsNotInProgress_doesNotEndRecording()
|
||||||
|
throws IOException {
|
||||||
|
doReturn(false).when(mController).isRecording();
|
||||||
|
|
||||||
|
mRecordingService.onStopped();
|
||||||
|
|
||||||
|
verify(mScreenMediaRecorder, never()).end();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_releasesRecording()
|
||||||
|
throws IOException {
|
||||||
|
doReturn(true).when(mController).isRecording();
|
||||||
|
doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
|
||||||
|
|
||||||
|
mRecordingService.onStopped();
|
||||||
|
|
||||||
|
verify(mScreenMediaRecorder).release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnSystemRequestedStop_recorderEndThrowsOOMError_releasesRecording()
|
||||||
|
throws IOException {
|
||||||
|
doReturn(true).when(mController).isRecording();
|
||||||
|
doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end();
|
||||||
|
|
||||||
|
assertThrows(Throwable.class, () -> mRecordingService.onStopped());
|
||||||
|
|
||||||
|
verify(mScreenMediaRecorder).release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user