Add calling process id checking for ScriptExecutor.

Bug: 201332144
Bug: 207175350
Test: atest
ScriptExecutorFunctionalTests:ScriptExecutorFunctionalTest
Test: atest ScriptExecutorNonSystemUserTest:ScriptExecutorNoneSystemUserTest
Test: atest ScriptExecutorUnitTest:JniUtilsTest

Change-Id: I72f0c2e8e08e6bcffa9a0d87ada3553ddc0700cd
Merged-In: I72f0c2e8e08e6bcffa9a0d87ada3553ddc0700cd
(cherry picked from commit cb5cf0a7c5b48ccbd0b1de592b5105747f577fee)
diff --git a/packages/ScriptExecutor/Android.bp b/packages/ScriptExecutor/Android.bp
index 952a9b8..7ada5bf 100644
--- a/packages/ScriptExecutor/Android.bp
+++ b/packages/ScriptExecutor/Android.bp
@@ -88,6 +88,6 @@
         "src/**/*.java",
     ],
 
-    sdk_version: "current",
+    sdk_version: "test_current",
 }
 
diff --git a/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java b/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java
index abd4145..362005e 100644
--- a/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java
+++ b/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java
@@ -18,12 +18,15 @@
 
 import android.app.Service;
 import android.content.Intent;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
@@ -37,7 +40,6 @@
  * and input arguments.
  */
 public final class ScriptExecutor extends Service {
-
     static {
         System.loadLibrary("scriptexecutorjni");
     }
@@ -53,7 +55,8 @@
         @Override
         public void invokeScript(String scriptBody, String functionName,
                 PersistableBundle publishedData, PersistableBundle savedState,
-                IScriptExecutorListener listener) {
+                IScriptExecutorListener listener) throws SecurityException {
+            ensureCallerIsSystem();
             mNativeHandler.post(() ->
                     nativeInvokeScript(mLuaEnginePtr, scriptBody, functionName, publishedData,
                             savedState, listener));
@@ -62,7 +65,8 @@
         @Override
         public void invokeScriptForLargeInput(String scriptBody, String functionName,
                 ParcelFileDescriptor publishedDataFileDescriptor, PersistableBundle savedState,
-                IScriptExecutorListener listener) {
+                IScriptExecutorListener listener) throws SecurityException {
+            ensureCallerIsSystem();
             mNativeHandler.post(() -> {
                 PersistableBundle publishedData;
                 try (InputStream input = new ParcelFileDescriptor.AutoCloseInputStream(
@@ -117,6 +121,12 @@
         return mScriptExecutorBinder;
     }
 
+    private void ensureCallerIsSystem() throws SecurityException {
+        if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
+            throw new SecurityException("ScriptExecutor called from non-system user");
+        }
+    }
+
     /**
      * Initializes Lua Engine.
      *
diff --git a/packages/ScriptExecutor/tests/unit/README.md b/packages/ScriptExecutor/tests/README.md
similarity index 69%
rename from packages/ScriptExecutor/tests/unit/README.md
rename to packages/ScriptExecutor/tests/README.md
index 8c5a67a..fb2e52d 100644
--- a/packages/ScriptExecutor/tests/unit/README.md
+++ b/packages/ScriptExecutor/tests/README.md
@@ -14,7 +14,18 @@
   limitations under the License
   -->
 
-# How to run unit tests for ScriptExecutor
+# Test categories
+
+**1. Functional Tests
+
+ScriptExecutor functional tests that are run as system user - which is required by
+ScriptExecutor to invoke script processing.
+
+**2. Non-system-user Tests
+
+These are tests that does not need to be run as system user or tests running as non-system user.
+
+# How to run tests for ScriptExecutor
 
 **1. Navigate to the root of the repo and do full build:**
 
@@ -26,7 +37,7 @@
 
 **3. Run the tests. For example**
 
-`atest ScriptExecutorUnitTest:ScriptExecutorTest`
+`atest ScriptExecutorFunctionalTests:ScriptExecutorFunctionalTest`
 
 
 ## How to rerun the tests after changes
@@ -34,7 +45,7 @@
 device flash.
 
 **1. Navigate to ScriptExecutor unit test location and build its targets:**
-`cd packages/services/Car/packages/ScriptExecutor/tests/unit`
+`cd packages/services/Car/packages/ScriptExecutor/tests/functional`
 
 `mm -j`
 
@@ -48,5 +59,5 @@
 
 **3. At this point we are ready to run the tests again. For example:**
 
-`atest ScriptExecutorUnitTest:ScriptExecutorTest`
+`atest ScriptExecutorFunctionalTests:ScriptExecutorFunctionalTest`
 
diff --git a/packages/ScriptExecutor/tests/functional/Android.bp b/packages/ScriptExecutor/tests/functional/Android.bp
new file mode 100644
index 0000000..1e0ef38
--- /dev/null
+++ b/packages/ScriptExecutor/tests/functional/Android.bp
@@ -0,0 +1,41 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "ScriptExecutorFunctionalTests",
+
+    srcs: ["src/**/*.java"],
+
+    platform_apis: true,
+
+    certificate: "platform",
+
+    instrumentation_for: "ScriptExecutor",
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "junit",
+        "scriptexecutor-test-lib",
+        "truth-prebuilt",
+    ],
+
+    test_suites: ["general-tests"],
+}
diff --git a/packages/ScriptExecutor/tests/functional/AndroidManifest.xml b/packages/ScriptExecutor/tests/functional/AndroidManifest.xml
new file mode 100644
index 0000000..79e85d6
--- /dev/null
+++ b/packages/ScriptExecutor/tests/functional/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.scriptexecutortest.functional"
+          android:sharedUserId="android.uid.system">
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+    <queries>
+        <package android:name="com.android.car.scriptexecutor" />
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.car.scriptexecutortest.functional"
+                     android:label="Tests for ScriptExecutor"/>
+</manifest>
diff --git a/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java b/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java
new file mode 100644
index 0000000..e9c8557
--- /dev/null
+++ b/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java
@@ -0,0 +1,915 @@
+/*
+ * 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 com.android.car.scriptexecutortest.functional;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.scriptexecutor.ScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.OutputStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public final class ScriptExecutorFunctionalTest {
+
+    private IScriptExecutor mScriptExecutor;
+    private ScriptExecutor mInstance;
+    private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+    private static final class ScriptExecutorListener extends IScriptExecutorListener.Stub {
+        private PersistableBundle mInterimResult;
+        private PersistableBundle mFinalResult;
+        private int mErrorType;
+        private String mMessage;
+        private String mStackTrace;
+        private final CountDownLatch mResponseLatch = new CountDownLatch(1);
+
+        @Override
+        public void onScriptFinished(PersistableBundle result) {
+            mFinalResult = result;
+            mResponseLatch.countDown();
+        }
+
+        @Override
+        public void onSuccess(PersistableBundle interimResult) {
+            mInterimResult = interimResult;
+            mResponseLatch.countDown();
+        }
+
+        @Override
+        public void onError(int errorType, String message, String stackTrace) {
+            mErrorType = errorType;
+            mMessage = message;
+            mStackTrace = stackTrace;
+            mResponseLatch.countDown();
+        }
+
+        private boolean awaitResponse(int waitTimeSec) throws InterruptedException {
+            return mResponseLatch.await(waitTimeSec, TimeUnit.SECONDS);
+        }
+    }
+
+    private final ScriptExecutorListener mListener = new ScriptExecutorListener();
+    private final PersistableBundle mEmptyPublishedData = new PersistableBundle();
+    private final PersistableBundle mEmptyIterimResult = new PersistableBundle();
+    private final CountDownLatch mBindLatch = new CountDownLatch(1);
+
+    private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
+    private static final int SCRIPT_PROCESSING_TIMEOUT_SEC = 10;
+
+    private final ServiceConnection mScriptExecutorConnection =
+            new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName className, IBinder service) {
+                    mScriptExecutor = IScriptExecutor.Stub.asInterface(service);
+                    mBindLatch.countDown();
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName className) {
+                    assertWithMessage("Service unexpectedly disconnected").fail();
+                }
+            };
+
+    @Before
+    public void setUp() throws InterruptedException {
+        Intent intent = new Intent();
+        intent.setComponent(
+                new ComponentName(
+                        "com.android.car.scriptexecutor",
+                        "com.android.car.scriptexecutor.ScriptExecutor"));
+        mContext.bindServiceAsUser(
+                intent, mScriptExecutorConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+        if (!mBindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            assertWithMessage("Failed to bind to ScriptExecutor service").fail();
+        }
+    }
+
+    @Test
+    public void invokeScript_returnsResult() throws RemoteException, InterruptedException {
+        String returnResultScript =
+                "function hello(data, state)\n"
+                        + "    result = {hello=\"world\"}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(
+                returnResultScript, "hello", mEmptyPublishedData, mEmptyIterimResult);
+
+        // Expect to get back a bundle with a single string key: string value pair:
+        // {"hello": "world"}
+        assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+        assertThat(mListener.mInterimResult.getString("hello")).isEqualTo("world");
+    }
+
+    @Test
+    public void invokeScript_allSupportedPrimitiveTypes()
+            throws RemoteException, InterruptedException {
+        String script =
+                "function knows(data, state)\n"
+                        + "    result = {string=\"hello\", boolean=true, integer=1, number=1.1}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(script, "knows", mEmptyPublishedData, mEmptyIterimResult);
+
+        // Expect to get back a bundle with 4 keys, each corresponding to a distinct supported type.
+        assertThat(mListener.mInterimResult.size()).isEqualTo(4);
+        assertThat(mListener.mInterimResult.getString("string")).isEqualTo("hello");
+        assertThat(mListener.mInterimResult.getBoolean("boolean")).isEqualTo(true);
+        assertThat(mListener.mInterimResult.getLong("integer")).isEqualTo(1);
+        assertThat(mListener.mInterimResult.getDouble("number")).isEqualTo(1.1);
+    }
+
+    @Test
+    public void invokeScript_skipsUnsupportedNestedTables()
+            throws RemoteException, InterruptedException {
+        String script =
+                "function nested(data, state)\n"
+                        + "    result = {string=\"hello\", boolean=true, integer=1, number=1.1}\n"
+                        + "    result.nested_table = {x=0, y=0}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(script, "nested", mEmptyPublishedData, mEmptyIterimResult);
+
+        // Verify that expected error is received.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage).contains("nested tables are not supported");
+    }
+
+    @Test
+    public void invokeScript_emptyBundle() throws RemoteException, InterruptedException {
+        String script =
+                "function empty(data, state)\n"
+                        + "    result = {}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(script, "empty", mEmptyPublishedData, mEmptyIterimResult);
+
+        // If a script returns empty table as the result, we get an empty bundle.
+        assertThat(mListener.mInterimResult).isNotNull();
+        assertThat(mListener.mInterimResult.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void invokeScript_processPreviousStateAndReturnResult()
+            throws RemoteException, InterruptedException {
+        // Here we verify that the script actually processes provided state from a previous run
+        // and makes calculation based on that and returns the result.
+        String script =
+                "function update(data, state)\n"
+                        + "    result = {y = state.x+1}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putInt("x", 1);
+
+        runScriptAndWaitForResponse(script, "update", mEmptyPublishedData, previousState);
+
+        // Verify that y = 2, because y = x + 1 and x = 1.
+        assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+        assertThat(mListener.mInterimResult.getLong("y")).isEqualTo(2);
+    }
+
+    @Test
+    public void invokeScript_allSupportedPrimitiveTypesWorkRoundTripWithKeyNamesPreserved()
+            throws RemoteException, InterruptedException {
+        // Here we verify that all supported primitive types in supplied previous state Bundle
+        // are interpreted by the script as expected.
+        String script =
+                "function update_all(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.integer = state.integer + 1\n"
+                        + "    result.number = state.number + 0.1\n"
+                        + "    result.boolean = not state.boolean\n"
+                        + "    result.string = state.string .. \"CADABRA\"\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putInt("integer", 1);
+        previousState.putDouble("number", 0.1);
+        previousState.putBoolean("boolean", false);
+        previousState.putString("string", "ABRA");
+
+        runScriptAndWaitForResponse(script, "update_all", mEmptyPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mListener.mInterimResult.size()).isEqualTo(4);
+        assertThat(mListener.mInterimResult.getLong("integer")).isEqualTo(2);
+        assertThat(mListener.mInterimResult.getDouble("number")).isEqualTo(0.2);
+        assertThat(mListener.mInterimResult.getBoolean("boolean")).isEqualTo(true);
+        assertThat(mListener.mInterimResult.getString("string")).isEqualTo("ABRACADABRA");
+    }
+
+    @Test
+    public void invokeScript_allSupportedArrayTypesWorkRoundTripWithKeyNamesPreserved()
+            throws RemoteException, InterruptedException {
+        // Here we verify that all supported array types in supplied previous state Bundle are
+        // interpreted by the script as expected.
+        String script =
+                "function arrays(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.int_array = state.int_array\n"
+                        + "    result.long_array = state.long_array\n"
+                        + "    result.string_array = state.string_array\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        int[] int_array = new int[] {1, 2};
+        long[] int_array_in_long = new long[] {1, 2};
+        long[] long_array = new long[] {1, 2, 3};
+        String[] string_array = new String[] {"one", "two", "three"};
+        previousState.putIntArray("int_array", int_array);
+        previousState.putLongArray("long_array", long_array);
+        previousState.putStringArray("string_array", string_array);
+
+        runScriptAndWaitForResponse(script, "arrays", mEmptyPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mListener.mInterimResult.size()).isEqualTo(3);
+        // Lua has only one lua_Integer. Here Java long is used to represent it when data is
+        // transferred from Lua to CarTelemetryService.
+        assertThat(mListener.mInterimResult.getLongArray("int_array")).isEqualTo(int_array_in_long);
+        assertThat(mListener.mInterimResult.getLongArray("long_array")).isEqualTo(long_array);
+        assertThat(mListener.mInterimResult.getStringArray("string_array")).isEqualTo(string_array);
+    }
+
+    @Test
+    public void invokeScript_modifiesArray() throws RemoteException, InterruptedException {
+        // Verify that an array modified by a script is properly sent back by the callback.
+        String script =
+                "function modify_array(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.long_array = state.long_array\n"
+                        + "    result.long_array[2] = 100\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        long[] long_array = new long[] {1, 2, 3};
+        previousState.putLongArray("long_array", long_array);
+        long[] expected_array = new long[] {1, 100, 3};
+
+        runScriptAndWaitForResponse(script, "modify_array", mEmptyPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+        assertThat(mListener.mInterimResult.getLongArray("long_array")).isEqualTo(expected_array);
+    }
+
+    @Test
+    public void invokeScript_processesStringArray() throws RemoteException, InterruptedException {
+        // Verify that an array modified by a script is properly sent back by the callback.
+        String script =
+                "function process_string_array(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.answer = state.string_array[1] .. state.string_array[2]\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        String[] string_array = new String[] {"Hello ", "world!"};
+        previousState.putStringArray("string_array", string_array);
+
+        runScriptAndWaitForResponse(
+                script, "process_string_array", mEmptyPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+        assertThat(mListener.mInterimResult.getString("answer")).isEqualTo("Hello world!");
+    }
+
+    @Test
+    public void invokeScript_arraysWithLengthAboveLimitCauseError()
+            throws RemoteException, InterruptedException {
+        // Verifies that arrays pushed by Lua that have their size over the limit cause error.
+        String script =
+                "function size_limit(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.huge_array = {}\n"
+                        + "    for i=1, 10000 do\n"
+                        + "        result.huge_array[i]=i\n"
+                        + "    end\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(
+                script, "size_limit", mEmptyPublishedData, mEmptyIterimResult);
+
+        // Verify that expected error is received.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage)
+                .isEqualTo(
+                        "Returned table huge_array exceeds maximum allowed size of 1000 elements."
+                                + " This key-value cannot be unpacked successfully. This error is"
+                                + " unrecoverable.");
+    }
+
+    @Test
+    public void invokeScript_arrayContainingVaryingTypesCausesError()
+            throws RemoteException, InterruptedException {
+        // Verifies that values in returned array must be the same integer type.
+        // For example string values in a Lua array are not allowed.
+        String script =
+                "function table_with_numbers_and_strings(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.mixed_array = state.long_array\n"
+                        + "    result.mixed_array[2] = 'a'\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        long[] long_array = new long[] {1, 2, 3};
+        previousState.putLongArray("long_array", long_array);
+
+        runScriptAndWaitForResponse(
+                script, "table_with_numbers_and_strings", mEmptyPublishedData, previousState);
+
+        // Verify that expected error is received.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage)
+                .contains("Returned Lua arrays must have elements of the same type.");
+    }
+
+    @Test
+    public void invokeScript_InTablesWithBothKeysAndIndicesCopiesOnlyIndexedData()
+            throws RemoteException, InterruptedException {
+        // Documents the current behavior that copies only indexed values in a Lua table that
+        // contains both keyed and indexed data.
+        String script =
+                "function keys_and_indices(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.mixed_array = state.long_array\n"
+                        + "    result.mixed_array['a'] = 130\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        long[] long_array = new long[] {1, 2, 3};
+        previousState.putLongArray("long_array", long_array);
+
+        runScriptAndWaitForResponse(
+                script, "keys_and_indices", mEmptyPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+        assertThat(mListener.mInterimResult.getLongArray("mixed_array")).isEqualTo(long_array);
+    }
+
+    @Test
+    public void invokeScript_noLuaBufferOverflowForLargeInputArrays()
+            throws RemoteException, InterruptedException {
+        // Tests that arrays with length that exceed internal Lua buffer size of 20 elements
+        // do not cause buffer overflow and are handled properly.
+        String script =
+                "function large_input_array(data, state)\n"
+                        + "    sum = 0\n"
+                        + "    for _, val in ipairs(state.long_array) do\n"
+                        + "        sum = sum + val\n"
+                        + "    end\n"
+                        + "    result = {total = sum}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        PersistableBundle previousState = new PersistableBundle();
+        int n = 10000;
+        long[] longArray = new long[n];
+        for (int i = 0; i < n; i++) {
+            longArray[i] = i;
+        }
+        previousState.putLongArray("long_array", longArray);
+        long expected_sum =
+                (longArray[0] + longArray[n - 1]) * n / 2; // sum of an arithmetic sequence.
+
+        runScriptAndWaitForResponse(
+                script, "large_input_array", mEmptyPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+        assertThat(mListener.mInterimResult.getLong("total")).isEqualTo(expected_sum);
+    }
+
+    @Test
+    public void invokeScript_scriptCallsOnError() throws RemoteException, InterruptedException {
+        String script =
+                "function calls_on_error()\n"
+                        + "    if 1 ~= 2 then\n"
+                        + "        on_error(\"one is not equal to two\")\n"
+                        + "        return\n"
+                        + "    end\n"
+                        + "end\n";
+
+        runScriptAndWaitForError(script, "calls_on_error");
+
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage).isEqualTo("one is not equal to two");
+    }
+
+    @Test
+    public void invokeScript_tooManyParametersInOnError()
+            throws RemoteException, InterruptedException {
+        String script =
+                "function too_many_params_in_on_error()\n"
+                        + "    if 1 ~= 2 then\n"
+                        + "        on_error(\"param1\", \"param2\")\n"
+                        + "        return\n"
+                        + "    end\n"
+                        + "end\n";
+
+        runScriptAndWaitForError(script, "too_many_params_in_on_error");
+
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage)
+                .isEqualTo("on_error can push only a single string parameter from Lua");
+    }
+
+    @Test
+    public void invokeScript_onErrorOnlyAcceptsString()
+            throws RemoteException, InterruptedException {
+        String script =
+                "function only_string()\n"
+                        + "    if 1 ~= 2 then\n"
+                        + "        on_error(false)\n"
+                        + "        return\n"
+                        + "    end\n"
+                        + "end\n";
+
+        runScriptAndWaitForError(script, "only_string");
+
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage)
+                .isEqualTo("on_error can push only a single string parameter from Lua");
+    }
+
+    @Test
+    public void invokeScript_returnsFinalResult() throws RemoteException, InterruptedException {
+        String returnFinalResultScript =
+                "function script_finishes(data, state)\n"
+                        + "    result = {data = state.input + 1}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putInt("input", 1);
+
+        runScriptAndWaitForResponse(
+                returnFinalResultScript, "script_finishes", mEmptyPublishedData, previousState);
+
+        // Expect to get back a bundle with a single key-value pair {"data": 2}
+        // because data = state.input + 1 as in the script body above.
+        assertThat(mListener.mFinalResult.size()).isEqualTo(1);
+        assertThat(mListener.mFinalResult.getLong("data")).isEqualTo(2);
+    }
+
+    @Test
+    public void invokeScript_emptyStringValueIsValidValue()
+            throws RemoteException, InterruptedException {
+        // Verify that an empty string value is a valid value to be returned from a script.
+        String returnFinalResultScript =
+                "function empty_string(data, state)\n"
+                        + "    result = {data = \"\"}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(
+                returnFinalResultScript,
+                "empty_string",
+                mEmptyPublishedData,
+                new PersistableBundle());
+
+        // Expect to get back a bundle with a single key-value pair {"data": ""}
+        assertThat(mListener.mFinalResult.size()).isEqualTo(1);
+        assertThat(mListener.mFinalResult.getString("data")).isEqualTo("");
+    }
+
+    @Test
+    public void invokeScript_allPrimitiveSupportedTypesForReturningFinalResult()
+            throws RemoteException, InterruptedException {
+        // Here we verify that all supported primitive types are present in the returned final
+        // result bundle are present.
+        String script =
+                "function finalize_all(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.integer = state.integer + 1\n"
+                        + "    result.number = state.number + 0.1\n"
+                        + "    result.boolean = not state.boolean\n"
+                        + "    result.string = state.string .. \"CADABRA\"\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putInt("integer", 1);
+        previousState.putDouble("number", 0.1);
+        previousState.putBoolean("boolean", false);
+        previousState.putString("string", "ABRA");
+
+        runScriptAndWaitForResponse(script, "finalize_all", mEmptyPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mListener.mFinalResult.size()).isEqualTo(4);
+        assertThat(mListener.mFinalResult.getLong("integer")).isEqualTo(2);
+        assertThat(mListener.mFinalResult.getDouble("number")).isEqualTo(0.2);
+        assertThat(mListener.mFinalResult.getBoolean("boolean")).isEqualTo(true);
+        assertThat(mListener.mFinalResult.getString("string")).isEqualTo("ABRACADABRA");
+    }
+
+    @Test
+    public void invokeScript_emptyFinalResultBundle() throws RemoteException, InterruptedException {
+        String script =
+                "function empty_final_result(data, state)\n"
+                        + "    result = {}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(
+                script, "empty_final_result", mEmptyPublishedData, mEmptyIterimResult);
+
+        // If a script returns empty table as the final result, we get an empty bundle.
+        assertThat(mListener.mFinalResult).isNotNull();
+        assertThat(mListener.mFinalResult.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void invokeScript_wrongNumberOfCallbackInputsInOnScriptFinished()
+            throws RemoteException, InterruptedException {
+        String script =
+                "function wrong_number_of_outputs_in_on_script_finished(data, state)\n"
+                        + "    result = {}\n"
+                        + "    extra = 1\n"
+                        + "    on_script_finished(result, extra)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(
+                script,
+                "wrong_number_of_outputs_in_on_script_finished",
+                mEmptyPublishedData,
+                mEmptyIterimResult);
+
+        // We expect to get an error here because we expect only 1 input parameter in
+        // on_script_finished.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage)
+                .isEqualTo(
+                        "on_script_finished can push only a single parameter from Lua - a Lua"
+                                + " table");
+    }
+
+    @Test
+    public void invokeScript_wrongNumberOfCallbackInputsInOnSuccess()
+            throws RemoteException, InterruptedException {
+        String script =
+                "function wrong_number_of_outputs_in_on_success(data, state)\n"
+                        + "    result = {}\n"
+                        + "    extra = 1\n"
+                        + "    on_success(result, extra)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(
+                script,
+                "wrong_number_of_outputs_in_on_success",
+                mEmptyPublishedData,
+                mEmptyIterimResult);
+
+        // We expect to get an error here because we expect only 1 input parameter in on_success.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage)
+                .isEqualTo("on_success can push only a single parameter from Lua - a Lua table");
+    }
+
+    @Test
+    public void invokeScript_wrongTypeInOnSuccess() throws RemoteException, InterruptedException {
+        String script =
+                "function wrong_type_in_on_success(data, state)\n"
+                        + "    result = 1\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(
+                script, "wrong_type_in_on_success", mEmptyPublishedData, mEmptyIterimResult);
+
+        // We expect to get an error here because the type of the input parameter for on_success
+        // must be a Lua table.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage)
+                .isEqualTo("on_success can push only a single parameter from Lua - a Lua table");
+    }
+
+    @Test
+    public void invokeScript_wrongTypeInOnScriptFinished()
+            throws RemoteException, InterruptedException {
+        String script =
+                "function wrong_type_in_on_script_finished(data, state)\n"
+                        + "    result = 1\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(
+                script,
+                "wrong_type_in_on_script_finished",
+                mEmptyPublishedData,
+                mEmptyIterimResult);
+
+        // We expect to get an error here because the type of the input parameter for
+        // on_script_finished must be a Lua table.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage)
+                .isEqualTo("on_success can push only a single parameter from Lua - a Lua table");
+    }
+
+    @Test
+    public void invokeScriptLargeInput_largePublishedData() throws Exception {
+        // Verifies that large input does not overwhelm Binder's buffer because pipes are used
+        // instead.
+        String script =
+                "function large_published_data(data, state)\n"
+                        + "    sum = 0\n"
+                        + "    for _, val in ipairs(data.array) do\n"
+                        + "        sum = sum + val\n"
+                        + "    end\n"
+                        + "    result = {total = sum}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+
+        ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+        ParcelFileDescriptor writeFd = fds[1];
+        ParcelFileDescriptor readFd = fds[0];
+
+        PersistableBundle bundle = new PersistableBundle();
+        int n = 1 << 20; // 1024 * 1024 values, roughly 1 Million.
+        long[] array8Mb = new long[n];
+        for (int i = 0; i < n; i++) {
+            array8Mb[i] = i;
+        }
+        bundle.putLongArray("array", array8Mb);
+        long expectedSum =
+                (array8Mb[0] + array8Mb[n - 1]) * n / 2; // sum of an arithmetic sequence.
+
+        mScriptExecutor.invokeScriptForLargeInput(
+                script, "large_published_data", readFd, mEmptyIterimResult, mListener);
+
+        readFd.close();
+        try (OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(writeFd)) {
+            bundle.writeToStream(outputStream);
+        }
+
+        boolean gotResponse = mListener.awaitResponse(SCRIPT_PROCESSING_TIMEOUT_SEC);
+
+        assertWithMessage("Failed to get the callback method called by the script on time")
+                .that(gotResponse)
+                .isTrue();
+        assertThat(mListener.mFinalResult.size()).isEqualTo(1);
+        assertThat(mListener.mFinalResult.getLong("total")).isEqualTo(expectedSum);
+    }
+
+    @Test
+    public void invokeScript_bothPublishedDataAndPreviousStateAreProvided()
+            throws RemoteException, InterruptedException {
+        // Verifies that both published data and previous state PersistableBundles
+        // are piped into script.
+        String script =
+                "function data_and_state(data, state)\n"
+                        + "    result = {answer = data.a .. data.b .. state.c .. state.d}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+
+        PersistableBundle publishedData = new PersistableBundle();
+        publishedData.putString("a", "A");
+        publishedData.putString("b", "B");
+
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putString("c", "C");
+        previousState.putString("d", "D");
+
+        runScriptAndWaitForResponse(script, "data_and_state", publishedData, previousState);
+
+        // Lua script combines both input published data and previous state into a single result.
+        assertThat(mListener.mFinalResult).isNotNull();
+        assertThat(mListener.mFinalResult.size()).isEqualTo(1);
+        assertThat(mListener.mFinalResult.getString("answer")).isEqualTo("ABCD");
+    }
+
+    @Test
+    public void invokeScript_outputIntAndLongAreTreatedAsLong()
+            throws RemoteException, InterruptedException {
+        // Verifies that we treat output both integer and long as long integer type although we
+        // distinguish between int and long in the script input.
+        String script =
+                "function int_and_long_are_output_long(data, state)\n"
+                        + "    result = {int = data.int, long = state.long}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+
+        PersistableBundle publishedData = new PersistableBundle();
+        publishedData.putInt("int", 100);
+
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putLong("long", 200);
+
+        runScriptAndWaitForResponse(
+                script, "int_and_long_are_output_long", publishedData, previousState);
+
+        // If a script returns empty table as the final result, we get an empty bundle.
+        assertThat(mListener.mFinalResult).isNotNull();
+        assertThat(mListener.mFinalResult.size()).isEqualTo(2);
+        // getInt should always return "empty" value (zero) because all integer types are treated
+        // as Java long.
+        assertThat(mListener.mFinalResult.getInt("int")).isEqualTo(0);
+        assertThat(mListener.mFinalResult.getInt("long")).isEqualTo(0);
+        // Instead all expected integer values are successfully retrieved using getLong method
+        // from the output bundle.
+        assertThat(mListener.mFinalResult.getLong("int")).isEqualTo(100);
+        assertThat(mListener.mFinalResult.getLong("long")).isEqualTo(200);
+    }
+
+    @Test
+    public void invokeScript_nonUTFCharactersDoNotCauseErrors()
+            throws RemoteException, InterruptedException {
+        // Tries to create an output string value that does not conform to Modified UTF-8.
+        // JNI gracefully handles it by parsing on the string as is.
+        String script =
+                "function non_utf_key_string(data, state)\n"
+                        + "    result = {answer = \"i\0np\200\200ut\"}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(
+                script, "non_utf_key_string", new PersistableBundle(), new PersistableBundle());
+
+        // The output will still have all characters, including those that do not conform to
+        // Modified UTF-8.
+        assertThat(mListener.mFinalResult).isNotNull();
+        assertThat(mListener.mFinalResult.size()).isEqualTo(1);
+        assertThat(mListener.mFinalResult.getString("answer")).isEqualTo("i\0np\200\200ut");
+    }
+
+    @Test
+    public void invokeScript_wrongFunctionNameProvided()
+            throws RemoteException, InterruptedException {
+        // Verifies that not specifying function name correctly is handled through error callback.
+        String script = "function correct_function(data, state)\n" + "end\n";
+
+        runScriptAndWaitForError(script, "wrong_function");
+
+        // Verify that the expected error is received.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_RUNTIME_ERROR);
+        assertThat(mListener.mMessage).contains("Wrong function name");
+    }
+
+    @Test
+    public void invokeScript_runtimeErrorDueToSyntax()
+            throws RemoteException, InterruptedException {
+        // Verifies that syntax errors during script loading are handled gracefully.
+        String script = "function wrong_syntax(data, state)\n" + "    x == 1\n" + "end\n";
+
+        runScriptAndWaitForError(script, "wrong_syntax");
+
+        // Verify that the expected error is received.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_RUNTIME_ERROR);
+        assertThat(mListener.mMessage).contains("Error encountered while loading the script");
+    }
+
+    @Test
+    public void invokeScript_runtimeErrorDueToUndefinedMethod()
+            throws RemoteException, InterruptedException {
+        // Verifies that runtime errors encountered during Lua script execution trigger an error
+        // returned via a callback.
+        String script =
+                "function runtime_error(data, state)\n" + "    on_problem(data, state)\n" + "end\n";
+
+        runScriptAndWaitForError(script, "runtime_error");
+
+        // Verify that the expected error is received.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_RUNTIME_ERROR);
+        assertThat(mListener.mMessage).contains("Error encountered while running the script");
+    }
+
+    @Test
+    public void invokeScript_returnedValuesOfUnsupportedTypesReturnError()
+            throws RemoteException, InterruptedException {
+        // Verifies that if we try to return a value of unsupported type, we get an error instead.
+        // In this case, the unsupported type is LUA_TFUNCTION type.
+        String script =
+                "function function_type(data, state)\n"
+                        + "    result = {fn = function_type}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(
+                script, "function_type", mEmptyPublishedData, mEmptyIterimResult);
+
+        // Verify that the expected error is received.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage)
+                .contains("has a Lua type=function, which is not supported yet");
+    }
+
+    @Test
+    public void invokeScript_returnedFloatingArraysNotSupported()
+            throws RemoteException, InterruptedException {
+        // Verifies that we do not support return values that contain floating number arrays.
+        String script =
+                "function floating_point_arrays(data, state)\n"
+                        + "    array = {}\n"
+                        + "    array[0] = 1.1\n"
+                        + "    array[1] = 1.2\n"
+                        + "    result = {data = array}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(
+                script, "floating_point_arrays", mEmptyPublishedData, mEmptyIterimResult);
+
+        // Verify that the expected error is received.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage)
+                .contains("a floating number array, which is not supported yet");
+    }
+
+    @Test
+    public void invokeScript_returnedBooleanArraysNotSupported()
+            throws RemoteException, InterruptedException {
+        // Verifies that we do not yet support return values that contain boolean arrays.
+        String script =
+                "function array_of_booleans(data, state)\n"
+                        + "    array = {}\n"
+                        + "    array[0] = false\n"
+                        + "    array[1] = true\n"
+                        + "    result = {data = array}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(
+                script, "array_of_booleans", mEmptyPublishedData, mEmptyIterimResult);
+
+        // Verify that the expected error is received.
+        assertThat(mListener.mErrorType)
+                .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mListener.mMessage)
+                .contains("is an array with values of type=boolean, which is not supported yet");
+    }
+
+    // Helper method to invoke the script and wait for it to complete and return a response.
+    private void runScriptAndWaitForResponse(
+            String script,
+            String function,
+            PersistableBundle publishedData,
+            PersistableBundle previousState)
+            throws RemoteException, InterruptedException {
+        mScriptExecutor.invokeScript(script, function, publishedData, previousState, mListener);
+        assertWithMessage("Failed to get the callback method called by the script on time")
+                .that(mListener.awaitResponse(SCRIPT_PROCESSING_TIMEOUT_SEC))
+                .isTrue();
+    }
+
+    private void runScriptAndWaitForError(String script, String function)
+            throws RemoteException, InterruptedException {
+        runScriptAndWaitForResponse(
+                script, function, new PersistableBundle(), new PersistableBundle());
+    }
+}
diff --git a/packages/ScriptExecutor/tests/nonsystemuser/Android.bp b/packages/ScriptExecutor/tests/nonsystemuser/Android.bp
new file mode 100644
index 0000000..cc95fe3
--- /dev/null
+++ b/packages/ScriptExecutor/tests/nonsystemuser/Android.bp
@@ -0,0 +1,41 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "ScriptExecutorNonSystemUserTests",
+
+    srcs: ["src/**/*.java"],
+
+    platform_apis: true,
+
+    certificate: "platform",
+
+    instrumentation_for: "ScriptExecutor",
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "junit",
+        "scriptexecutor-test-lib",
+        "truth-prebuilt",
+    ],
+
+    test_suites: ["general-tests"],
+}
diff --git a/packages/ScriptExecutor/tests/nonsystemuser/AndroidManifest.xml b/packages/ScriptExecutor/tests/nonsystemuser/AndroidManifest.xml
new file mode 100644
index 0000000..9e20a43
--- /dev/null
+++ b/packages/ScriptExecutor/tests/nonsystemuser/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.scriptexecutortest.nonsystemuser">
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+    <queries>
+        <package android:name="com.android.car.scriptexecutor" />
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.car.scriptexecutortest.nonsystemuser"
+                     android:label="Tests for ScriptExecutor"/>
+</manifest>
diff --git a/packages/ScriptExecutor/tests/nonsystemuser/src/com/android/car/scriptexecutortest/nonsystemuser/ScriptExecutorNonSystemUserTest.java b/packages/ScriptExecutor/tests/nonsystemuser/src/com/android/car/scriptexecutortest/nonsystemuser/ScriptExecutorNonSystemUserTest.java
new file mode 100644
index 0000000..6949e3e
--- /dev/null
+++ b/packages/ScriptExecutor/tests/nonsystemuser/src/com/android/car/scriptexecutortest/nonsystemuser/ScriptExecutorNonSystemUserTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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 com.android.car.scriptexecutortest.nonsystemuser;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.scriptexecutor.ScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public final class ScriptExecutorNonSystemUserTest {
+
+    private IScriptExecutor mScriptExecutor;
+    private ScriptExecutor mInstance;
+    private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+    private static final class ScriptExecutorListener extends IScriptExecutorListener.Stub {
+        public PersistableBundle mInterimResult;
+        public PersistableBundle mFinalResult;
+        public int mErrorType;
+        public String mMessage;
+        public String mStackTrace;
+        public final CountDownLatch mResponseLatch = new CountDownLatch(1);
+
+        @Override
+        public void onScriptFinished(PersistableBundle result) {
+            mFinalResult = result;
+            mResponseLatch.countDown();
+        }
+
+        @Override
+        public void onSuccess(PersistableBundle stateToPersist) {
+            mInterimResult = stateToPersist;
+            mResponseLatch.countDown();
+        }
+
+        @Override
+        public void onError(int errorType, String message, String stackTrace) {
+            mErrorType = errorType;
+            mMessage = message;
+            mStackTrace = stackTrace;
+            mResponseLatch.countDown();
+        }
+
+        private boolean awaitResponse(int waitTimeSec) throws InterruptedException {
+            return mResponseLatch.await(waitTimeSec, TimeUnit.SECONDS);
+        }
+    }
+
+    private final ScriptExecutorListener mListener = new ScriptExecutorListener();
+
+    private final PersistableBundle mPublishedData = new PersistableBundle();
+    private final PersistableBundle mSavedState = new PersistableBundle();
+
+    private final CountDownLatch mBindLatch = new CountDownLatch(1);
+
+    private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
+    private static final int SCRIPT_PROCESSING_TIMEOUT_SEC = 10;
+
+    private final ServiceConnection mScriptExecutorConnection =
+            new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName className, IBinder service) {
+                    mScriptExecutor = IScriptExecutor.Stub.asInterface(service);
+                    mBindLatch.countDown();
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName className) {
+                    assertWithMessage("Service unexpectedly disconnected").fail();
+                }
+            };
+
+    @Before
+    public void setUp() throws InterruptedException {
+        Intent intent = new Intent();
+        intent.setComponent(
+                new ComponentName(
+                        "com.android.car.scriptexecutor",
+                        "com.android.car.scriptexecutor.ScriptExecutor"));
+        mContext.bindServiceAsUser(
+                intent, mScriptExecutorConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+        if (!mBindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            assertWithMessage("Failed to bind to ScriptExecutor service").fail();
+        }
+    }
+
+    @Test
+    public void invokeScript_FromNonSystemUser_isNotProcessed() throws Exception {
+        String script =
+                "function hello(data, state)\n"
+                        + "    result = {hello=\"world\"}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        mScriptExecutor.invokeScript(script, "hello", mPublishedData, mSavedState, mListener);
+
+        assertThat(mListener.awaitResponse(SCRIPT_PROCESSING_TIMEOUT_SEC)).isFalse();
+    }
+
+    @Test
+    public void invokeScriptForLargeInput_FromNonSystemUser_isNotProcessed() throws Exception {
+        String script =
+                "function hello(data, state)\n"
+                        + "    result = {hello=\"world\"}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+        ParcelFileDescriptor writeFd = fds[1];
+        ParcelFileDescriptor readFd = fds[0];
+
+        mScriptExecutor.invokeScriptForLargeInput(
+                script, "large_published_data", readFd, mSavedState, mListener);
+
+        readFd.close();
+        writeFd.close();
+
+        assertThat(mListener.awaitResponse(SCRIPT_PROCESSING_TIMEOUT_SEC)).isFalse();
+    }
+}
diff --git a/packages/ScriptExecutor/tests/unit/Android.bp b/packages/ScriptExecutor/tests/unit/Android.bp
index 80ba55f..5f6213c 100644
--- a/packages/ScriptExecutor/tests/unit/Android.bp
+++ b/packages/ScriptExecutor/tests/unit/Android.bp
@@ -19,7 +19,7 @@
 }
 
 android_test {
-    name: "ScriptExecutorUnitTest",
+    name: "ScriptExecutorUnitTests",
 
     srcs: ["src/**/*.java"],
 
@@ -53,7 +53,7 @@
     ],
 
     srcs: [
-        "src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp",
+        "src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp",
     ],
 
     stl: "libc++_static",
diff --git a/packages/ScriptExecutor/tests/unit/AndroidManifest.xml b/packages/ScriptExecutor/tests/unit/AndroidManifest.xml
index f393408..9809a8a 100644
--- a/packages/ScriptExecutor/tests/unit/AndroidManifest.xml
+++ b/packages/ScriptExecutor/tests/unit/AndroidManifest.xml
@@ -16,7 +16,7 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.car.scriptexecutor_test">
+          package="com.android.car.scriptexecutortest.unit">
 
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
 
@@ -29,6 +29,6 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.car.scriptexecutor_test"
+                     android:targetPackage="com.android.car.scriptexecutortest.unit"
                      android:label="Tests for ScriptExecutor"/>
 </manifest>
diff --git a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java
deleted file mode 100644
index 1a4028a..0000000
--- a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java
+++ /dev/null
@@ -1,907 +0,0 @@
-/*
- * 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 com.android.car.scriptexecutor;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.fail;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.os.RemoteException;
-import android.os.UserHandle;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
-import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.OutputStream;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(JUnit4.class)
-public final class ScriptExecutorTest {
-
-    private IScriptExecutor mScriptExecutor;
-    private ScriptExecutor mInstance;
-    private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-
-
-    private static final class ScriptExecutorListener extends IScriptExecutorListener.Stub {
-        public PersistableBundle mSavedBundle;
-        public PersistableBundle mFinalResult;
-        public int mErrorType;
-        public String mMessage;
-        public String mStackTrace;
-        public final CountDownLatch mResponseLatch = new CountDownLatch(1);
-
-        @Override
-        public void onScriptFinished(PersistableBundle result) {
-            mFinalResult = result;
-            mResponseLatch.countDown();
-        }
-
-        @Override
-        public void onSuccess(PersistableBundle stateToPersist) {
-            mSavedBundle = stateToPersist;
-            mResponseLatch.countDown();
-        }
-
-        @Override
-        public void onError(int errorType, String message, String stackTrace) {
-            mErrorType = errorType;
-            mMessage = message;
-            mStackTrace = stackTrace;
-            mResponseLatch.countDown();
-        }
-    }
-
-    private final ScriptExecutorListener mFakeScriptExecutorListener =
-            new ScriptExecutorListener();
-
-    private final PersistableBundle mPublishedData = new PersistableBundle();
-    private final PersistableBundle mSavedState = new PersistableBundle();
-
-    private final CountDownLatch mBindLatch = new CountDownLatch(1);
-
-    private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
-    private static final int SCRIPT_PROCESSING_TIMEOUT_SEC = 10;
-
-
-    private final ServiceConnection mScriptExecutorConnection =
-            new ServiceConnection() {
-                @Override
-                public void onServiceConnected(ComponentName className, IBinder service) {
-                    mScriptExecutor = IScriptExecutor.Stub.asInterface(service);
-                    mBindLatch.countDown();
-                }
-
-                @Override
-                public void onServiceDisconnected(ComponentName className) {
-                    fail("Service unexpectedly disconnected");
-                }
-            };
-
-    // Helper method to invoke the script and wait for it to complete and return a response.
-    private void runScriptAndWaitForResponse(String script, String function,
-            PersistableBundle publishedData, PersistableBundle previousState)
-            throws RemoteException {
-        mScriptExecutor.invokeScript(script, function, publishedData, previousState,
-                mFakeScriptExecutorListener);
-        try {
-            if (!mFakeScriptExecutorListener.mResponseLatch.await(SCRIPT_PROCESSING_TIMEOUT_SEC,
-                    TimeUnit.SECONDS)) {
-                fail("Failed to get the callback method called by the script on time");
-            }
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-            fail(e.getMessage());
-        }
-    }
-
-    private void runScriptAndWaitForError(String script, String function) throws RemoteException {
-        runScriptAndWaitForResponse(script, function, new PersistableBundle(),
-                new PersistableBundle());
-    }
-
-    @Before
-    public void setUp() throws InterruptedException {
-        Intent intent = new Intent();
-        intent.setComponent(new ComponentName("com.android.car.scriptexecutor",
-                "com.android.car.scriptexecutor.ScriptExecutor"));
-        mContext.bindServiceAsUser(intent, mScriptExecutorConnection, Context.BIND_AUTO_CREATE,
-                UserHandle.SYSTEM);
-        if (!mBindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
-            fail("Failed to bind to ScriptExecutor service");
-        }
-    }
-
-    @Test
-    public void invokeScript_returnsResult() throws RemoteException {
-        String returnResultScript =
-                "function hello(data, state)\n"
-                        + "    result = {hello=\"world\"}\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(returnResultScript, "hello", mPublishedData, mSavedState);
-
-        // Expect to get back a bundle with a single string key: string value pair:
-        // {"hello": "world"}
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("hello")).isEqualTo(
-                "world");
-    }
-
-    @Test
-    public void invokeScript_allSupportedPrimitiveTypes() throws RemoteException {
-        String script =
-                "function knows(data, state)\n"
-                        + "    result = {string=\"hello\", boolean=true, integer=1, number=1.1}\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "knows", mPublishedData, mSavedState);
-
-        // Expect to get back a bundle with 4 keys, each corresponding to a distinct supported type.
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(4);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("string")).isEqualTo(
-                "hello");
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getBoolean("boolean")).isEqualTo(true);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLong("integer")).isEqualTo(1);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getDouble("number")).isEqualTo(1.1);
-    }
-
-    @Test
-    public void invokeScript_skipsUnsupportedNestedTables() throws RemoteException {
-        String script =
-                "function nested(data, state)\n"
-                        + "    result = {string=\"hello\", boolean=true, integer=1, number=1.1}\n"
-                        + "    result.nested_table = {x=0, y=0}\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "nested", mPublishedData, mSavedState);
-
-        // Verify that expected error is received.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).contains(
-                "nested tables are not supported");
-    }
-
-    @Test
-    public void invokeScript_emptyBundle() throws RemoteException {
-        String script =
-                "function empty(data, state)\n"
-                        + "    result = {}\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "empty", mPublishedData, mSavedState);
-
-        // If a script returns empty table as the result, we get an empty bundle.
-        assertThat(mFakeScriptExecutorListener.mSavedBundle).isNotNull();
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(0);
-    }
-
-    @Test
-    public void invokeScript_processPreviousStateAndReturnResult() throws RemoteException {
-        // Here we verify that the script actually processes provided state from a previous run
-        // and makes calculation based on that and returns the result.
-        String script =
-                "function update(data, state)\n"
-                        + "    result = {y = state.x+1}\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-        PersistableBundle previousState = new PersistableBundle();
-        previousState.putInt("x", 1);
-
-        runScriptAndWaitForResponse(script, "update", mPublishedData, previousState);
-
-        // Verify that y = 2, because y = x + 1 and x = 1.
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLong("y")).isEqualTo(2);
-    }
-
-    @Test
-    public void invokeScript_allSupportedPrimitiveTypesWorkRoundTripWithKeyNamesPreserved()
-            throws RemoteException {
-        // Here we verify that all supported primitive types in supplied previous state Bundle
-        // are interpreted by the script as expected.
-        String script =
-                "function update_all(data, state)\n"
-                        + "    result = {}\n"
-                        + "    result.integer = state.integer + 1\n"
-                        + "    result.number = state.number + 0.1\n"
-                        + "    result.boolean = not state.boolean\n"
-                        + "    result.string = state.string .. \"CADABRA\"\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-        PersistableBundle previousState = new PersistableBundle();
-        previousState.putInt("integer", 1);
-        previousState.putDouble("number", 0.1);
-        previousState.putBoolean("boolean", false);
-        previousState.putString("string", "ABRA");
-
-        runScriptAndWaitForResponse(script, "update_all", mPublishedData, previousState);
-
-        // Verify that keys are preserved but the values are modified as expected.
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(4);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLong("integer")).isEqualTo(2);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getDouble("number")).isEqualTo(0.2);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getBoolean("boolean")).isEqualTo(true);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("string")).isEqualTo(
-                "ABRACADABRA");
-    }
-
-    @Test
-    public void invokeScript_allSupportedArrayTypesWorkRoundTripWithKeyNamesPreserved()
-            throws RemoteException {
-        // Here we verify that all supported array types in supplied previous state Bundle are
-        // interpreted by the script as expected.
-        String script =
-                "function arrays(data, state)\n"
-                        + "    result = {}\n"
-                        + "    result.int_array = state.int_array\n"
-                        + "    result.long_array = state.long_array\n"
-                        + "    result.string_array = state.string_array\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-        PersistableBundle previousState = new PersistableBundle();
-        int[] int_array = new int[]{1, 2};
-        long[] int_array_in_long = new long[]{1, 2};
-        long[] long_array = new long[]{1, 2, 3};
-        String[] string_array = new String[]{"one", "two", "three"};
-        previousState.putIntArray("int_array", int_array);
-        previousState.putLongArray("long_array", long_array);
-        previousState.putStringArray("string_array", string_array);
-
-        runScriptAndWaitForResponse(script, "arrays", mPublishedData, previousState);
-
-        // Verify that keys are preserved but the values are modified as expected.
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(3);
-        // Lua has only one lua_Integer. Here Java long is used to represent it when data is
-        // transferred from Lua to CarTelemetryService.
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLongArray("int_array")).isEqualTo(
-                int_array_in_long);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLongArray("long_array")).isEqualTo(
-                long_array);
-        assertThat(
-                mFakeScriptExecutorListener.mSavedBundle.getStringArray("string_array")).isEqualTo(
-                string_array);
-    }
-
-    @Test
-    public void invokeScript_modifiesArray()
-            throws RemoteException {
-        // Verify that an array modified by a script is properly sent back by the callback.
-        String script =
-                "function modify_array(data, state)\n"
-                        + "    result = {}\n"
-                        + "    result.long_array = state.long_array\n"
-                        + "    result.long_array[2] = 100\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-        PersistableBundle previousState = new PersistableBundle();
-        long[] long_array = new long[]{1, 2, 3};
-        previousState.putLongArray("long_array", long_array);
-        long[] expected_array = new long[]{1, 100, 3};
-
-        runScriptAndWaitForResponse(script, "modify_array", mPublishedData, previousState);
-
-        // Verify that keys are preserved but the values are modified as expected.
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLongArray("long_array")).isEqualTo(
-                expected_array);
-    }
-
-    @Test
-    public void invokeScript_processesStringArray()
-            throws RemoteException {
-        // Verify that an array modified by a script is properly sent back by the callback.
-        String script =
-                "function process_string_array(data, state)\n"
-                        + "    result = {}\n"
-                        + "    result.answer = state.string_array[1] .. state.string_array[2]\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-        PersistableBundle previousState = new PersistableBundle();
-        String[] string_array = new String[]{"Hello ", "world!"};
-        previousState.putStringArray("string_array", string_array);
-
-        runScriptAndWaitForResponse(script, "process_string_array", mPublishedData, previousState);
-
-        // Verify that keys are preserved but the values are modified as expected.
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("answer")).isEqualTo(
-                "Hello world!");
-    }
-
-    @Test
-    public void invokeScript_arraysWithLengthAboveLimitCauseError()
-            throws RemoteException {
-        // Verifies that arrays pushed by Lua that have their size over the limit cause error.
-        String script =
-                "function size_limit(data, state)\n"
-                        + "    result = {}\n"
-                        + "    result.huge_array = {}\n"
-                        + "    for i=1, 10000 do\n"
-                        + "        result.huge_array[i]=i\n"
-                        + "    end\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "size_limit", mPublishedData, mSavedState);
-
-        // Verify that expected error is received.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
-                "Returned table huge_array exceeds maximum allowed size of 1000 "
-                        + "elements. This key-value cannot be unpacked successfully. This error "
-                        + "is unrecoverable.");
-    }
-
-    @Test
-    public void invokeScript_arrayContainingVaryingTypesCausesError()
-            throws RemoteException {
-        // Verifies that values in returned array must be the same integer type.
-        // For example string values in a Lua array are not allowed.
-        String script =
-                "function table_with_numbers_and_strings(data, state)\n"
-                        + "    result = {}\n"
-                        + "    result.mixed_array = state.long_array\n"
-                        + "    result.mixed_array[2] = 'a'\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-        PersistableBundle previousState = new PersistableBundle();
-        long[] long_array = new long[]{1, 2, 3};
-        previousState.putLongArray("long_array", long_array);
-
-        runScriptAndWaitForResponse(script, "table_with_numbers_and_strings", mPublishedData,
-                previousState);
-
-        // Verify that expected error is received.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).contains(
-                "Returned Lua arrays must have elements of the same type.");
-    }
-
-    @Test
-    public void invokeScript_InTablesWithBothKeysAndIndicesCopiesOnlyIndexedData()
-            throws RemoteException {
-        // Documents the current behavior that copies only indexed values in a Lua table that
-        // contains both keyed and indexed data.
-        String script =
-                "function keys_and_indices(data, state)\n"
-                        + "    result = {}\n"
-                        + "    result.mixed_array = state.long_array\n"
-                        + "    result.mixed_array['a'] = 130\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-        PersistableBundle previousState = new PersistableBundle();
-        long[] long_array = new long[]{1, 2, 3};
-        previousState.putLongArray("long_array", long_array);
-
-        runScriptAndWaitForResponse(script, "keys_and_indices", mPublishedData, previousState);
-
-        // Verify that keys are preserved but the values are modified as expected.
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLongArray("mixed_array")).isEqualTo(
-                long_array);
-    }
-
-    @Test
-    public void invokeScript_noLuaBufferOverflowForLargeInputArrays() throws RemoteException {
-        // Tests that arrays with length that exceed internal Lua buffer size of 20 elements
-        // do not cause buffer overflow and are handled properly.
-        String script =
-                "function large_input_array(data, state)\n"
-                        + "    sum = 0\n"
-                        + "    for _, val in ipairs(state.long_array) do\n"
-                        + "        sum = sum + val\n"
-                        + "    end\n"
-                        + "    result = {total = sum}\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-
-        PersistableBundle previousState = new PersistableBundle();
-        int n = 10000;
-        long[] longArray = new long[n];
-        for (int i = 0; i < n; i++) {
-            longArray[i] = i;
-        }
-        previousState.putLongArray("long_array", longArray);
-        long expected_sum =
-                (longArray[0] + longArray[n - 1]) * n / 2; // sum of an arithmetic sequence.
-
-        runScriptAndWaitForResponse(script, "large_input_array", mPublishedData, previousState);
-
-        // Verify that keys are preserved but the values are modified as expected.
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLong("total")).isEqualTo(
-                expected_sum);
-    }
-
-    @Test
-    public void invokeScript_scriptCallsOnError() throws RemoteException {
-        String script =
-                "function calls_on_error()\n"
-                        + "    if 1 ~= 2 then\n"
-                        + "        on_error(\"one is not equal to two\")\n"
-                        + "        return\n"
-                        + "    end\n"
-                        + "end\n";
-
-        runScriptAndWaitForError(script, "calls_on_error");
-
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo("one is not equal to two");
-    }
-
-    @Test
-    public void invokeScript_tooManyParametersInOnError() throws RemoteException {
-        String script =
-                "function too_many_params_in_on_error()\n"
-                        + "    if 1 ~= 2 then\n"
-                        + "        on_error(\"param1\", \"param2\")\n"
-                        + "        return\n"
-                        + "    end\n"
-                        + "end\n";
-
-        runScriptAndWaitForError(script, "too_many_params_in_on_error");
-
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
-                "on_error can push only a single string parameter from Lua");
-    }
-
-    @Test
-    public void invokeScript_onErrorOnlyAcceptsString() throws RemoteException {
-        String script =
-                "function only_string()\n"
-                        + "    if 1 ~= 2 then\n"
-                        + "        on_error(false)\n"
-                        + "        return\n"
-                        + "    end\n"
-                        + "end\n";
-
-        runScriptAndWaitForError(script, "only_string");
-
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
-                "on_error can push only a single string parameter from Lua");
-    }
-
-    @Test
-    public void invokeScript_returnsFinalResult() throws RemoteException {
-        String returnFinalResultScript =
-                "function script_finishes(data, state)\n"
-                        + "    result = {data = state.input + 1}\n"
-                        + "    on_script_finished(result)\n"
-                        + "end\n";
-        PersistableBundle previousState = new PersistableBundle();
-        previousState.putInt("input", 1);
-
-        runScriptAndWaitForResponse(returnFinalResultScript, "script_finishes", mPublishedData,
-                previousState);
-
-        // Expect to get back a bundle with a single key-value pair {"data": 2}
-        // because data = state.input + 1 as in the script body above.
-        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("data")).isEqualTo(2);
-    }
-
-    @Test
-    public void invokeScript_emptyStringValueIsValidValue() throws RemoteException {
-        // Verify that an empty string value is a valid value to be returned from a script.
-        String returnFinalResultScript =
-                "function empty_string(data, state)\n"
-                        + "    result = {data = \"\"}\n"
-                        + "    on_script_finished(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(returnFinalResultScript, "empty_string", mPublishedData,
-                new PersistableBundle());
-
-        // Expect to get back a bundle with a single key-value pair {"data": ""}
-        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getString("data")).isEqualTo("");
-    }
-
-    @Test
-    public void invokeScript_allPrimitiveSupportedTypesForReturningFinalResult()
-            throws RemoteException {
-        // Here we verify that all supported primitive types are present in the returned final
-        // result bundle are present.
-        String script =
-                "function finalize_all(data, state)\n"
-                        + "    result = {}\n"
-                        + "    result.integer = state.integer + 1\n"
-                        + "    result.number = state.number + 0.1\n"
-                        + "    result.boolean = not state.boolean\n"
-                        + "    result.string = state.string .. \"CADABRA\"\n"
-                        + "    on_script_finished(result)\n"
-                        + "end\n";
-        PersistableBundle previousState = new PersistableBundle();
-        previousState.putInt("integer", 1);
-        previousState.putDouble("number", 0.1);
-        previousState.putBoolean("boolean", false);
-        previousState.putString("string", "ABRA");
-
-        runScriptAndWaitForResponse(script, "finalize_all", mPublishedData, previousState);
-
-        // Verify that keys are preserved but the values are modified as expected.
-        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(4);
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("integer")).isEqualTo(2);
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getDouble("number")).isEqualTo(0.2);
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getBoolean("boolean")).isEqualTo(true);
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getString("string")).isEqualTo(
-                "ABRACADABRA");
-    }
-
-    @Test
-    public void invokeScript_emptyFinalResultBundle() throws RemoteException {
-        String script =
-                "function empty_final_result(data, state)\n"
-                        + "    result = {}\n"
-                        + "    on_script_finished(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "empty_final_result", mPublishedData, mSavedState);
-
-        // If a script returns empty table as the final result, we get an empty bundle.
-        assertThat(mFakeScriptExecutorListener.mFinalResult).isNotNull();
-        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(0);
-    }
-
-    @Test
-    public void invokeScript_wrongNumberOfCallbackInputsInOnScriptFinished()
-            throws RemoteException {
-        String script =
-                "function wrong_number_of_outputs_in_on_script_finished(data, state)\n"
-                        + "    result = {}\n"
-                        + "    extra = 1\n"
-                        + "    on_script_finished(result, extra)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "wrong_number_of_outputs_in_on_script_finished",
-                mPublishedData, mSavedState);
-
-        // We expect to get an error here because we expect only 1 input parameter in
-        // on_script_finished.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
-                "on_script_finished can push only a single parameter from Lua - a Lua table");
-    }
-
-    @Test
-    public void invokeScript_wrongNumberOfCallbackInputsInOnSuccess() throws RemoteException {
-        String script =
-                "function wrong_number_of_outputs_in_on_success(data, state)\n"
-                        + "    result = {}\n"
-                        + "    extra = 1\n"
-                        + "    on_success(result, extra)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "wrong_number_of_outputs_in_on_success",
-                mPublishedData, mSavedState);
-
-        // We expect to get an error here because we expect only 1 input parameter in on_success.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
-                "on_success can push only a single parameter from Lua - a Lua table");
-    }
-
-    @Test
-    public void invokeScript_wrongTypeInOnSuccess() throws RemoteException {
-        String script =
-                "function wrong_type_in_on_success(data, state)\n"
-                        + "    result = 1\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "wrong_type_in_on_success",
-                mPublishedData, mSavedState);
-
-        // We expect to get an error here because the type of the input parameter for on_success
-        // must be a Lua table.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
-                "on_success can push only a single parameter from Lua - a Lua table");
-    }
-
-    @Test
-    public void invokeScript_wrongTypeInOnScriptFinished() throws RemoteException {
-        String script =
-                "function wrong_type_in_on_script_finished(data, state)\n"
-                        + "    result = 1\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "wrong_type_in_on_script_finished",
-                mPublishedData, mSavedState);
-
-        // We expect to get an error here because the type of the input parameter for
-        // on_script_finished must be a Lua table.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
-                "on_success can push only a single parameter from Lua - a Lua table");
-    }
-
-    @Test
-    public void invokeScriptLargeInput_largePublishedData() throws Exception {
-        // Verifies that large input does not overwhelm Binder's buffer because pipes are used
-        // instead.
-        String script =
-                "function large_published_data(data, state)\n"
-                        + "    sum = 0\n"
-                        + "    for _, val in ipairs(data.array) do\n"
-                        + "        sum = sum + val\n"
-                        + "    end\n"
-                        + "    result = {total = sum}\n"
-                        + "    on_script_finished(result)\n"
-                        + "end\n";
-
-        ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
-        ParcelFileDescriptor writeFd = fds[1];
-        ParcelFileDescriptor readFd = fds[0];
-
-        PersistableBundle bundle = new PersistableBundle();
-        int n = 1 << 20; // 1024 * 1024 values, roughly 1 Million.
-        long[] array8Mb = new long[n];
-        for (int i = 0; i < n; i++) {
-            array8Mb[i] = i;
-        }
-        bundle.putLongArray("array", array8Mb);
-        long expectedSum =
-                (array8Mb[0] + array8Mb[n - 1]) * n / 2; // sum of an arithmetic sequence.
-
-        mScriptExecutor.invokeScriptForLargeInput(script, "large_published_data", readFd,
-                mSavedState,
-                mFakeScriptExecutorListener);
-
-        readFd.close();
-        try (OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(writeFd)) {
-            bundle.writeToStream(outputStream);
-        }
-
-        boolean gotResponse = mFakeScriptExecutorListener.mResponseLatch.await(
-                SCRIPT_PROCESSING_TIMEOUT_SEC,
-                TimeUnit.SECONDS);
-
-        assertWithMessage("Failed to get the callback method called by the script on time")
-                .that(gotResponse).isTrue();
-        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("total"))
-                .isEqualTo(expectedSum);
-    }
-
-    @Test
-    public void invokeScript_bothPublishedDataAndPreviousStateAreProvided() throws RemoteException {
-        // Verifies that both published data and previous state PersistableBundles
-        // are piped into script.
-        String script =
-                "function data_and_state(data, state)\n"
-                        + "    result = {answer = data.a .. data.b .. state.c .. state.d}\n"
-                        + "    on_script_finished(result)\n"
-                        + "end\n";
-
-        PersistableBundle publishedData = new PersistableBundle();
-        publishedData.putString("a", "A");
-        publishedData.putString("b", "B");
-
-        PersistableBundle previousState = new PersistableBundle();
-        previousState.putString("c", "C");
-        previousState.putString("d", "D");
-
-        runScriptAndWaitForResponse(script, "data_and_state", publishedData, previousState);
-
-        // Lua script combines both input published data and previous state into a single result.
-        assertThat(mFakeScriptExecutorListener.mFinalResult).isNotNull();
-        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getString("answer")).isEqualTo(
-                "ABCD");
-    }
-
-    @Test
-    public void invokeScript_outputIntAndLongAreTreatedAsLong() throws RemoteException {
-        // Verifies that we treat output both integer and long as long integer type although we
-        // distinguish between int and long in the script input.
-        String script =
-                "function int_and_long_are_output_long(data, state)\n"
-                        + "    result = {int = data.int, long = state.long}\n"
-                        + "    on_script_finished(result)\n"
-                        + "end\n";
-
-        PersistableBundle publishedData = new PersistableBundle();
-        publishedData.putInt("int", 100);
-
-        PersistableBundle previousState = new PersistableBundle();
-        previousState.putLong("long", 200);
-
-        runScriptAndWaitForResponse(script, "int_and_long_are_output_long",
-                publishedData, previousState);
-
-        // If a script returns empty table as the final result, we get an empty bundle.
-        assertThat(mFakeScriptExecutorListener.mFinalResult).isNotNull();
-        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(2);
-        // getInt should always return "empty" value (zero) because all integer types are treated
-        // as Java long.
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getInt("int")).isEqualTo(0);
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getInt("long")).isEqualTo(0);
-        // Instead all expected integer values are successfully retrieved using getLong method
-        // from the output bundle.
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("int")).isEqualTo(100);
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("long")).isEqualTo(200);
-    }
-
-    @Test
-    public void invokeScript_nonUTFCharactersDoNotCauseErrors() throws RemoteException {
-        // Tries to create an output string value that does not conform to Modified UTF-8.
-        // JNI gracefully handles it by parsing on the string as is.
-        String script =
-                "function non_utf_key_string(data, state)\n"
-                        + "    result = {answer = \"i\0np\200\200ut\"}\n"
-                        + "    on_script_finished(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "non_utf_key_string", new PersistableBundle(),
-                new PersistableBundle());
-
-        // The output will still have all characters, including those that do not conform to
-        // Modified UTF-8.
-        assertThat(mFakeScriptExecutorListener.mFinalResult).isNotNull();
-        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
-        assertThat(mFakeScriptExecutorListener.mFinalResult.getString("answer")).isEqualTo(
-                "i\0np\200\200ut");
-    }
-
-    @Test
-    public void invokeScript_wrongFunctionNameProvided() throws RemoteException {
-        // Verifies that not specifying function name correctly is handled through error callback.
-        String script =
-                "function correct_function(data, state)\n"
-                        + "end\n";
-
-        runScriptAndWaitForError(script, "wrong_function");
-
-        // Verify that the expected error is received.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_RUNTIME_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).contains(
-                "Wrong function name");
-    }
-
-    @Test
-    public void invokeScript_runtimeErrorDueToSyntax() throws RemoteException {
-        // Verifies that syntax errors during script loading are handled gracefully.
-        String script =
-                "function wrong_syntax(data, state)\n"
-                        + "    x == 1\n"
-                        + "end\n";
-
-        runScriptAndWaitForError(script, "wrong_syntax");
-
-        // Verify that the expected error is received.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_RUNTIME_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).contains(
-                "Error encountered while loading the script");
-    }
-
-    @Test
-    public void invokeScript_runtimeErrorDueToUndefinedMethod() throws RemoteException {
-        // Verifies that runtime errors encountered during Lua script execution trigger an error
-        // returned via a callback.
-        String script =
-                "function runtime_error(data, state)\n"
-                        + "    on_problem(data, state)\n"
-                        + "end\n";
-
-        runScriptAndWaitForError(script, "runtime_error");
-
-        // Verify that the expected error is received.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_RUNTIME_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).contains(
-                "Error encountered while running the script");
-    }
-
-    @Test
-    public void invokeScript_returnedValuesOfUnsupportedTypesReturnError() throws RemoteException {
-        // Verifies that if we try to return a value of unsupported type, we get an error instead.
-        // In this case, the unsupported type is LUA_TFUNCTION type.
-        String script =
-                "function function_type(data, state)\n"
-                        + "    result = {fn = function_type}\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "function_type", mPublishedData, mSavedState);
-
-        // Verify that the expected error is received.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).contains(
-                "has a Lua type=function, which is not supported yet");
-    }
-
-    @Test
-    public void invokeScript_returnedFloatingArraysNotSupported() throws RemoteException {
-        // Verifies that we do not support return values that contain floating number arrays.
-        String script =
-                "function floating_point_arrays(data, state)\n"
-                        + "    array = {}\n"
-                        + "    array[0] = 1.1\n"
-                        + "    array[1] = 1.2\n"
-                        + "    result = {data = array}\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "floating_point_arrays", mPublishedData, mSavedState);
-
-        // Verify that the expected error is received.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).contains(
-                "a floating number array, which is not supported yet");
-    }
-
-    @Test
-    public void invokeScript_returnedBooleanArraysNotSupported() throws RemoteException {
-        // Verifies that we do not yet support return values that contain boolean arrays.
-        String script =
-                "function array_of_booleans(data, state)\n"
-                        + "    array = {}\n"
-                        + "    array[0] = false\n"
-                        + "    array[1] = true\n"
-                        + "    result = {data = array}\n"
-                        + "    on_success(result)\n"
-                        + "end\n";
-
-        runScriptAndWaitForResponse(script, "array_of_booleans", mPublishedData, mSavedState);
-
-        // Verify that the expected error is received.
-        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
-                IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
-        assertThat(mFakeScriptExecutorListener.mMessage).contains(
-                "is an array with values of type=boolean, which is not supported yet");
-    }
-}
-
diff --git a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTest.java b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTest.java
similarity index 98%
rename from packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTest.java
rename to packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTest.java
index 3292009..7cf8728 100644
--- a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTest.java
+++ b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.scriptexecutor;
+package com.android.car.scriptexecutortest.unit;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp
similarity index 68%
rename from packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp
rename to packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp
index a205b32..57fea30 100644
--- a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp
+++ b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp
@@ -24,14 +24,16 @@
 namespace com {
 namespace android {
 namespace car {
-namespace scriptexecutor {
+namespace scriptexecutortest {
+namespace unit {
 namespace {
 
 template <typename T>
 bool hasIntegerArray(JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, T rawInputArray,
                      const int arrayLength) {
     const char* rawKey = env->GetStringUTFChars(key, nullptr);
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+    scriptexecutor::LuaEngine* engine =
+            reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
     // Assumes the table is on top of the stack.
     auto* luaState = engine->getLuaState();
     lua_pushstring(luaState, rawKey);
@@ -69,34 +71,43 @@
 
 #include "lua.h"
 
-JNIEXPORT jlong JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeCreateLuaEngine(
-        JNIEnv* env, jobject object) {
+JNIEXPORT jlong JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeCreateLuaEngine(JNIEnv* env,
+                                                                                jobject object) {
     // Cast first to intptr_t to ensure int can hold the pointer without loss.
-    return static_cast<jlong>(reinterpret_cast<intptr_t>(new LuaEngine()));
-}
-
-JNIEXPORT void JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeDestroyLuaEngine(
-        JNIEnv* env, jobject object, jlong luaEnginePtr) {
-    delete reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+    return static_cast<jlong>(reinterpret_cast<intptr_t>(new scriptexecutor::LuaEngine()));
 }
 
 JNIEXPORT void JNICALL
-Java_com_android_car_scriptexecutor_JniUtilsTest_nativePushBundleToLuaTableCaller(
-        JNIEnv* env, jobject object, jlong luaEnginePtr, jobject bundle) {
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
-    pushBundleToLuaTable(env, engine->getLuaState(), bundle);
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeDestroyLuaEngine(
+        JNIEnv* env, jobject object, jlong luaEnginePtr) {
+    delete reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
 }
 
-JNIEXPORT jint JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeGetObjectSize(
-        JNIEnv* env, jobject object, jlong luaEnginePtr, jint index) {
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+JNIEXPORT void JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativePushBundleToLuaTableCaller(
+        JNIEnv* env, jobject object, jlong luaEnginePtr, jobject bundle) {
+    scriptexecutor::LuaEngine* engine =
+            reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+    scriptexecutor::pushBundleToLuaTable(env, engine->getLuaState(), bundle);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeGetObjectSize(JNIEnv* env,
+                                                                              jobject object,
+                                                                              jlong luaEnginePtr,
+                                                                              jint index) {
+    scriptexecutor::LuaEngine* engine =
+            reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
     return lua_rawlen(engine->getLuaState(), static_cast<int>(index));
 }
 
-JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasBooleanValue(
+JNIEXPORT bool JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasBooleanValue(
         JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jboolean value) {
     const char* rawKey = env->GetStringUTFChars(key, nullptr);
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+    scriptexecutor::LuaEngine* engine =
+            reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
     auto* luaState = engine->getLuaState();
     lua_pushstring(luaState, rawKey);
     env->ReleaseStringUTFChars(key, rawKey);
@@ -110,10 +121,11 @@
     return result;
 }
 
-JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasIntValue(
+JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasIntValue(
         JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jint value) {
     const char* rawKey = env->GetStringUTFChars(key, nullptr);
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+    scriptexecutor::LuaEngine* engine =
+            reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
     // Assumes the table is on top of the stack.
     auto* luaState = engine->getLuaState();
     lua_pushstring(luaState, rawKey);
@@ -128,10 +140,12 @@
     return result;
 }
 
-JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasDoubleValue(
+JNIEXPORT bool JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasDoubleValue(
         JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jdouble value) {
     const char* rawKey = env->GetStringUTFChars(key, nullptr);
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+    scriptexecutor::LuaEngine* engine =
+            reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
     // Assumes the table is on top of the stack.
     auto* luaState = engine->getLuaState();
     lua_pushstring(luaState, rawKey);
@@ -146,10 +160,12 @@
     return result;
 }
 
-JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasStringValue(
+JNIEXPORT bool JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasStringValue(
         JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jstring value) {
     const char* rawKey = env->GetStringUTFChars(key, nullptr);
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+    scriptexecutor::LuaEngine* engine =
+            reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
     // Assumes the table is on top of the stack.
     auto* luaState = engine->getLuaState();
     lua_pushstring(luaState, rawKey);
@@ -168,7 +184,8 @@
     return result;
 }
 
-JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasIntArrayValue(
+JNIEXPORT bool JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasIntArrayValue(
         JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jintArray value) {
     jint* rawInputArray = env->GetIntArrayElements(value, nullptr);
     const auto kInputLength = env->GetArrayLength(value);
@@ -177,7 +194,8 @@
     return result;
 }
 
-JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasLongArrayValue(
+JNIEXPORT bool JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasLongArrayValue(
         JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jlongArray value) {
     jlong* rawInputArray = env->GetLongArrayElements(value, nullptr);
     const auto kInputLength = env->GetArrayLength(value);
@@ -189,7 +207,8 @@
 }  //  extern "C"
 
 }  // namespace
-}  // namespace scriptexecutor
+}  // namespace unit
+}  // namespace scriptexecutortest
 }  // namespace car
 }  // namespace android
 }  // namespace com