Merge "Bind to Sdk Dependency Installer Service" into main
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
index 745665b..527d680 100644
--- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -17,51 +17,240 @@
 package com.android.server.pm;
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+import static android.os.Process.SYSTEM_UID;
 
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
 import android.content.pm.SharedLibraryInfo;
+import android.content.pm.dependencyinstaller.DependencyInstallerCallback;
+import android.content.pm.dependencyinstaller.IDependencyInstallerCallback;
+import android.content.pm.dependencyinstaller.IDependencyInstallerService;
 import android.content.pm.parsing.PackageLite;
+import android.os.Handler;
 import android.os.OutcomeReceiver;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
 
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Helper class to interact with SDK Dependency Installer service.
  */
 public class InstallDependencyHelper {
-    private final SharedLibrariesImpl mSharedLibraries;
+    private static final String TAG = InstallDependencyHelper.class.getSimpleName();
+    private static final boolean DEBUG = true;
+    private static final String ACTION_INSTALL_DEPENDENCY =
+            "android.intent.action.INSTALL_DEPENDENCY";
+    // The maximum amount of time to wait before the system unbinds from the verifier.
+    private static final long UNBIND_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(6);
+    private static final long REQUEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1);
 
-    InstallDependencyHelper(SharedLibrariesImpl sharedLibraries) {
+    private final SharedLibrariesImpl mSharedLibraries;
+    private final Context mContext;
+    private final Object mRemoteServiceLock = new Object();
+
+    @GuardedBy("mRemoteServiceLock")
+    private ServiceConnector<IDependencyInstallerService> mRemoteService = null;
+
+    InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries) {
+        mContext = context;
         mSharedLibraries = sharedLibraries;
     }
 
-    void resolveLibraryDependenciesIfNeeded(PackageLite pkg,
-            OutcomeReceiver<Void, PackageManagerException> callback) {
-        final List<SharedLibraryInfo> missing;
+    void resolveLibraryDependenciesIfNeeded(PackageLite pkg, Computer snapshot, int userId,
+            Handler handler, OutcomeReceiver<Void, PackageManagerException> origCallback) {
+        CallOnceProxy callback = new CallOnceProxy(handler, origCallback);
         try {
-            missing = mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
+            resolveLibraryDependenciesIfNeededInternal(pkg, snapshot, userId, handler, callback);
         } catch (PackageManagerException e) {
             callback.onError(e);
-            return;
+        } catch (Exception e) {
+            onError(callback, e.getMessage());
         }
+    }
+
+
+    private void resolveLibraryDependenciesIfNeededInternal(PackageLite pkg, Computer snapshot,
+            int userId, Handler handler, CallOnceProxy callback) throws PackageManagerException {
+        final List<SharedLibraryInfo> missing =
+                mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
 
         if (missing.isEmpty()) {
+            if (DEBUG) {
+                Slog.i(TAG, "No missing dependency for " + pkg);
+            }
             // No need for dependency resolution. Move to installation directly.
             callback.onResult(null);
             return;
         }
 
-        try {
-            bindToDependencyInstaller();
-        } catch (Exception e) {
-            PackageManagerException pe = new PackageManagerException(
-                    INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage());
-            callback.onError(pe);
+        if (!bindToDependencyInstallerIfNeeded(userId, handler, snapshot)) {
+            onError(callback, "Dependency Installer Service not found");
+            return;
+        }
+
+        IDependencyInstallerCallback serviceCallback = new IDependencyInstallerCallback.Stub() {
+            @Override
+            public void onAllDependenciesResolved(int[] sessionIds) throws RemoteException {
+                // TODO(b/372862145): Implement waiting for sessions to finish installation
+                callback.onResult(null);
+            }
+
+            @Override
+            public void onFailureToResolveAllDependencies() throws RemoteException {
+                onError(callback, "Failed to resolve all dependencies automatically");
+            }
+        };
+
+        boolean scheduleSuccess;
+        synchronized (mRemoteServiceLock) {
+            scheduleSuccess = mRemoteService.run(service -> {
+                service.onDependenciesRequired(missing,
+                        new DependencyInstallerCallback(serviceCallback.asBinder()));
+            });
+        }
+        if (!scheduleSuccess) {
+            onError(callback, "Failed to schedule job on Dependency Installer Service");
         }
     }
 
-    private void bindToDependencyInstaller() {
-        throw new IllegalStateException("Failed to bind to Dependency Installer");
+    private void onError(CallOnceProxy callback, String msg) {
+        PackageManagerException pe = new PackageManagerException(
+                INSTALL_FAILED_MISSING_SHARED_LIBRARY, msg);
+        callback.onError(pe);
     }
 
+    private boolean bindToDependencyInstallerIfNeeded(int userId, Handler handler,
+            Computer snapshot) {
+        synchronized (mRemoteServiceLock) {
+            if (mRemoteService != null) {
+                if (DEBUG) {
+                    Slog.i(TAG, "DependencyInstallerService already bound");
+                }
+                return true;
+            }
+        }
 
+        Intent serviceIntent = new Intent(ACTION_INSTALL_DEPENDENCY);
+        // TODO(b/372862145): Use RoleManager to find the package name
+        List<ResolveInfo> resolvedIntents = snapshot.queryIntentServicesInternal(
+                serviceIntent, /*resolvedType=*/ null, /*flags=*/0,
+                userId, SYSTEM_UID, Process.INVALID_PID,
+                /*includeInstantApps*/ false, /*resolveForStart*/ false);
+
+        if (resolvedIntents.isEmpty()) {
+            return false;
+        }
+
+
+        ResolveInfo resolveInfo = resolvedIntents.getFirst();
+        ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
+        serviceIntent.setComponent(componentName);
+
+        ServiceConnector<IDependencyInstallerService> serviceConnector =
+                new ServiceConnector.Impl<IDependencyInstallerService>(mContext, serviceIntent,
+                    Context.BIND_AUTO_CREATE, userId,
+                    IDependencyInstallerService.Stub::asInterface) {
+                    @Override
+                    protected Handler getJobHandler() {
+                        return handler;
+                    }
+
+                    @Override
+                    protected long getRequestTimeoutMs() {
+                        return REQUEST_TIMEOUT_MILLIS;
+                    }
+
+                    @Override
+                    protected long getAutoDisconnectTimeoutMs() {
+                        return UNBIND_TIMEOUT_MILLIS;
+                    }
+                };
+
+
+        synchronized (mRemoteServiceLock) {
+            // Some other thread managed to connect to the service first
+            if (mRemoteService != null) {
+                return true;
+            }
+            mRemoteService = serviceConnector;
+            mRemoteService.setServiceLifecycleCallbacks(
+                new ServiceConnector.ServiceLifecycleCallbacks<>() {
+                    @Override
+                    public void onDisconnected(@NonNull IDependencyInstallerService service) {
+                        Slog.w(TAG,
+                                "DependencyInstallerService " + componentName + " is disconnected");
+                        destroy();
+                    }
+
+                    @Override
+                    public void onBinderDied() {
+                        Slog.w(TAG, "DependencyInstallerService " + componentName + " has died");
+                        destroy();
+                    }
+
+                    private void destroy() {
+                        synchronized (mRemoteServiceLock) {
+                            if (mRemoteService != null) {
+                                mRemoteService.unbind();
+                                mRemoteService = null;
+                            }
+                        }
+                    }
+
+                });
+            AndroidFuture<IDependencyInstallerService> unusedFuture = mRemoteService.connect();
+        }
+        return true;
+    }
+
+    /**
+     * Ensure we call one of the outcomes only once, on the right handler.
+     *
+     * Repeated calls will be no-op.
+     */
+    private static class CallOnceProxy implements OutcomeReceiver<Void, PackageManagerException> {
+        private final Handler mHandler;
+        private final OutcomeReceiver<Void, PackageManagerException> mCallback;
+        @GuardedBy("this")
+        private boolean mCalled = false;
+
+        CallOnceProxy(Handler handler, OutcomeReceiver<Void, PackageManagerException> callback) {
+            mHandler = handler;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResult(Void result) {
+            synchronized (this) {
+                if (!mCalled) {
+                    mHandler.post(() -> {
+                        mCallback.onResult(null);
+                    });
+                    mCalled = true;
+                }
+            }
+        }
+
+        @Override
+        public void onError(@NonNull PackageManagerException error) {
+            synchronized (this) {
+                if (!mCalled) {
+                    mHandler.post(() -> {
+                        mCallback.onError(error);
+                    });
+                    mCalled = true;
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index eb70748..9b44f93 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -347,7 +347,7 @@
         synchronized (mVerificationPolicyPerUser) {
             mVerificationPolicyPerUser.put(USER_SYSTEM, DEFAULT_VERIFICATION_POLICY);
         }
-        mInstallDependencyHelper = new InstallDependencyHelper(
+        mInstallDependencyHelper = new InstallDependencyHelper(mContext,
                 mPm.mInjector.getSharedLibrariesImpl());
 
         LocalServices.getService(SystemServiceManager.class).startService(
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index e156b31..505b7e6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3428,8 +3428,8 @@
 
     private void resolveLibraryDependenciesIfNeeded() {
         synchronized (mLock) {
-            // TODO(b/372862145): Callback should be called on a handler passed as parameter
             mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite,
+                    mPm.snapshotComputer(), userId, mHandler,
                     new OutcomeReceiver<>() {
 
                         @Override
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index fc54f68..17d7a14 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -1017,10 +1017,11 @@
                     boolean isSdkOrStatic = libraryType.equals(LIBRARY_TYPE_SDK)
                             || libraryType.equals(LIBRARY_TYPE_STATIC);
                     if (isSdkOrStatic && outMissingSharedLibraryInfos != null) {
-                        // TODO(b/372862145): Pass the CertDigest too
                         // If Dependency Installation is supported, try that instead of failing.
+                        final List<String> libCertDigests = Arrays.asList(requiredCertDigests[i]);
                         SharedLibraryInfo missingLibrary = new SharedLibraryInfo(
-                                libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE
+                                libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE,
+                                libCertDigests
                         );
                         outMissingSharedLibraryInfos.add(missingLibrary);
                     } else {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
index f6c644e..20ac078 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.parsing.ApkLite;
 import android.content.pm.parsing.ApkLiteParseUtils;
@@ -34,6 +35,8 @@
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.OutcomeReceiver;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -71,13 +74,17 @@
     private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/smockingservicestest/pm/";
     private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
 
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
     @Mock private SharedLibrariesImpl mSharedLibraries;
+    @Mock private Context mContext;
+    @Mock private Computer mComputer;
     private InstallDependencyHelper mInstallDependencyHelper;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mInstallDependencyHelper = new InstallDependencyHelper(mSharedLibraries);
+        mInstallDependencyHelper = new InstallDependencyHelper(mContext, mSharedLibraries);
     }
 
     @Test
@@ -88,7 +95,8 @@
 
         PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
         CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
-        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+                0, mHandler, callback);
         callback.assertFailure();
 
         assertThat(callback.error).hasMessageThat().contains("xyz");
@@ -104,11 +112,12 @@
                 .thenReturn(missingDependency);
 
         CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
-        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+                0, mHandler, callback);
         callback.assertFailure();
 
         assertThat(callback.error).hasMessageThat().contains(
-                "Failed to bind to Dependency Installer");
+                "Dependency Installer Service not found");
     }
 
 
@@ -121,7 +130,8 @@
                 .thenReturn(missingDependency);
 
         CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true);
-        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+                0, mHandler, callback);
         callback.assertSuccess();
     }