Merge "Crystalball : Collect AppTransitionMetrics and support for hot launch."
diff --git a/libraries/collectors-helper/statsd/src/com/android/helpers/AppStartupHelper.java b/libraries/collectors-helper/statsd/src/com/android/helpers/AppStartupHelper.java
index b2f027b..5d1be27 100644
--- a/libraries/collectors-helper/statsd/src/com/android/helpers/AppStartupHelper.java
+++ b/libraries/collectors-helper/statsd/src/com/android/helpers/AppStartupHelper.java
@@ -54,6 +54,7 @@
 
     private static final String PROCESS_START = "process_start";
     private static final String PROCESS_START_DELAY = "process_start_delay";
+    private static final String TRANSITION_DELAY_MILLIS = "transition_delay_millis";
     private boolean isProcStartDetailsDisabled;
 
     private StatsdHelper mStatsdHelper = new StatsdHelper();
@@ -87,11 +88,13 @@
                 String pkgName = appStartAtom.getPkgName();
                 String transitionType = appStartAtom.getType().toString();
                 int windowsDrawnMillis = appStartAtom.getWindowsDrawnDelayMillis();
+                int transitionDelayMillis = appStartAtom.getTransitionDelayMillis();
                 Log.i(LOG_TAG, String.format("Pkg Name: %s, Transition Type: %s, "
-                        + "WindowDrawnDelayMillis: %s",
-                        pkgName, transitionType, windowsDrawnMillis));
+                        + "WindowDrawnDelayMillis: %s, TransitionDelayMillis: %s",
+                        pkgName, transitionType, windowsDrawnMillis, transitionDelayMillis));
 
-                String metricKey = "";
+                String metricTypeKey = "";
+                String metricTransitionKey = "";
                 // To track number of startups per type per package.
                 String metricCountKey = "";
                 // To track total number of startups per type.
@@ -111,13 +114,20 @@
                         break;
                 }
                 if (!typeKey.isEmpty()) {
-                    metricKey = MetricUtility.constructKey(typeKey, pkgName);
+                    metricTypeKey = MetricUtility.constructKey(typeKey, pkgName);
                     metricCountKey = MetricUtility.constructKey(typeKey, COUNT, pkgName);
                     totalCountKey = MetricUtility.constructKey(typeKey, TOTAL_COUNT);
-                    // Update the metrics.
-                    MetricUtility.addMetric(metricKey, windowsDrawnMillis, appStartResultMap);
+
+                    // Update the windows drawn delay metrics.
+                    MetricUtility.addMetric(metricTypeKey, windowsDrawnMillis, appStartResultMap);
                     MetricUtility.addMetric(metricCountKey, appStartCountMap);
                     MetricUtility.addMetric(totalCountKey, appStartCountMap);
+
+                    // Update the transition delay metrics.
+                    metricTransitionKey = MetricUtility.constructKey(typeKey,
+                            TRANSITION_DELAY_MILLIS, pkgName);
+                    MetricUtility.addMetric(metricTransitionKey, transitionDelayMillis,
+                            appStartResultMap);
                 }
             }
             if (atom.hasAppStartFullyDrawn()) {
@@ -188,7 +198,7 @@
                     totalCountKey = MetricUtility.constructKey(typeKey, PROCESS_START,
                             TOTAL_COUNT);
                     // Update the metrics
-                    if(isProcStartDetailsDisabled) {
+                    if (isProcStartDetailsDisabled) {
                         MetricUtility.addMetric(metricCountKey, tempResultCountMap);
                     } else {
                         MetricUtility.addMetric(metricKey, processStartDelayMillis,
diff --git a/libraries/collectors-helper/statsd/test/src/com/android/helpers/AppStartupHelperTest.java b/libraries/collectors-helper/statsd/test/src/com/android/helpers/AppStartupHelperTest.java
index 6565fca..824f371 100644
--- a/libraries/collectors-helper/statsd/test/src/com/android/helpers/AppStartupHelperTest.java
+++ b/libraries/collectors-helper/statsd/test/src/com/android/helpers/AppStartupHelperTest.java
@@ -53,6 +53,13 @@
     private static final String COLD_LAUNCH_KEY_TEMPLATE = "cold_startup_%s";
     private static final String COLD_LAUNCH_PROCESSS_FG_KEY_TEMPLATE =
             "cold_startup_process_start_delay_%s_fg";
+    private static final String COLD_LAUNCH_TRANSITION_DELAY_MILLIS_KEY_TEMPLATE =
+            "cold_startup_transition_delay_millis_%s";
+    private static final String WARM_LAUNCH_TRANSITION_DELAY_MILLIS_KEY_TEMPLATE =
+            "warm_startup_transition_delay_millis_%s";
+    private static final String HOT_LAUNCH_TRANSITION_DELAY_MILLIS_KEY_TEMPLATE =
+            "hot_startup_transition_delay_millis_%s";
+
     private static final String COLD_LAUNCH_COUNT_PKG_KEY_TEMPLATE = "cold_startup_count_%s";
     private static final String COLD_LAUNCH_PROCESS_COUNT_FG_KEY_TEMPLATE =
             "cold_startup_process_start_count_%s_fg";
@@ -118,6 +125,14 @@
         assertEquals(1, Integer.parseInt(appLaunchMetrics.get(COLD_LAUNCH_TOTAL_COUNT_KEY_TEMPLATE)
                 .toString()));
 
+        // Verify transition metrics.
+        String coldLaunchTransitionMetricKey = String.format(
+                COLD_LAUNCH_TRANSITION_DELAY_MILLIS_KEY_TEMPLATE,
+                CALENDAR_PKG_NAME);
+        assertTrue(appLaunchMetrics.keySet().contains(coldLaunchTransitionMetricKey));
+        assertEquals(1,
+                appLaunchMetrics.get(coldLaunchTransitionMetricKey).toString().split(",").length);
+
         // Verify process start values.
         String coldLaunchProcessMetricKey = String.format(COLD_LAUNCH_PROCESSS_FG_KEY_TEMPLATE,
                 CALENDAR_PKG_NAME);
@@ -223,6 +238,20 @@
         assertEquals(2, Integer.parseInt(appLaunchMetrics.get(COLD_LAUNCH_TOTAL_COUNT_KEY_TEMPLATE)
                 .toString()));
 
+        // Verify transition metrics.
+        String coldLaunchTransCalMetricKey = String.format(
+                COLD_LAUNCH_TRANSITION_DELAY_MILLIS_KEY_TEMPLATE,
+                CALENDAR_PKG_NAME);
+        String coldLaunchTransSetMetricKey = String.format(
+                COLD_LAUNCH_TRANSITION_DELAY_MILLIS_KEY_TEMPLATE,
+                SETTINGS_PKG_NAME);
+        assertTrue(appLaunchMetrics.keySet().contains(coldLaunchTransCalMetricKey));
+        assertTrue(appLaunchMetrics.keySet().contains(coldLaunchTransSetMetricKey));
+        assertEquals(1,
+                appLaunchMetrics.get(coldLaunchTransCalMetricKey).toString().split(",").length);
+        assertEquals(1,
+                appLaunchMetrics.get(coldLaunchTransSetMetricKey).toString().split(",").length);
+
         // Verify process start values.
         String coldLaunchProcessMetricKey = String.format(COLD_LAUNCH_PROCESSS_FG_KEY_TEMPLATE,
                 CALENDAR_PKG_NAME);
@@ -277,6 +306,15 @@
         assertTrue(appLaunchMetrics.keySet().contains(calendarWarmLaunchKey));
         assertEquals(1, appLaunchMetrics.get(calendarWarmLaunchKey).toString().split(",").length);
         assertTrue(mAppStartupHelper.stopCollecting());
+
+        // Verify transition metrics.
+        String warmLaunchTransitionMetricKey = String.format(
+                WARM_LAUNCH_TRANSITION_DELAY_MILLIS_KEY_TEMPLATE,
+                CALENDAR_PKG_NAME);
+        assertTrue(appLaunchMetrics.keySet().contains(warmLaunchTransitionMetricKey));
+        assertEquals(1,
+                appLaunchMetrics.get(warmLaunchTransitionMetricKey).toString().split(",").length);
+
         mHelper.get().exit();
     }
 
@@ -303,6 +341,15 @@
         SystemClock.sleep(HelperTestUtility.ACTION_DELAY);
         HelperTestUtility.clearApp(String.format(KILL_TEST_APP_CMD_TEMPLATE, SETTINGS_PKG_NAME));
         SystemClock.sleep(HelperTestUtility.ACTION_DELAY);
+
+        // Verify transition metrics.
+        String hotLaunchTransitionMetricKey = String.format(
+                HOT_LAUNCH_TRANSITION_DELAY_MILLIS_KEY_TEMPLATE,
+                SETTINGS_PKG_NAME);
+        assertTrue(appLaunchMetrics.keySet().contains(hotLaunchTransitionMetricKey));
+        assertEquals(1,
+                appLaunchMetrics.get(hotLaunchTransitionMetricKey).toString().split(",").length);
+
     }
 
     /**
diff --git a/libraries/rule/src/android/platform/test/rule/KillAppsRule.java b/libraries/rule/src/android/platform/test/rule/KillAppsRule.java
index d14ca17..6217836 100644
--- a/libraries/rule/src/android/platform/test/rule/KillAppsRule.java
+++ b/libraries/rule/src/android/platform/test/rule/KillAppsRule.java
@@ -15,6 +15,7 @@
  */
 package android.platform.test.rule;
 
+import androidx.annotation.VisibleForTesting;
 import org.junit.runner.Description;
 import org.junit.runners.model.InitializationError;
 
@@ -24,6 +25,10 @@
 public class KillAppsRule extends TestWatcher {
     private final String[] mApplications;
 
+    @VisibleForTesting
+    static final String KILL_APP = "kill-app";
+    private static boolean mKillApp;
+
     public KillAppsRule() throws InitializationError {
         throw new InitializationError("Must supply an application to kill.");
     }
@@ -34,9 +39,16 @@
 
     @Override
     protected void starting(Description description) {
-        // Force stop each application in sequence.
+        // Check if killing the app after launch is selected or not.
+        mKillApp = Boolean.parseBoolean(getArguments().getString(KILL_APP, "true"));
+        if (!mKillApp) {
+            return;
+        }
+
+        // Force stop each application in sequence if the kill app option is selected.
         for (String app : mApplications) {
             executeShellCommand(String.format("am force-stop %s", app));
         }
+
     }
 }
diff --git a/libraries/rule/src/android/platform/test/rule/PressHomeRule.java b/libraries/rule/src/android/platform/test/rule/PressHomeRule.java
new file mode 100644
index 0000000..61a0cd1
--- /dev/null
+++ b/libraries/rule/src/android/platform/test/rule/PressHomeRule.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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 android.platform.test.rule;
+
+import android.os.SystemClock;
+
+import org.junit.runner.Description;
+
+/**
+ * This rule will navigate to home at the end of each test method.
+ * TODO: Revisit this after b/132100677 is fixed.
+ */
+public class PressHomeRule extends TestWatcher {
+
+    static final String GO_HOME = "press-home";
+    private static boolean mGoHome;
+
+    @Override
+    protected void finished(Description description) {
+        // Navigate to home after the test method is executed.
+        mGoHome = Boolean.parseBoolean(getArguments().getString(GO_HOME, "true"));
+        if (!mGoHome) {
+            return;
+        }
+
+        getUiDevice().pressHome();
+        // Sleep for statsd to update the metrics.
+        SystemClock.sleep(3000);
+
+    }
+}
diff --git a/libraries/rule/tests/src/android/platform/test/rule/KillAppsRuleTest.java b/libraries/rule/tests/src/android/platform/test/rule/KillAppsRuleTest.java
index 2f82a05..89240b1 100644
--- a/libraries/rule/tests/src/android/platform/test/rule/KillAppsRuleTest.java
+++ b/libraries/rule/tests/src/android/platform/test/rule/KillAppsRuleTest.java
@@ -18,6 +18,8 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.fail;
 
+import android.os.Bundle;
+
 import org.junit.Test;
 import org.junit.runner.Description;
 import org.junit.runner.RunWith;
@@ -51,12 +53,12 @@
      */
     @Test
     public void testOneAppToKill() throws Throwable {
-        TestableKillAppsRule rule = new TestableKillAppsRule("example.package.name");
+        TestableKillAppsRule rule = new TestableKillAppsRule(new Bundle(), "example.package.name");
         rule.apply(rule.getTestStatement(), Description.createTestDescription("clzz", "mthd"))
-            .evaluate();
+                .evaluate();
         assertThat(rule.getOperations()).containsExactly(
                 "am force-stop example.package.name", "test")
-            .inOrder();
+                .inOrder();
     }
 
     /**
@@ -64,29 +66,48 @@
      */
     @Test
     public void testMultipleAppsToKill() throws Throwable {
-        TestableKillAppsRule rule = new TestableKillAppsRule(
+        TestableKillAppsRule rule = new TestableKillAppsRule(new Bundle(),
                 "package.name1",
                 "package.name2",
                 "package.name3");
         rule.apply(rule.getTestStatement(), Description.createTestDescription("clzz", "mthd"))
-            .evaluate();
+                .evaluate();
         assertThat(rule.getOperations()).containsExactly(
                 "am force-stop package.name1",
                 "am force-stop package.name2",
                 "am force-stop package.name3",
                 "test")
-            .inOrder();
+                .inOrder();
+    }
+
+    /**
+     * Tests apps are not killed if kill-app flag is set to false.
+     */
+    @Test
+    public void testDisableKillsAppsRuleOption() throws Throwable {
+        Bundle noKillAppsBundle = new Bundle();
+        noKillAppsBundle.putString(KillAppsRule.KILL_APP, "false");
+        TestableKillAppsRule rule = new TestableKillAppsRule(noKillAppsBundle,
+                "example.package.name");
+
+        rule.apply(rule.getTestStatement(), Description.createTestDescription("clzz", "mthd"))
+                .evaluate();
+        assertThat(rule.getOperations()).containsExactly("test")
+                .inOrder();
     }
 
     private static class TestableKillAppsRule extends KillAppsRule {
         private List<String> mOperations = new ArrayList<>();
+        private Bundle mBundle;
 
-        public TestableKillAppsRule(String app) {
+        public TestableKillAppsRule(Bundle bundle, String app) {
             super(app);
+            mBundle = bundle;
         }
 
-        public TestableKillAppsRule(String... apps) {
+        public TestableKillAppsRule(Bundle bundle, String... apps) {
             super(apps);
+            mBundle = bundle;
         }
 
         @Override
@@ -95,6 +116,11 @@
             return "";
         }
 
+        @Override
+        protected Bundle getArguments() {
+            return mBundle;
+        }
+
         public List<String> getOperations() {
             return mOperations;
         }
diff --git a/libraries/rule/tests/src/android/platform/test/rule/PressHomeRuleTest.java b/libraries/rule/tests/src/android/platform/test/rule/PressHomeRuleTest.java
new file mode 100644
index 0000000..3e836fa
--- /dev/null
+++ b/libraries/rule/tests/src/android/platform/test/rule/PressHomeRuleTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 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 android.platform.test.rule;
+
+import android.os.Bundle;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.support.test.uiautomator.UiDevice;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.model.Statement;
+
+import org.mockito.Mockito;
+
+/**
+ * Unit test the logic for {@link PressHomeRule}
+ */
+@RunWith(JUnit4.class)
+public class PressHomeRuleTest {
+    /**
+     * Tests that press home happens at the end of the test method.
+     */
+    @Test
+    public void testPressHomeEnabled() throws Throwable {
+
+        Bundle pressHomeBundle = new Bundle();
+        pressHomeBundle.putString(PressHomeRule.GO_HOME, "true");
+        TestablePressHomeRule rule = new TestablePressHomeRule(pressHomeBundle);
+
+        Statement testStatement = new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                // Assert that navigate to press home is not initiated.
+                verify(rule.getUiDevice(), never()).pressHome();
+            }
+        };
+        rule.apply(testStatement, Description.createTestDescription("clzz", "mthd")).evaluate();
+        // Assert that the home button is pressed after the test method.
+        verify(rule.getUiDevice(), times(1)).pressHome();
+    }
+
+    /**
+     * Tests that did not press home if the option is disabled
+     */
+    @Test
+    public void testPressHomeDisabled() throws Throwable {
+
+        Bundle pressHomeBundle = new Bundle();
+        pressHomeBundle.putString(PressHomeRule.GO_HOME, "false");
+        TestablePressHomeRule rule = new TestablePressHomeRule(pressHomeBundle);
+
+        Statement testStatement = new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                // Assert that navigate to press home is not initiated.
+                verify(rule.getUiDevice(), never()).pressHome();
+            }
+        };
+        rule.apply(testStatement, Description.createTestDescription("clzz", "mthd")).evaluate();
+        // Assert that the home button is not pressed after the test method.
+        verify(rule.getUiDevice(), times(0)).pressHome();
+    }
+
+    private static class TestablePressHomeRule extends PressHomeRule {
+        private UiDevice mUiDevice;
+        private Bundle mBundle;
+
+        public TestablePressHomeRule(Bundle bundle) {
+            mUiDevice = Mockito.mock(UiDevice.class);
+            mBundle = bundle;
+        }
+
+        @Override
+        protected Bundle getArguments() {
+            return mBundle;
+        }
+
+        @Override
+        protected UiDevice getUiDevice() {
+            return mUiDevice;
+        }
+    }
+}