[CTS] Add tests for profiler functionality in 'am' command line tool

Verify the profiling funcitonality is avaialbe using both
'am start --start-profiler' and 'am profile start' commands.

Also briefly verify the output file regardless of whether
'--sampling N' and '--streaming' options are given.

Bug: 33300765

Test: android.server.cts.ActivityManagerAmProfileTests
	#testAmProfileStartNoSamplingNoStreaming
	#testAmProfileStartNoSamplingStreaming
	#testAmProfileStartSamplingNoStreaming
	#testAmProfileStartSamplingStreaming
	#testAmStartStartProfilerNoSamplingNoStreaming
	#testAmStartStartProfilerNoSamplingStreaming
	#testAmStartStartProfilerSamplingNoStreaming
	#testAmStartStartProfilerSamplingStreaming

Change-Id: I4d3c58e5dec6b5fe88d27694ac6410475a39e1a8
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/AndroidTest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/AndroidTest.xml
index 6b2a9c2..883cc5b 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/AndroidTest.xml
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/AndroidTest.xml
@@ -19,11 +19,12 @@
         <option name="test-file-name" value="CtsDeviceServicesTestApp.apk" />
         <option name="test-file-name" value="CtsDeviceServicesTestSecondApp.apk" />
         <option name="test-file-name" value="CtsDeviceServicesTestThirdApp.apk" />
+        <option name="test-file-name" value="CtsDeviceDebuggableApp.apk" />
         <option name="test-file-name" value="CtsDeviceDisplaySizeApp.apk" />
         <option name="test-file-name" value="CtsDeviceTranslucentTestApp.apk" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsServicesHostTestCases.jar" />
-        <option name="runtime-hint" value="4m7s" />
+        <option name="runtime-hint" value="4m44s" />
     </test>
 </configuration>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/Android.mk b/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/Android.mk
new file mode 100644
index 0000000..fa381fb
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/Android.mk
@@ -0,0 +1,31 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := test_current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceDebuggableApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/AndroidManifest.xml
new file mode 100644
index 0000000..0ab25aa
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.server.cts">
+
+    <!--
+     * Security policy requires that only debuggable processes can be profiled
+     * which is tested by ActivityManagerAmProfileTests.
+     -->
+    <application android:debuggable="true">
+        <activity android:name=".DebuggableAppActivity"
+                  android:resizeableActivity="true"
+                  android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/src/android/server/cts/DebuggableAppActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/src/android/server/cts/DebuggableAppActivity.java
new file mode 100644
index 0000000..e844ef7
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/src/android/server/cts/DebuggableAppActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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 android.server.cts;
+
+import android.app.Activity;
+
+public class DebuggableAppActivity extends Activity {
+}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmProfileTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmProfileTests.java
new file mode 100644
index 0000000..87e62f1d
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmProfileTests.java
@@ -0,0 +1,164 @@
+/*
+ * 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 android.server.cts;
+
+import com.google.common.io.Files;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+import java.lang.StringBuilder;
+
+/**
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerAmProfileTests
+ *
+ * Please talk to Android Studio team first if you want to modify or delete these tests.
+ */
+public class ActivityManagerAmProfileTests extends ActivityManagerTestBase {
+
+    private static final String TEST_ACTIVITY_NAME = "DebuggableAppActivity";
+    private static final String OUTPUT_FILE_PATH = "/data/local/tmp/profile.trace";
+    private static final String FIRST_WORD_NO_STREAMING = "*version\n";
+    private static final String FIRST_WORD_STREAMING = "SLOW";  // Magic word set by runtime.
+
+    private ITestDevice mDevice;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDevice = getDevice();
+    }
+
+    /**
+     * Test am profile functionality with the following 3 configurable options:
+     *    starting the activity before start profiling? yes;
+     *    sampling-based profiling? no;
+     *    using streaming output mode? no.
+     */
+    public void testAmProfileStartNoSamplingNoStreaming() throws Exception {
+        // am profile start ... , and the same to the following 3 test methods.
+        testProfile(true, false, false);
+    }
+
+    /**
+     * The following tests are similar to testAmProfileStartNoSamplingNoStreaming(),
+     * only different in the three configuration options.
+     */
+    public void testAmProfileStartNoSamplingStreaming() throws Exception {
+        testProfile(true, false, true);
+    }
+    public void testAmProfileStartSamplingNoStreaming() throws Exception {
+        testProfile(true, true, false);
+    }
+    public void testAmProfileStartSamplingStreaming() throws Exception {
+        testProfile(true, true, true);
+    }
+    public void testAmStartStartProfilerNoSamplingNoStreaming() throws Exception {
+        // am start --start-profiler ..., and the same to the following 3 test methods.
+        testProfile(false, false, false);
+    }
+    public void testAmStartStartProfilerNoSamplingStreaming() throws Exception {
+        testProfile(false, false, true);
+    }
+    public void testAmStartStartProfilerSamplingNoStreaming() throws Exception {
+        testProfile(false, true, false);
+    }
+    public void testAmStartStartProfilerSamplingStreaming() throws Exception {
+        testProfile(false, true, true);
+    }
+
+    private void testProfile(boolean startActivityFirst,
+                             boolean sampling, boolean streaming) throws Exception {
+        if (startActivityFirst) {
+            launchActivity(TEST_ACTIVITY_NAME);
+        }
+
+        String cmd = getStartCmd(TEST_ACTIVITY_NAME, startActivityFirst, sampling, streaming);
+        executeShellCommand(cmd);
+        // Go to home screen and then warm start the activity to generate some interesting trace.
+        pressHomeButton();
+        launchActivity(TEST_ACTIVITY_NAME);
+
+        cmd = "am profile stop " + componentName;
+        executeShellCommand(cmd);
+        // Sleep for 0.1 second (100 milliseconds) so the generation of the profiling
+        // file is complete.
+        try {
+            Thread.sleep(100);
+        } catch (InterruptedException e) {
+            //ignored
+        }
+        verifyOutputFileFormat(streaming);
+    }
+
+    private String getStartCmd(String activityName, boolean activityAlreadyStarted,
+                                        boolean sampling, boolean streaming) {
+        StringBuilder builder = new StringBuilder("am");
+        if (activityAlreadyStarted) {
+            builder.append(" profile start");
+        } else {
+            builder.append(String.format(" start -n %s/.%s -W --start-profiler %s",
+                                         componentName, activityName, OUTPUT_FILE_PATH));
+        }
+        if (sampling) {
+            builder.append(" --sampling 1000");
+        }
+        if (streaming) {
+            builder.append(" --streaming");
+        }
+        if (activityAlreadyStarted) {
+            builder.append(String.format(" %s %s", componentName, OUTPUT_FILE_PATH));
+        } else {
+
+        }
+        return builder.toString();
+    }
+
+    private void verifyOutputFileFormat(boolean streaming) throws Exception {
+        String expectedFirstWord = streaming ? FIRST_WORD_STREAMING : FIRST_WORD_NO_STREAMING;
+        byte[] data = readFileOnClient(OUTPUT_FILE_PATH);
+        assertTrue("data size=" + data.length, data.length >= expectedFirstWord.length());
+        String actualFirstWord = new String(data, 0, expectedFirstWord.length());
+        assertTrue("Unexpected first word: '" + actualFirstWord + "'",
+                   actualFirstWord.equals(expectedFirstWord));
+        // Clean up.
+        executeShellCommand("rm -f " + OUTPUT_FILE_PATH);
+    }
+
+    private byte[] readFileOnClient(String clientPath) throws Exception {
+        assertTrue("File not found on client: " + clientPath,
+                mDevice.doesFileExist(clientPath));
+        File copyOnHost = File.createTempFile("host", "copy");
+        try {
+            executeAdbCommand("pull", clientPath, copyOnHost.getPath());
+            return Files.toByteArray(copyOnHost);
+        } finally {
+            FileUtil.deleteFile(copyOnHost);
+        }
+    }
+
+    private String[] executeAdbCommand(String... command) throws DeviceNotAvailableException {
+        String output = mDevice.executeAdbCommand(command);
+        // "".split() returns { "" }, but we want an empty array
+        String[] lines = output.equals("") ? new String[0] : output.split("\n");
+        return lines;
+    }
+
+}