Avoid killing the sandbox in hostside tests

After running runDeviceTests() in a hostside test, the app
instrumentation and sandbox are no longer alive, so there is no sandbox
to be killed. A new permission and a test API stopSdkSandbox is added to
allow an app to kill its own sandbox. Hostside tests that were
previously trying to kill a sandbox are rewritten.

Additionally, register a sandbox death recipient for cleaning up the
state of SdkSandboxManagerService each time onServiceConnected() is
called as the system can restart a new sandbox after the death of an old
one.

Bug: 239690000
Bug: 239553253
Bug: 240164606
Test: atest
SdkSandboxManagerTest#testReloadingSdkAfterSandboxDeathIsSuccessful
Test: atest SdkSandboxManagerServiceUnitTest
Change-Id: I74b625767da8f49f1a07eba438ec54e14a2536dd

Change-Id: If5b90e8285882902f8d56df0c43b3b1a6b36de6d
diff --git a/sdksandbox/SdkSandbox/AndroidManifest.xml b/sdksandbox/SdkSandbox/AndroidManifest.xml
index 3e65cbc..7d09b20 100644
--- a/sdksandbox/SdkSandbox/AndroidManifest.xml
+++ b/sdksandbox/SdkSandbox/AndroidManifest.xml
@@ -26,6 +26,10 @@
         android:minSdkVersion="33"
         android:targetSdkVersion="33" />
 
+    <!-- @hide @TestApi -->
+    <permission android:name="com.android.app.sdksandbox.permission.STOP_SDK_SANDBOX"
+                android:protectionLevel="normal" />
+
     <!-- Allows SdkSandbox to start activities. -->
     <!-- TODO(b/209599396): temporary workaround, remove once proper solution is implemented. -->
     <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
diff --git a/sdksandbox/TEST_MAPPING b/sdksandbox/TEST_MAPPING
index cb73de2..33a00ff 100644
--- a/sdksandbox/TEST_MAPPING
+++ b/sdksandbox/TEST_MAPPING
@@ -17,9 +17,6 @@
       "name": "SdkSandboxManagerTests[com.google.android.adservices.apex]"
     },
     {
-      "name": "SdkSandboxHostTest[com.google.android.adservices.apex]"
-    },
-    {
       "name": "SdkSandboxLifecycleHostTest[com.google.android.adservices.apex]"
     },
     {
@@ -60,9 +57,6 @@
       "name": "SdkSandboxManagerTests"
     },
     {
-      "name": "SdkSandboxHostTest"
-    },
-    {
       "name": "SdkSandboxLifecycleHostTest"
     },
     {
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/ISdkSandboxManager.aidl b/sdksandbox/framework/java/android/app/sdksandbox/ISdkSandboxManager.aidl
index 12bb23a..30fdf05 100644
--- a/sdksandbox/framework/java/android/app/sdksandbox/ISdkSandboxManager.aidl
+++ b/sdksandbox/framework/java/android/app/sdksandbox/ISdkSandboxManager.aidl
@@ -32,4 +32,5 @@
     void sendData(in String callingPackageName, in String sdkName, in Bundle data, in ISendDataCallback callback);
     List<SharedLibraryInfo> getLoadedSdkLibrariesInfo(in String callingPackageName);
     void syncDataFromClient(in String callingPackageName, in Bundle data);
+    void stopSdkSandbox(in String callingPackageName);
 }
diff --git a/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManager.java b/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManager.java
index a71bab9..e31ebd2 100644
--- a/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManager.java
+++ b/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManager.java
@@ -21,7 +21,9 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.SharedLibraryInfo;
 import android.os.Binder;
@@ -163,6 +165,21 @@
     }
 
     /**
+     * Stop the SDK sandbox process corresponding to the app.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission("com.android.app.sdksandbox.permission.STOP_SDK_SANDBOX")
+    public void stopSdkSandbox() {
+        try {
+            mService.stopSdkSandbox(mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Load SDK in a SDK sandbox java process.
      *
      * <p>It loads SDK library with {@code sdkName} to a sandbox process asynchronously, caller
diff --git a/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java b/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java
index 3bcfd66..065ca21 100644
--- a/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java
+++ b/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java
@@ -86,6 +86,8 @@
     private static final String TAG = "SdkSandboxManager";
     private static final String PROPERTY_SDK_PROVIDER_CLASS_NAME =
             "android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME";
+    private static final String STOP_SDK_SANDBOX_PERMISSION =
+            "com.android.app.sdksandbox.permission.STOP_SDK_SANDBOX";
 
     private final Context mContext;
     private final SdkTokenManager mSdkTokenManager = new SdkTokenManager();
@@ -420,24 +422,24 @@
         }
     }
 
-    static class SandboxServiceConnection implements ServiceConnection {
+    interface SandboxBindingCallback {
+        void onBindingSuccessful(ISdkSandboxService service);
 
-        interface Callback {
-            void onBindingSuccessful(ISdkSandboxService service);
+        void onBindingFailed();
+    }
 
-            void onBindingFailed();
-        }
+    class SandboxServiceConnection implements ServiceConnection {
 
         private final SdkSandboxServiceProvider mServiceProvider;
         private final CallingInfo mCallingInfo;
         private boolean mServiceBound = false;
 
-        private final Callback mCallback;
+        private final SandboxBindingCallback mCallback;
 
         SandboxServiceConnection(
                 SdkSandboxServiceProvider serviceProvider,
                 CallingInfo callingInfo,
-                Callback callback) {
+                SandboxBindingCallback callback) {
             mServiceProvider = serviceProvider;
             mCallingInfo = callingInfo;
             mCallback = callback;
@@ -454,6 +456,13 @@
                             mCallingInfo.getPackageName(), mCallingInfo.getUid()));
             mServiceProvider.setBoundServiceForApp(mCallingInfo, mService);
 
+            try {
+                service.linkToDeath(() -> removeAllSdkTokensAndLinks(mCallingInfo), 0);
+            } catch (RemoteException re) {
+                // Sandbox had already died, cleanup sdk tokens and links.
+                removeAllSdkTokensAndLinks(mCallingInfo);
+            }
+
             if (!mServiceBound) {
                 mCallback.onBindingSuccessful(mService);
                 mServiceBound = true;
@@ -481,7 +490,7 @@
         }
     }
 
-    void startSdkSandbox(CallingInfo callingInfo, SandboxServiceConnection.Callback callback) {
+    void startSdkSandbox(CallingInfo callingInfo, SandboxBindingCallback callback) {
         mServiceProvider.bindService(
                 callingInfo, new SandboxServiceConnection(mServiceProvider, callingInfo, callback));
     }
@@ -497,17 +506,9 @@
 
         startSdkSandbox(
                 callingInfo,
-                new SandboxServiceConnection.Callback() {
+                new SandboxBindingCallback() {
                     @Override
                     public void onBindingSuccessful(ISdkSandboxService service) {
-                        try {
-                            service.asBinder()
-                                    .linkToDeath(() -> removeAllSdkTokensAndLinks(callingInfo), 0);
-                        } catch (RemoteException re) {
-                            // Sandbox had already died, cleanup sdk tokens and links.
-                            removeAllSdkTokensAndLinks(callingInfo);
-                        }
-
                         loadSdkForService(callingInfo, sdkToken, info, params, link, service);
                     }
 
@@ -521,7 +522,29 @@
                 });
     }
 
+    @Override
+    public void stopSdkSandbox(String callingPackageName) {
+        final int callingUid = Binder.getCallingUid();
+        final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
+        enforceCallingPackageBelongsToUid(callingInfo);
+
+        mContext.enforceCallingPermission(
+                STOP_SDK_SANDBOX_PERMISSION,
+                callingPackageName + " does not have permission to stop their sandbox");
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            stopSdkSandboxService(callingInfo, "App requesting sandbox kill");
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     void stopSdkSandboxService(CallingInfo callingInfo, String reason) {
+        if (!isSdkSandboxServiceRunning(callingInfo)) {
+            return;
+        }
+
         mServiceProvider.unbindService(callingInfo);
         final int sdkSandboxUid = Process.toSdkSandboxUid(callingInfo.getUid());
         Log.i(TAG, "Killing sdk sandbox/s with uid " + sdkSandboxUid);
@@ -589,6 +612,7 @@
         synchronized (mLock) {
             ISdkSandboxService boundSandbox = mServiceProvider.getBoundServiceForApp(callingInfo);
             boolean shouldStopSandbox = true;
+            ArrayList<IBinder> linksToDelete = new ArrayList<>();
             for (int i = 0; i < mAppAndRemoteSdkLinks.size(); i++) {
                 AppAndRemoteSdkLink link = mAppAndRemoteSdkLinks.valueAt(i);
                 if (link.mCallingInfo.equals(callingInfo)) {
@@ -602,14 +626,19 @@
                                 Log.w(TAG, "Failed to unload SDK: ", e);
                             }
                         }
-                        mSdkTokenManager.destroy(sdkToken);
-                        mAppAndRemoteSdkLinks.remove(sdkToken);
+                        linksToDelete.add(sdkToken);
                     } else {
                         shouldStopSandbox = false;
                     }
                 }
             }
 
+            for (int i = 0; i < linksToDelete.size(); i++) {
+                IBinder sdkToken = linksToDelete.get(i);
+                mSdkTokenManager.destroy(sdkToken);
+                mAppAndRemoteSdkLinks.remove(sdkToken);
+            }
+
             return shouldStopSandbox;
         }
     }
diff --git a/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxShellCommand.java b/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxShellCommand.java
index d2e27d5..83c5bf9 100644
--- a/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxShellCommand.java
+++ b/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxShellCommand.java
@@ -120,7 +120,7 @@
 
     /** Callback for binding sandbox. Provides blocking interface {@link #isSuccessful()}. */
     private class LatchSandboxServiceConnectionCallback
-            implements SdkSandboxManagerService.SandboxServiceConnection.Callback {
+            implements SdkSandboxManagerService.SandboxBindingCallback {
 
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private boolean mSuccess = false;
diff --git a/sdksandbox/tests/cts/endtoendtests/Android.bp b/sdksandbox/tests/cts/endtoendtests/Android.bp
index 447d1ee..1e00ec6 100644
--- a/sdksandbox/tests/cts/endtoendtests/Android.bp
+++ b/sdksandbox/tests/cts/endtoendtests/Android.bp
@@ -34,7 +34,7 @@
     data: [
       ":LoadSdkSuccessfullySdkProvider",
       ":GetLoadedSdkLibInfoSuccessfully",
-      ":RetryLoadSameSdkShouldFailSdkProvider",
+      ":LoadSdkSuccessfullySdkProviderTwo",
       ":LoadSdkWithInternalErrorSdkProvider",
       ":RequestSurfacePackageSuccessfullySdkProvider",
       ":RequestSurfacePackageWithInternalErrorSdkProvider",
diff --git a/sdksandbox/tests/cts/endtoendtests/AndroidManifest.xml b/sdksandbox/tests/cts/endtoendtests/AndroidManifest.xml
index 949e465..f6c2920 100644
--- a/sdksandbox/tests/cts/endtoendtests/AndroidManifest.xml
+++ b/sdksandbox/tests/cts/endtoendtests/AndroidManifest.xml
@@ -20,6 +20,7 @@
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="com.android.app.sdksandbox.permission.STOP_SDK_SANDBOX" />
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
         <uses-sdk-library android:name="com.android.loadSdkSuccessfullySdkProvider"
@@ -28,7 +29,7 @@
         <uses-sdk-library android:name="com.android.getLoadedSdkLibInfoSuccessfully"
                           android:versionMajor="1"
                           android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
-        <uses-sdk-library android:name="com.android.retryLoadSameSdkShouldFailSdkProvider"
+        <uses-sdk-library android:name="com.android.loadSdkSuccessfullySdkProviderTwo"
                           android:versionMajor="1"
                           android:certDigest="0B:44:2D:88:FA:A7:B3:AD:23:8D:DE:29:8A:A1:9B:D5:62:03:92:0B:BF:D8:D3:EB:C8:99:33:2C:8E:E1:15:99" />
         <uses-sdk-library android:name="com.android.loadSdkWithInternalErrorSdkProvider"
diff --git a/sdksandbox/tests/cts/endtoendtests/AndroidTest.xml b/sdksandbox/tests/cts/endtoendtests/AndroidTest.xml
index d2e805d..24e83c8 100644
--- a/sdksandbox/tests/cts/endtoendtests/AndroidTest.xml
+++ b/sdksandbox/tests/cts/endtoendtests/AndroidTest.xml
@@ -24,7 +24,7 @@
         <option name="cleanup-apks" value="true"/>
         <option name="test-file-name" value="LoadSdkSuccessfullySdkProvider.apk"/>
         <option name="test-file-name" value="GetLoadedSdkLibInfoSuccessfully.apk"/>
-        <option name="test-file-name" value="RetryLoadSameSdkShouldFailSdkProvider.apk"/>
+        <option name="test-file-name" value="LoadSdkSuccessfullySdkProviderTwo.apk"/>
         <option name="test-file-name" value="LoadSdkWithInternalErrorSdkProvider.apk"/>
         <option name="test-file-name" value="RequestSurfacePackageSuccessfullySdkProvider.apk"/>
         <option name="test-file-name" value="RequestSurfacePackageWithInternalErrorSdkProvider.apk"/>
diff --git a/sdksandbox/tests/cts/endtoendtests/providers/retryLoadSameSdkShouldFail/Android.bp b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/Android.bp
similarity index 94%
rename from sdksandbox/tests/cts/endtoendtests/providers/retryLoadSameSdkShouldFail/Android.bp
rename to sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/Android.bp
index 6c16bb6..95b2785 100644
--- a/sdksandbox/tests/cts/endtoendtests/providers/retryLoadSameSdkShouldFail/Android.bp
+++ b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/Android.bp
@@ -17,7 +17,7 @@
 }
 
 android_test_helper_app {
-    name: "RetryLoadSameSdkShouldFailSdkProvider",
+    name: "LoadSdkSuccessfullySdkProviderTwo",
     defaults: ["platform_app_defaults"],
     certificate: ":sdksandbox-test",
     srcs: [
diff --git a/sdksandbox/tests/cts/endtoendtests/providers/retryLoadSameSdkShouldFail/AndroidManifest.xml b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/AndroidManifest.xml
similarity index 84%
rename from sdksandbox/tests/cts/endtoendtests/providers/retryLoadSameSdkShouldFail/AndroidManifest.xml
rename to sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/AndroidManifest.xml
index 302fbec..90229b2 100644
--- a/sdksandbox/tests/cts/endtoendtests/providers/retryLoadSameSdkShouldFail/AndroidManifest.xml
+++ b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/AndroidManifest.xml
@@ -18,9 +18,9 @@
           package="com.android.retryloadsamesdkshouldfailsdkprovider">
 
     <application>
-        <sdk-library android:name="com.android.retryLoadSameSdkShouldFailSdkProvider"
+        <sdk-library android:name="com.android.loadSdkSuccessfullySdkProviderTwo"
                      android:versionMajor="1" />
         <property android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME"
-                  android:value="com.android.retryloadsamesdkshouldfailsdkprovider.SdkProvider" />
+                  android:value="com.android.loadsdksuccessfullysdkprovidertwo.SdkProvider" />
     </application>
 </manifest>
diff --git a/sdksandbox/tests/cts/endtoendtests/providers/retryLoadSameSdkShouldFail/src/com/android/retryloadsamesdkshouldfailsdkprovider/SdkProvider.java b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/src/com/android/loadsdksuccessfullysdkprovidertwo/SdkProvider.java
similarity index 95%
rename from sdksandbox/tests/cts/endtoendtests/providers/retryLoadSameSdkShouldFail/src/com/android/retryloadsamesdkshouldfailsdkprovider/SdkProvider.java
rename to sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/src/com/android/loadsdksuccessfullysdkprovidertwo/SdkProvider.java
index 3185670..f06845c 100644
--- a/sdksandbox/tests/cts/endtoendtests/providers/retryLoadSameSdkShouldFail/src/com/android/retryloadsamesdkshouldfailsdkprovider/SdkProvider.java
+++ b/sdksandbox/tests/cts/endtoendtests/providers/loadSdkSuccessfullyTwo/src/com/android/loadsdksuccessfullysdkprovidertwo/SdkProvider.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.retryloadsamesdkshouldfailsdkprovider;
+package com.android.loadsdksuccessfullysdkprovidertwo;
 
 import android.app.sdksandbox.SandboxedSdkProvider;
 import android.content.Context;
diff --git a/sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/endtoend/SdkSandboxManagerTest.java b/sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/endtoend/SdkSandboxManagerTest.java
index 2657763..c7cc7ce 100644
--- a/sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/endtoend/SdkSandboxManagerTest.java
+++ b/sdksandbox/tests/cts/endtoendtests/src/com/android/tests/sdksandbox/endtoend/SdkSandboxManagerTest.java
@@ -64,7 +64,7 @@
 
     @Test
     public void retryLoadSameSdkShouldFail() {
-        final String sdkName = "com.android.retryLoadSameSdkShouldFailSdkProvider";
+        final String sdkName = "com.android.loadSdkSuccessfullySdkProviderTwo";
         FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
 
         mSdkSandboxManager.loadSdk(sdkName, new Bundle(), Runnable::run, callback);
@@ -163,6 +163,20 @@
     }
 
     @Test
+    public void testReloadingSdkAfterKillingSandboxIsSuccessful() throws Exception {
+        // Killing the sandbox and loading the same SDKs again multiple times should work
+        for (int i = 0; i < 3; ++i) {
+            // Kill the sandbox if it already exists from previous tests/loop
+            killSandboxAndWaitForDeath();
+            // The same SDKs should be able to be loaded again after sandbox death
+            loadMultipleSdks();
+        }
+
+        // Clean up before running other tests
+        killSandboxAndWaitForDeath();
+    }
+
+    @Test
     public void getLoadedSdkLibrariesInfoSuccessfully() {
         final String sdkName = "com.android.getLoadedSdkLibInfoSuccessfully";
         final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
@@ -262,5 +276,24 @@
         mSdkSandboxManager.loadSdk(sdkName, new Bundle(), Runnable::run, callback);
         assertThat(callback.isLoadSdkSuccessful()).isTrue();
     }
+
+    private void loadMultipleSdks() {
+        FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
+        final String sdk1 = "com.android.loadSdkSuccessfullySdkProvider";
+        mSdkSandboxManager.loadSdk(sdk1, new Bundle(), Runnable::run, callback);
+        assertThat(callback.isLoadSdkSuccessful()).isTrue();
+
+        FakeLoadSdkCallback callback2 = new FakeLoadSdkCallback();
+        final String sdk2 = "com.android.loadSdkSuccessfullySdkProviderTwo";
+        mSdkSandboxManager.loadSdk(sdk2, new Bundle(), Runnable::run, callback2);
+        assertThat(callback2.isLoadSdkSuccessful()).isTrue();
+    }
+
+    private void killSandboxAndWaitForDeath() throws Exception {
+        // TODO(b/241542162): Avoid using reflection as a workaround once test apis can be run
+        //  without issue.
+        mSdkSandboxManager.getClass().getMethod("stopSdkSandbox").invoke(mSdkSandboxManager);
+        Thread.sleep(1000);
+    }
 }
 
diff --git a/sdksandbox/tests/hostsidetests/Android.bp b/sdksandbox/tests/hostsidetests/Android.bp
index a651d38..2f41daf 100644
--- a/sdksandbox/tests/hostsidetests/Android.bp
+++ b/sdksandbox/tests/hostsidetests/Android.bp
@@ -17,18 +17,6 @@
 }
 
 java_test_host {
-    name: "SdkSandboxHostTest",
-    srcs: ["src/**/SdkSandboxHostTest.java"],
-    libs: ["tradefed"],
-    test_suites: ["general-tests"],
-    data: [
-        ":TestCodeProvider",
-        ":TestCodeProvider2",
-        ":SdkSandboxTestApp",
-    ],
-}
-
-java_test_host {
     name: "SdkSandboxLifecycleHostTest",
     srcs: [
         "src/**/SdkSandboxLifecycleHostTest.java",
@@ -71,14 +59,12 @@
     platform_apis: true,
     srcs: [
         "app/src/**/SdkSandboxTestActivity.java",
-        "app/src/**/SdkSandboxTestApp.java",
         ":framework-sdksandbox-sources",
         ":sdksandbox_aidl",
         ":sdksandbox-sources",
     ],
     static_libs: [
         "androidx.core_core",
-        "compatibility-device-util-axt",
         "SdkSandboxTestUtils",
     ],
 }
diff --git a/sdksandbox/tests/hostsidetests/AndroidTest.xml b/sdksandbox/tests/hostsidetests/AndroidTest.xml
deleted file mode 100644
index 705f953..0000000
--- a/sdksandbox/tests/hostsidetests/AndroidTest.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?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.
-  -->
-<configuration description="Runs sdk sandbox host tests">
-    <option name="test-suite-tag" value="SdkSandboxHostTest" />
-
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="TestCodeProvider.apk" />
-    </target_preparer>
-
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="TestCodeProvider2.apk" />
-    </target_preparer>
-
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="SdkSandboxTestApp.apk" />
-    </target_preparer>
-
-    <test class="com.android.tradefed.testtype.HostTest" >
-        <option name="class"
-                value="com.android.tests.sdksandbox.host.SdkSandboxHostTest" />
-    </test>
-
-    <object type="module_controller"
-            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController" >
-        <option name="mainline-module-package-name" value="com.google.android.adservices" />
-    </object>
-
-    <option name="config-descriptor:metadata" key="mainline-param"
-            value="com.google.android.adservices.apex" />
-</configuration>
\ No newline at end of file
diff --git a/sdksandbox/tests/hostsidetests/app/SdkSandboxTestAppManifest.xml b/sdksandbox/tests/hostsidetests/app/SdkSandboxTestAppManifest.xml
index 82eff93..e2ef2ac 100644
--- a/sdksandbox/tests/hostsidetests/app/SdkSandboxTestAppManifest.xml
+++ b/sdksandbox/tests/hostsidetests/app/SdkSandboxTestAppManifest.xml
@@ -40,7 +40,4 @@
         />
     </application>
 
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.sdksandbox.app"
-                     android:label="SdkSandboxtestApp"/>
 </manifest>
diff --git a/sdksandbox/tests/hostsidetests/app/src/com/android/sdksandbox/app/SdkSandboxTestApp.java b/sdksandbox/tests/hostsidetests/app/src/com/android/sdksandbox/app/SdkSandboxTestApp.java
deleted file mode 100644
index 9c79a72..0000000
--- a/sdksandbox/tests/hostsidetests/app/src/com/android/sdksandbox/app/SdkSandboxTestApp.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2022 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.sdksandbox.app;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.sdksandbox.SdkSandboxManager;
-import android.app.sdksandbox.testutils.FakeLoadSdkCallback;
-import android.os.Bundle;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class SdkSandboxTestApp {
-
-    private static final String SDK_NAME = "com.android.testcode";
-    private static final String SDK_2_NAME = "com.android.testcode2";
-
-    private SdkSandboxManager mSdkSandboxManager;
-
-    @Before
-    public void setup() {
-        mSdkSandboxManager =
-                ApplicationProvider.getApplicationContext()
-                        .getSystemService(SdkSandboxManager.class);
-        assertThat(mSdkSandboxManager).isNotNull();
-    }
-
-    @Test
-    public void testLoadMultipleSdks() throws Exception {
-        FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
-        mSdkSandboxManager.loadSdk(SDK_NAME, new Bundle(), Runnable::run, callback);
-        assertThat(callback.isLoadSdkSuccessful()).isTrue();
-
-        FakeLoadSdkCallback callback2 = new FakeLoadSdkCallback();
-        mSdkSandboxManager.loadSdk(SDK_2_NAME, new Bundle(), Runnable::run, callback2);
-        assertThat(callback2.isLoadSdkSuccessful()).isTrue();
-    }
-}
diff --git a/sdksandbox/tests/hostsidetests/src/com/android/tests/sdksandbox/host/SdkSandboxHostTest.java b/sdksandbox/tests/hostsidetests/src/com/android/tests/sdksandbox/host/SdkSandboxHostTest.java
deleted file mode 100644
index a2bfc99..0000000
--- a/sdksandbox/tests/hostsidetests/src/com/android/tests/sdksandbox/host/SdkSandboxHostTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2022 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.tests.sdksandbox.host;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class SdkSandboxHostTest extends BaseHostJUnit4Test {
-
-    private static final String APP_PACKAGE = "com.android.sdksandbox.app";
-    private static final String APP_TEST_CLASS = APP_PACKAGE + ".SdkSandboxTestApp";
-
-    private static final String SDK_APK = "TestCodeProvider.apk";
-
-    /**
-     * Runs the given phase of a test by calling into the device. Throws an exception if the test
-     * phase fails.
-     *
-     * <p>For example, <code>runPhase("testExample");</code>
-     */
-    private void runPhase(String phase) throws Exception {
-        assertThat(runDeviceTests(APP_PACKAGE, APP_TEST_CLASS, phase)).isTrue();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        assertThat(getBuild()).isNotNull();
-        assertThat(getDevice()).isNotNull();
-
-        // Ensure the app is not currently running
-        getDevice().executeShellCommand(String.format("pm clear %s", APP_PACKAGE));
-
-        // Workaround for autoTeardown which removes packages installed in test
-        if (!isPackageInstalled(SDK_APK)) {
-            installPackage(SDK_APK, "-d");
-        }
-    }
-
-    @Test
-    public void testReloadingSdkAfterKillingSandboxIsSuccessful() throws Exception {
-        // Have the app load multiple SDKs and bring up the sandbox
-        runPhase("testLoadMultipleSdks");
-
-        final String sandboxProcessName = APP_PACKAGE + "_sdk_sandbox";
-        final String sandboxPid =
-                getDevice().executeShellCommand(String.format("pidof -s %s", sandboxProcessName));
-        // Kill the sandbox
-        getDevice().executeShellCommand(String.format("kill -9 %s", sandboxPid));
-        String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
-        assertThat(processDump).doesNotContain(sandboxProcessName);
-
-        // Loading both SDKs again should succeed
-        runPhase("testLoadMultipleSdks");
-    }
-}
diff --git a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkSandboxManagerService.java b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkSandboxManagerService.java
index 26e2cdf..2a84dad 100644
--- a/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkSandboxManagerService.java
+++ b/sdksandbox/tests/testutils/src/android/app/sdksandbox/testutils/StubSdkSandboxManagerService.java
@@ -23,6 +23,7 @@
 import android.content.pm.SharedLibraryInfo;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.RemoteException;
 
 import java.util.Collections;
 import java.util.List;
@@ -62,5 +63,8 @@
     }
 
     @Override
+    public void stopSdkSandbox(String callingPackageName) throws RemoteException {}
+
+    @Override
     public void syncDataFromClient(String callingPackageName, Bundle data) {}
 }
diff --git a/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java b/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java
index bcd2b54..4e86d9f 100644
--- a/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java
+++ b/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java
@@ -683,6 +683,33 @@
         assertThat(mSdkSandboxService.getLastUpdate()).isSameInstanceAs(data);
     }
 
+    @Test
+    public void testStopSdkSandbox() throws Exception {
+        disableKillUid();
+        disableNetworkPermissionChecks();
+
+        FakeLoadSdkCallbackBinder callback = new FakeLoadSdkCallbackBinder();
+        mService.loadSdk(TEST_PACKAGE, SDK_NAME, new Bundle(), callback);
+        // Assume sandbox loads successfully
+        mSdkSandboxService.sendLoadCodeSuccessful();
+        assertThat(callback.isLoadSdkSuccessful()).isTrue();
+
+        Mockito.doNothing()
+                .when(mSpyContext)
+                .enforceCallingPermission(
+                        Mockito.eq("com.android.app.sdksandbox.permission.STOP_SDK_SANDBOX"),
+                        Mockito.anyString());
+        mService.stopSdkSandbox(TEST_PACKAGE);
+        int callingUid = Binder.getCallingUid();
+        final CallingInfo callingInfo = new CallingInfo(callingUid, TEST_PACKAGE);
+        assertThat(mProvider.getBoundServiceForApp(callingInfo)).isEqualTo(null);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testStopSdkSandbox_WithoutPermission() {
+        mService.stopSdkSandbox(TEST_PACKAGE);
+    }
+
     /**
      * Fake service provider that returns local instance of {@link SdkSandboxServiceProvider}
      */