Adding unit tests

Tests that the screen record tile on/off/cancel works as expected and that the
recording controller updates state and calls listeners correctly

Also updated a couple methods so they make more sense (aren't called
multiple times per event, differentiating between onRecordingEnd and
onCountdownEnd)

Test: atest ScreenRecordTileTest RecordingControllerTest
Bug: 144944929
Change-Id: Ic2d44bbc250bcb1e90eff78839c9ab37c0d93564
This commit is contained in:
Beth Thibodeau
2020-02-18 20:55:38 -05:00
parent cb7f6ffe03
commit 58772783bf
6 changed files with 290 additions and 15 deletions

View File

@@ -108,7 +108,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
Log.d(TAG, "Starting countdown");
// Close QS, otherwise the permission dialog appears beneath it
getHost().collapsePanels();
mController.launchRecordPrompt(this);
mController.launchRecordPrompt();
}
private void cancelCountdown() {
@@ -128,6 +128,11 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
refreshState();
}
@Override
public void onCountdownEnd() {
refreshState();
}
@Override
public void onRecordingStart() {
refreshState();

View File

@@ -23,7 +23,6 @@ import android.content.Intent;
import android.os.CountDownTimer;
import android.util.Log;
import com.android.systemui.qs.tiles.ScreenRecordTile;
import com.android.systemui.statusbar.policy.CallbackController;
import java.util.ArrayList;
@@ -62,7 +61,7 @@ public class RecordingController
/**
* Show dialog of screen recording options to user.
*/
public void launchRecordPrompt(ScreenRecordTile tileToUpdate) {
public void launchRecordPrompt() {
final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
SYSUI_SCREENRECORD_LAUNCHER);
final Intent intent = new Intent();
@@ -73,15 +72,17 @@ public class RecordingController
/**
* Start counting down in preparation to start a recording
* @param ms Time in ms to count down
* @param ms Total time in ms to wait before starting
* @param interval Time in ms per countdown step
* @param startIntent Intent to start a recording
* @param stopIntent Intent to stop a recording
*/
public void startCountdown(long ms, PendingIntent startIntent, PendingIntent stopIntent) {
public void startCountdown(long ms, long interval, PendingIntent startIntent,
PendingIntent stopIntent) {
mIsStarting = true;
mStopIntent = stopIntent;
mCountDownTimer = new CountDownTimer(ms, 1000) {
mCountDownTimer = new CountDownTimer(ms, interval) {
@Override
public void onTick(long millisUntilFinished) {
for (RecordingStateChangeCallback cb : mListeners) {
@@ -94,7 +95,7 @@ public class RecordingController
mIsStarting = false;
mIsRecording = true;
for (RecordingStateChangeCallback cb : mListeners) {
cb.onRecordingEnd();
cb.onCountdownEnd();
}
try {
startIntent.send();
@@ -120,7 +121,7 @@ public class RecordingController
mIsStarting = false;
for (RecordingStateChangeCallback cb : mListeners) {
cb.onRecordingEnd();
cb.onCountdownEnd();
}
}
@@ -144,16 +145,12 @@ public class RecordingController
* Stop the recording
*/
public void stopRecording() {
updateState(false);
try {
mStopIntent.send();
updateState(false);
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Error stopping: " + e.getMessage());
}
for (RecordingStateChangeCallback cb : mListeners) {
cb.onRecordingEnd();
}
}
/**
@@ -192,6 +189,12 @@ public class RecordingController
*/
default void onCountdown(long millisUntilFinished) {}
/**
* Called when a countdown to recording has ended. This is a separate method so that if
* needed, listeners can handle cases where recording fails to start
*/
default void onCountdownEnd() {}
/**
* Called when a screen recording has started
*/

View File

@@ -34,6 +34,7 @@ import javax.inject.Inject;
*/
public class ScreenRecordDialog extends Activity {
private static final long DELAY_MS = 3000;
private static final long INTERVAL_MS = 1000;
private final RecordingController mController;
private Switch mAudioSwitch;
@@ -83,6 +84,6 @@ public class ScreenRecordDialog extends Activity {
RecordingService.REQUEST_CODE,
RecordingService.getStopIntent(this),
PendingIntent.FLAG_UPDATE_CURRENT);
mController.startCountdown(DELAY_MS, startIntent, stopIntent);
mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent);
}
}

View File

@@ -677,6 +677,12 @@ public class PhoneStatusBarPolicy
mIconController.setIconVisibility(mSlotScreenRecord, true);
}
@Override
public void onCountdownEnd() {
if (DEBUG) Log.d(TAG, "screenrecord: hiding icon during countdown");
mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
}
@Override
public void onRecordingStart() {
if (DEBUG) Log.d(TAG, "screenrecord: showing icon");
@@ -687,7 +693,7 @@ public class PhoneStatusBarPolicy
@Override
public void onRecordingEnd() {
// Ensure this is on the main thread, since it could be called during countdown
// Ensure this is on the main thread
if (DEBUG) Log.d(TAG, "screenrecord: hiding icon");
mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
}

View File

@@ -0,0 +1,136 @@
/*
* 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.qs.tiles;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.screenrecord.RecordingController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class ScreenRecordTileTest extends SysuiTestCase {
@Mock
private RecordingController mController;
@Mock
private QSTileHost mHost;
private TestableLooper mTestableLooper;
private ScreenRecordTile mTile;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
mController = mDependency.injectMockDependency(RecordingController.class);
when(mHost.getContext()).thenReturn(mContext);
mTile = new ScreenRecordTile(mHost, mController);
}
// Test that the tile is inactive and labeled correctly when the controller is neither starting
// or recording, and that clicking on the tile in this state brings up the record prompt
@Test
public void testNotActive() {
when(mController.isStarting()).thenReturn(false);
when(mController.isRecording()).thenReturn(false);
mTile.refreshState();
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_INACTIVE, mTile.getState().state);
assertTrue(mTile.getState().secondaryLabel.toString().equals(
mContext.getString(R.string.quick_settings_screen_record_start)));
mTile.handleClick();
verify(mController, times(1)).launchRecordPrompt();
}
// Test that the tile is active and labeled correctly when the controller is starting
@Test
public void testIsStarting() {
when(mController.isStarting()).thenReturn(true);
when(mController.isRecording()).thenReturn(false);
mTile.refreshState();
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_ACTIVE, mTile.getState().state);
assertTrue(mTile.getState().secondaryLabel.toString().endsWith("..."));
}
// Test that the tile cancels countdown if it is clicked when the controller is starting
@Test
public void testCancelRecording() {
when(mController.isStarting()).thenReturn(true);
when(mController.isRecording()).thenReturn(false);
mTile.handleClick();
verify(mController, times(1)).cancelCountdown();
}
// Test that the tile is active and labeled correctly when the controller is recording
@Test
public void testIsRecording() {
when(mController.isStarting()).thenReturn(false);
when(mController.isRecording()).thenReturn(true);
mTile.refreshState();
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_ACTIVE, mTile.getState().state);
assertTrue(mTile.getState().secondaryLabel.toString().equals(
mContext.getString(R.string.quick_settings_screen_record_stop)));
}
// Test that the tile stops the recording if it is clicked when the controller is recording
@Test
public void testStopRecording() {
when(mController.isStarting()).thenReturn(false);
when(mController.isRecording()).thenReturn(true);
mTile.handleClick();
verify(mController, times(1)).stopRecording();
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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 junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import android.app.PendingIntent;
import android.os.Looper;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
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;
@SmallTest
@RunWith(AndroidTestingRunner.class)
/**
* Tests for exception handling and bitmap configuration in adding smart actions to Screenshot
* Notification.
*/
public class RecordingControllerTest extends SysuiTestCase {
@Mock
RecordingController.RecordingStateChangeCallback mCallback;
RecordingController mController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mController = new RecordingController(mContext);
mController.addCallback(mCallback);
}
// Test that when a countdown in progress is cancelled, the controller goes from starting to not
// starting, and notifies listeners.
@Test
public void testCancelCountdown() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
mController.startCountdown(100, 10, null, null);
assertTrue(mController.isStarting());
assertFalse(mController.isRecording());
mController.cancelCountdown();
assertFalse(mController.isStarting());
assertFalse(mController.isRecording());
verify(mCallback).onCountdownEnd();
}
// Test that when recording is started, the start intent is sent and listeners are notified.
@Test
public void testStartRecording() throws PendingIntent.CanceledException {
if (Looper.myLooper() == null) {
Looper.prepare();
}
PendingIntent startIntent = Mockito.mock(PendingIntent.class);
mController.startCountdown(0, 0, startIntent, null);
verify(mCallback).onCountdownEnd();
verify(startIntent).send();
}
// Test that when recording is stopped, the stop intent is sent and listeners are notified.
@Test
public void testStopRecording() throws PendingIntent.CanceledException {
if (Looper.myLooper() == null) {
Looper.prepare();
}
PendingIntent startIntent = Mockito.mock(PendingIntent.class);
PendingIntent stopIntent = Mockito.mock(PendingIntent.class);
mController.startCountdown(0, 0, startIntent, stopIntent);
mController.stopRecording();
assertFalse(mController.isStarting());
assertFalse(mController.isRecording());
verify(stopIntent).send();
verify(mCallback).onRecordingEnd();
}
// Test that updating the controller state works and notifies listeners.
@Test
public void testUpdateState() {
mController.updateState(true);
assertTrue(mController.isRecording());
verify(mCallback).onRecordingStart();
mController.updateState(false);
assertFalse(mController.isRecording());
verify(mCallback).onRecordingEnd();
}
}