Merge "Adding unit tests"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 84891ec..6663237 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -108,7 +108,7 @@
         Log.d(TAG, "Starting countdown");
         // Close QS, otherwise the permission dialog appears beneath it
         getHost().collapsePanels();
-        mController.launchRecordPrompt(this);
+        mController.launchRecordPrompt();
     }
 
     private void cancelCountdown() {
@@ -129,6 +129,11 @@
         }
 
         @Override
+        public void onCountdownEnd() {
+            refreshState();
+        }
+
+        @Override
         public void onRecordingStart() {
             refreshState();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 6ad9c40..8dad08e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -23,7 +23,6 @@
 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 @@
     /**
      * 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 @@
 
     /**
      * 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 @@
                 mIsStarting = false;
                 mIsRecording = true;
                 for (RecordingStateChangeCallback cb : mListeners) {
-                    cb.onRecordingEnd();
+                    cb.onCountdownEnd();
                 }
                 try {
                     startIntent.send();
@@ -120,7 +121,7 @@
         mIsStarting = false;
 
         for (RecordingStateChangeCallback cb : mListeners) {
-            cb.onRecordingEnd();
+            cb.onCountdownEnd();
         }
     }
 
@@ -144,16 +145,12 @@
      * 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();
-        }
     }
 
     /**
@@ -193,6 +190,12 @@
         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
          */
         default void onRecordingStart() {}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index 566f12b..26973d0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -34,6 +34,7 @@
  */
 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 @@
                 RecordingService.REQUEST_CODE,
                 RecordingService.getStopIntent(this),
                 PendingIntent.FLAG_UPDATE_CURRENT);
-        mController.startCountdown(DELAY_MS, startIntent, stopIntent);
+        mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 260f94c..1ab36c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -678,6 +678,12 @@
     }
 
     @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");
         mIconController.setIcon(mSlotScreenRecord,
@@ -687,7 +693,7 @@
 
     @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));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
new file mode 100644
index 0000000..e8e98b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -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();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
new file mode 100644
index 0000000..b877c7f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -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();
+    }
+}