Add a status checker for activity on top

Check that activity on top is the home launcher and not
some left over activity from previous tests.

Test: unit tests
Bug: 64713262
Change-Id: Iafd34881370a3f5104439598c89f466d22351bd1
diff --git a/src/com/android/tradefed/suite/checker/ActivityStatusChecker.java b/src/com/android/tradefed/suite/checker/ActivityStatusChecker.java
new file mode 100644
index 0000000..4a66304
--- /dev/null
+++ b/src/com/android/tradefed/suite/checker/ActivityStatusChecker.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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.tradefed.suite.checker;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.ITestLogger;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestLoggerReceiver;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.util.StreamUtil;
+
+/** Status checker for left over activities running at the end of a module. */
+public class ActivityStatusChecker implements ISystemStatusChecker, ITestLoggerReceiver {
+
+    private ITestLogger mLogger;
+
+    @Override
+    public boolean postExecutionCheck(ITestDevice device) throws DeviceNotAvailableException {
+        return isFrontActivityLauncher(device);
+    }
+
+    private boolean isFrontActivityLauncher(ITestDevice device) throws DeviceNotAvailableException {
+        String output =
+                device.executeShellCommand(
+                        "dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp'");
+        CLog.d("dumpsys window windows: %s", output);
+        if (output.contains("Launcher")) {
+            return true;
+        } else {
+            InputStreamSource screen = device.getScreenshot("JPEG");
+            try {
+                mLogger.testLog("status_checker_front_activity", LogDataType.JPEG, screen);
+            } finally {
+                StreamUtil.cancel(screen);
+            }
+            // TODO: Add a step to return to home page, or refresh the device (reboot?)
+            return false;
+        }
+    }
+
+    @Override
+    public void setTestLogger(ITestLogger testLogger) {
+        mLogger = testLogger;
+    }
+}
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index 7949afb..45db8cf 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -26,6 +26,7 @@
 import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.ITestLoggerReceiver;
 import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.suite.checker.ISystemStatusChecker;
@@ -193,6 +194,13 @@
             return;
         }
 
+        // Allow checkers to log files for easier debbuging.
+        for (ISystemStatusChecker checker : mSystemStatusCheckers) {
+            if (checker instanceof ITestLoggerReceiver) {
+                ((ITestLoggerReceiver) checker).setTestLogger(listener);
+            }
+        }
+
         /** Setup a special listener to take actions on test failures. */
         TestFailureListener failureListener =
                 new TestFailureListener(
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 7c52eb3..4d88011 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -99,6 +99,7 @@
 import com.android.tradefed.sandbox.SandboxConfigDumpTest;
 import com.android.tradefed.sandbox.SandboxConfigUtilTest;
 import com.android.tradefed.sandbox.TradefedSandboxTest;
+import com.android.tradefed.suite.checker.ActivityStatusCheckerTest;
 import com.android.tradefed.suite.checker.KeyguardStatusCheckerTest;
 import com.android.tradefed.suite.checker.SystemServerFileDescriptorCheckerTest;
 import com.android.tradefed.suite.checker.SystemServerStatusCheckerTest;
@@ -371,6 +372,7 @@
     TradefedSandboxTest.class,
 
     // suite/checker
+    ActivityStatusCheckerTest.class,
     KeyguardStatusCheckerTest.class,
     SystemServerFileDescriptorCheckerTest.class,
     SystemServerStatusCheckerTest.class,
diff --git a/tests/src/com/android/tradefed/suite/checker/ActivityStatusCheckerTest.java b/tests/src/com/android/tradefed/suite/checker/ActivityStatusCheckerTest.java
new file mode 100644
index 0000000..cf7a085
--- /dev/null
+++ b/tests/src/com/android/tradefed/suite/checker/ActivityStatusCheckerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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.tradefed.suite.checker;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.ITestLogger;
+import com.android.tradefed.result.ByteArrayInputStreamSource;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.LogDataType;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ActivityStatusChecker}. */
+@RunWith(JUnit4.class)
+public class ActivityStatusCheckerTest {
+    private ActivityStatusChecker mChecker;
+    private ITestLogger mMockLogger;
+    private ITestDevice mMockDevice;
+
+    @Before
+    public void setUp() {
+        mMockLogger = EasyMock.createStrictMock(ITestLogger.class);
+        mChecker = new ActivityStatusChecker();
+        mChecker.setTestLogger(mMockLogger);
+        mMockDevice = EasyMock.createStrictMock(ITestDevice.class);
+    }
+
+    /** Test that the status checker is a success if the home/launcher activity is on top. */
+    @Test
+    public void testCheckerLauncherHomeScreen() throws Exception {
+        EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.anyObject()))
+                .andReturn(
+                        "  mCurrentFocus=Window{46dd15 u0 com.google.android.apps.nexuslauncher/"
+                                + "com.google.android.apps.nexuslauncher.NexusLauncherActivity}\n"
+                                + "  mFocusedApp=AppWindowToken{37e3c39 token=Token{312ce85 ActivityRecord{a9437fc "
+                                + "u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t2}}}");
+        EasyMock.replay(mMockLogger, mMockDevice);
+        assertTrue(mChecker.postExecutionCheck(mMockDevice));
+        EasyMock.verify(mMockLogger, mMockDevice);
+    }
+
+    /** Test that if another activity is on top, then we fail the checker and take a screenshot. */
+    @Test
+    public void testCheckerOtherActivity() throws Exception {
+        EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.anyObject()))
+                .andReturn(
+                        "mCurrentFocus=Window{52b89df u0 com.android.chrome/org.chromium.chrome."
+                                + "browser.ChromeTabbedActivity}\n"
+                                + "  mFocusedApp=AppWindowToken{955b485 token=Token{6bebd1b ActivityRecord{fd30b2a "
+                                + "u0 com.android.chrome/org.chromium.chrome.browser.ChromeTabbedActivity t7}}}");
+        InputStreamSource fake = new ByteArrayInputStreamSource("fakedata".getBytes());
+        EasyMock.expect(mMockDevice.getScreenshot(EasyMock.anyObject())).andReturn(fake);
+        mMockLogger.testLog("status_checker_front_activity", LogDataType.JPEG, fake);
+        EasyMock.replay(mMockLogger, mMockDevice);
+        assertFalse(mChecker.postExecutionCheck(mMockDevice));
+        EasyMock.verify(mMockLogger, mMockDevice);
+    }
+}
diff --git a/tests/src/com/android/tradefed/targetprep/InstallAllTestZipAppsSetupTest.java b/tests/src/com/android/tradefed/targetprep/InstallAllTestZipAppsSetupTest.java
index daf3174..b1a50b2 100644
--- a/tests/src/com/android/tradefed/targetprep/InstallAllTestZipAppsSetupTest.java
+++ b/tests/src/com/android/tradefed/targetprep/InstallAllTestZipAppsSetupTest.java
@@ -96,7 +96,7 @@
     }
 
     @Test
-    public void testGetZipFile() throws DeviceNotAvailableException, TargetSetupError {
+    public void testGetZipFile() throws TargetSetupError {
         String zip = "zip";
         mPrep.setTestZipName(zip);
         File file = new File(zip);
@@ -108,7 +108,7 @@
     }
 
     @Test
-    public void testGetZipFileDoesntExist() throws DeviceNotAvailableException, TargetSetupError {
+    public void testGetZipFileDoesntExist() throws TargetSetupError {
         String zip = "zip";
         mPrep.setTestZipName(zip);
         EasyMock.expect(mMockBuildInfo.getFile(zip)).andReturn(null);
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index f16714a..9eaadf3 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -257,6 +257,9 @@
      */
     @Test
     public void testRun_rebootBeforeModule() throws Exception {
+        List<ISystemStatusChecker> sysChecker = new ArrayList<ISystemStatusChecker>();
+        sysChecker.add(mMockSysChecker);
+        mTestSuite.setSystemStatusChecker(sysChecker);
         OptionSetter setter = new OptionSetter(mTestSuite);
         setter.setOptionValue("skip-all-system-status-check", "true");
         setter.setOptionValue("reboot-per-module", "true");
@@ -275,6 +278,8 @@
      */
     @Test
     public void testRun_unresponsiveDevice() throws Exception {
+        List<ISystemStatusChecker> sysChecker = new ArrayList<ISystemStatusChecker>();
+        sysChecker.add(mMockSysChecker);
         mTestSuite =
                 new TestSuiteImpl() {
                     @Override
@@ -297,6 +302,7 @@
         mTestSuite.setDevice(mMockDevice);
         mTestSuite.setBuild(mMockBuildInfo);
         mTestSuite.setInvocationContext(mContext);
+        mTestSuite.setSystemStatusChecker(sysChecker);
         OptionSetter setter = new OptionSetter(mTestSuite);
         setter.setOptionValue("skip-all-system-status-check", "true");
         setter.setOptionValue("reboot-per-module", "true");
@@ -317,6 +323,8 @@
      */
     @Test
     public void testRun_runtimeException() throws Exception {
+        List<ISystemStatusChecker> sysChecker = new ArrayList<ISystemStatusChecker>();
+        sysChecker.add(mMockSysChecker);
         mTestSuite =
                 new TestSuiteImpl() {
                     @Override
@@ -336,6 +344,7 @@
                         return testConfig;
                     }
                 };
+        mTestSuite.setSystemStatusChecker(sysChecker);
         mTestSuite.setDevice(mMockDevice);
         mTestSuite.setBuild(mMockBuildInfo);
         mTestSuite.setInvocationContext(mContext);