RootlessGpuDebug: Change Activity to Service

On platforms that use Vulkan exclusively, we're tripping up Activity
start when we launch negative tests (i.e. those that expect to FAIL
when launching with layers that cannot be accessed).  We don't even
make it to our test code before our process has been terminated due
to failed vkCreateInstance calls in platform code.

In order to avoid complications with how the platform renders the
empty Activity, this CL switches to a background Service that doesn't
render anything.

This test really only wants to init Vulkan and GLES itself in native
code, then observe loader and layer behavior.

Test: CtsGpuToolsHostTestCases on Cuttlefish using SkiaVk
Bug: b/206011745
Bug: b/206023179
Change-Id: Icbeab9850e24ead6ffd996a5531f32cd5da37035
(cherry picked from commit f7411f644badc1a582249aa98db062dfc769d873)
diff --git a/hostsidetests/gputools/apps/AndroidManifest.xml b/hostsidetests/gputools/apps/AndroidManifest.xml
index 89ecaf8..3c8de1f 100755
--- a/hostsidetests/gputools/apps/AndroidManifest.xml
+++ b/hostsidetests/gputools/apps/AndroidManifest.xml
@@ -19,13 +19,13 @@
      package="android.rootlessgpudebug.app">
 
     <application android:extractNativeLibs="true">
-        <activity android:name=".RootlessGpuDebugDeviceActivity"
+        <service android:name=".RootlessGpuDebugService"
+             android:process=":target_api_service"
              android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
+                <action android:name="android.service.action.TARGET_API_SERVICE"/>
             </intent-filter>
-        </activity>
+        </service>
     </application>
 
 </manifest>
diff --git a/hostsidetests/gputools/apps/inject/AndroidManifest.xml b/hostsidetests/gputools/apps/inject/AndroidManifest.xml
index 63bd9c1..743c2be 100644
--- a/hostsidetests/gputools/apps/inject/AndroidManifest.xml
+++ b/hostsidetests/gputools/apps/inject/AndroidManifest.xml
@@ -21,13 +21,12 @@
     <application android:extractNativeLibs="true">
         <meta-data android:name="com.android.graphics.injectLayers.enable"
              android:value="true"/>
-        <activity android:name=".RootlessGpuDebugDeviceActivity"
-             android:exported="true">
+        <service android:name=".RootlessGpuDebugService"
+                android:process=":target_api_service"
+                android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
+                <action android:name="android.service.action.TARGET_API_SERVICE"/>
             </intent-filter>
-        </activity>
+        </service>
     </application>
-
 </manifest>
diff --git a/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp b/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp
index f62a583..44317c5 100644
--- a/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp
+++ b/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp
@@ -17,9 +17,6 @@
 
 #define LOG_TAG "RootlessGpuDebug"
 
-#include <string>
-#include <vector>
-
 #include <EGL/egl.h>
 #include <GLES3/gl3.h>
 #include <android/log.h>
@@ -27,6 +24,10 @@
 #include <jni.h>
 #include <vulkan/vulkan.h>
 
+#include <sstream>
+#include <string>
+#include <vector>
+
 #define ALOGI(msg, ...) \
     __android_log_print(ANDROID_LOG_INFO, LOG_TAG, (msg), __VA_ARGS__)
 #define ALOGE(msg, ...) \
@@ -39,7 +40,7 @@
 typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer;
 
 std::string initVulkan() {
-    std::string result = "";
+    std::stringstream result;
 
     {
       uint32_t count = 0;
@@ -78,13 +79,13 @@
     };
     VkInstance instance;
     VkResult vkResult = vkCreateInstance(&instance_info, nullptr, &instance);
-    if (vkResult == VK_ERROR_INITIALIZATION_FAILED) {
-        result = "vkCreateInstance failed, meaning layers could not be chained.";
+    if (vkResult == VK_SUCCESS) {
+        result << "vkCreateInstance succeeded.";
     } else {
-        result = "vkCreateInstance succeeded.";
+        result << "vkCreateInstance failed with VkResult: " << vkResult;
     }
 
-    return result;
+    return result.str();
 }
 
 std::string initGLES() {
@@ -163,8 +164,7 @@
 }  // anonymous namespace
 
 int register_android_gputools_cts_RootlessGpuDebug(JNIEnv* env) {
-    jclass clazz = env->FindClass(
-        "android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity");
+    jclass clazz = env->FindClass("android/rootlessgpudebug/app/RootlessGpuDebugService");
     return env->RegisterNatives(clazz, gMethods,
                                 sizeof(gMethods) / sizeof(JNINativeMethod));
 }
diff --git a/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity.java b/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity.java
deleted file mode 100644
index a29a246..0000000
--- a/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.rootlessgpudebug.app;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.lang.Override;
-
-public class RootlessGpuDebugDeviceActivity extends Activity {
-
-    static {
-        System.loadLibrary("ctsgputools_jni");
-    }
-
-    private static final String TAG = RootlessGpuDebugDeviceActivity.class.getSimpleName();
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        String result = nativeInitVulkan();
-        Log.i(TAG, result);
-
-        result = nativeInitGLES();
-        Log.i(TAG, result);
-
-        Log.i(TAG, "RootlessGpuDebug activity complete");
-    }
-
-    private static native String nativeInitVulkan();
-    private static native String nativeInitGLES();
-
-}
-
diff --git a/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugService.java b/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugService.java
new file mode 100644
index 0000000..47b1ad6
--- /dev/null
+++ b/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugService.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 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.rootlessgpudebug.app;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.lang.Override;
+
+
+public class RootlessGpuDebugService extends Service {
+
+    private static final String TAG = RootlessGpuDebugService.class.getSimpleName();
+
+    static {
+        System.loadLibrary("ctsgputools_jni");
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        // We don't provide binding, so return null
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+
+        // The target API is provided via Intent extras
+        String API = null;
+
+        if (intent == null) {
+            throw new AssertionError("No Intent provided to " + TAG);
+        }
+
+        Bundle bundle = intent.getExtras();
+
+        // Ensure the service was started with extras
+        if (bundle == null) {
+            throw new AssertionError("Failed to get Intent extras for " + TAG);
+        }
+
+        API = bundle.getString("API");
+
+        // Without an API to target, we're done
+        if (API == null) {
+            throw new AssertionError("No API provided for " + TAG);
+        }
+
+        // Only three combinations are expected
+        Boolean supportedApi = API.equals("Vulkan") ||
+                               API.equals("GLES") ||
+                               API.equals("Both");
+        if (!supportedApi) {
+            throw new AssertionError("Unsupported API " + API + " in " + TAG);
+        }
+
+        // For each choice, init the target API
+        if (API.equals("Vulkan") || API.equals("Both")) {
+            String result = nativeInitVulkan();
+            Log.i(TAG, result);
+        }
+
+        if (API.equals("GLES") || API.equals("Both")) {
+            String result = nativeInitGLES();
+            Log.i(TAG, result);
+        }
+
+        // Mark service completion
+        Log.i(TAG, "RootlessGpuDebugService complete");
+
+        // Don't try to restart when this is shut down
+        return START_NOT_STICKY;
+    }
+
+    private static native String nativeInitVulkan();
+    private static native String nativeInitGLES();
+}
+
diff --git a/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java b/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
index 4ec4ef8..3f4c4c9 100644
--- a/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
+++ b/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
@@ -33,7 +33,7 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class CtsRootlessGpuDebugHostTest extends BaseHostJUnit4Test {
 
-    public static final String TAG = "RootlessGpuDebugDeviceActivity";
+    public static final String TAG = "RootlessGpuDebugService";
 
     // This test ensures that the Vulkan and GLES loaders can use Settings to load layers
     // from the base directory of debuggable applications.  Is also tests several
@@ -90,8 +90,9 @@
     // Positive combined tests
     // - Ensure we can load Vulkan and GLES layers at the same time, from multiple external apps (testMultipleExternalApps)
 
-    private static final String CLASS = "RootlessGpuDebugDeviceActivity";
-    private static final String ACTIVITY = "android.rootlessgpudebug.app.RootlessGpuDebugDeviceActivity";
+    private static final String API_VULKAN = "Vulkan";
+    private static final String API_GLES = "GLES";
+    private static final String API_BOTH = "Both";
     private static final String VK_LAYER_LIB_PREFIX = "libVkLayer_nullLayer";
     private static final String VK_LAYER_A_LIB = VK_LAYER_LIB_PREFIX + "A.so";
     private static final String VK_LAYER_B_LIB = VK_LAYER_LIB_PREFIX + "B.so";
@@ -257,7 +258,7 @@
                     result.found = true;
                     result.lineNumber = lineNumber;
                 }
-                if (line.contains("RootlessGpuDebug activity complete")) {
+                if (line.contains("RootlessGpuDebugService complete")) {
                     // Once we've got output from the app, we've collected what we need
                     scanComplete= true;
                 }
@@ -311,6 +312,20 @@
     }
 
     /**
+     * Launch our test as a background service, avoiding any platform rendering code
+     */
+    private void launchBackgroundService(String appName, String Api) throws Exception {
+
+        // Allow the app to be launched as a background service
+        getDevice().executeAdbCommand("shell", "cmd", "deviceidle", "tempwhitelist", appName);
+
+        // Start the service and tell it to init Vulkan/GLES/Both
+        getDevice().executeAdbCommand("shell", "am", "startservice", "-a", "android.service.action.TARGET_API_SERVICE",
+                                      "--es", "API", Api, appName);
+    }
+
+
+    /**
      * This is the primary test of the feature. It pushes layers to our debuggable app and ensures they are
      * loaded in the correct order.
      */
@@ -326,7 +341,6 @@
         setupLayer(VK_LAYER_A_LIB, LAYERS_APP);
         setupLayer(VK_LAYER_B_LIB, LAYERS_APP);
 
-
         // Copy them over to our DEBUG app
         getDevice().executeAdbCommand("shell", "cat", "/data/local/tmp/" + VK_LAYER_A_LIB, "|",
                 "run-as", DEBUG_APP, "--user", Integer.toString(getDevice().getCurrentUser()),
@@ -335,10 +349,9 @@
                 "run-as", DEBUG_APP, "--user", Integer.toString(getDevice().getCurrentUser()),
                 "sh", "-c", "\'cat", ">", VK_LAYER_B_LIB, ";", "chmod", "700", VK_LAYER_B_LIB + "\'");
 
-
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        launchBackgroundService(DEBUG_APP, API_VULKAN);
 
         // Check that both layers were loaded, in the correct order
         String searchStringA = "nullCreateInstance called in " + VK_LAYER_A;
@@ -353,6 +366,7 @@
     }
 
     public void testLayerNotLoadedVulkan(final String APP_NAME) throws Exception {
+
         // Set up a layers to be loaded for RELEASE or INJECT app
         applySetting("enable_gpu_debug_layers", "1");
         applySetting("gpu_debug_app", APP_NAME);
@@ -366,9 +380,9 @@
             "run-as", APP_NAME, "--user", Integer.toString(getDevice().getCurrentUser()),
             "sh", "-c", "\'cat", ">", VK_LAYER_A_LIB, ";", "chmod", "700", VK_LAYER_A_LIB + "\'", "||", "echo", "run-as", "failed");
 
-        // Kick off our RELEASE app
+        // Kick off our app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", APP_NAME + "/" + ACTIVITY);
+        launchBackgroundService(APP_NAME, API_VULKAN);
 
         // Ensure we don't load the layer in base dir
         assertVkLayerEnumeration(appStartTime, VK_LAYER_A, false);
@@ -414,7 +428,7 @@
 
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        launchBackgroundService(DEBUG_APP, API_VULKAN);
 
         // Ensure we don't load the layer in base dir
         assertVkLayerEnumeration(appStartTime, VK_LAYER_A, false);
@@ -442,7 +456,7 @@
 
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        launchBackgroundService(DEBUG_APP, API_VULKAN);
 
         // Ensure we don't load the layer in base dir
         assertVkLayerEnumeration(appStartTime, VK_LAYER_A, false);
@@ -470,7 +484,7 @@
 
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        launchBackgroundService(DEBUG_APP, API_VULKAN);
 
         // Ensure layerA is not loaded
         assertVkLayerLoading(appStartTime, VK_LAYER_A, false);
@@ -492,7 +506,7 @@
 
         // Kick off our RELEASE app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", RELEASE_APP + "/" + ACTIVITY);
+        launchBackgroundService(RELEASE_APP, API_VULKAN);
 
         // Check that only layerC was loaded
         assertVkLayerEnumeration(appStartTime, VK_LAYER_A, false);
@@ -527,7 +541,7 @@
 
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        launchBackgroundService(DEBUG_APP, API_VULKAN);
 
         // Ensure only layerA is loaded
         assertVkLayerLoading(appStartTime, VK_LAYER_A, true);
@@ -548,9 +562,9 @@
         // Specify the external app that hosts layers
         applySetting("gpu_debug_layer_app", LAYERS_APP);
 
-        // Kick off our DEBUG app
+        // Kick off our app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", APP_NAME + "/" + ACTIVITY);
+        launchBackgroundService(APP_NAME, API_VULKAN);
 
         String[] layerNames = layers.split(":");
         for (String layerName : layerNames) {
@@ -621,7 +635,7 @@
 
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        launchBackgroundService(DEBUG_APP, API_GLES);
 
         // Check that both layers were loaded, in the correct order
         String searchStringA = "glesLayer_eglChooseConfig called in " + GLES_LAYER_A;
@@ -657,7 +671,7 @@
 
         // Kick off our RELEASE app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", RELEASE_APP + "/" + ACTIVITY);
+        launchBackgroundService(RELEASE_APP, API_GLES);
 
         // Ensure we don't load the layer in base dir
         String searchStringA = GLES_LAYER_A + " loaded";
@@ -686,7 +700,7 @@
 
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        launchBackgroundService(DEBUG_APP, API_GLES);
 
         // Ensure we don't load the layer in base dir
         String searchStringA = GLES_LAYER_A + " loaded";
@@ -715,7 +729,7 @@
 
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        launchBackgroundService(DEBUG_APP, API_GLES);
 
         // Ensure we don't load the layer in base dir
         String searchStringA = GLES_LAYER_A + " loaded";
@@ -744,7 +758,7 @@
 
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        launchBackgroundService(DEBUG_APP, API_GLES);
 
         // Ensure layerA is not loaded
         String searchStringA = "glesLayer_eglChooseConfig called in " + GLES_LAYER_A;
@@ -768,7 +782,7 @@
 
         // Kick off our RELEASE app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", RELEASE_APP + "/" + ACTIVITY);
+        launchBackgroundService(RELEASE_APP, API_GLES);
 
         // Check that both layers were loaded, in the correct order
         String searchStringA = GLES_LAYER_A + "loaded";
@@ -808,7 +822,7 @@
 
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        launchBackgroundService(DEBUG_APP, API_GLES);
 
         // Ensure only layerA is loaded
         String searchStringA = "glesLayer_eglChooseConfig called in " + GLES_LAYER_A;
@@ -829,9 +843,9 @@
         // Specify the external app that hosts layers
         applySetting("gpu_debug_layer_app", GLES_LAYERS_APP);
 
-        // Kick off our DEBUG app
+        // Kick off our app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", APP_NAME + "/" + ACTIVITY);
+        launchBackgroundService(APP_NAME, API_GLES);
 
         // Check that our external layer was loaded
         String searchStringC = "glesLayer_eglChooseConfig called in " + GLES_LAYER_C;
@@ -872,7 +886,7 @@
 
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        launchBackgroundService(DEBUG_APP, API_BOTH);
 
         // Check that external layers were loaded from both apps
         assertVkLayerLoading(appStartTime, VK_LAYER_C, true);