Merge "Create the service to be implemented by the holders of the Dependency Installer role." into main
diff --git a/core/api/current.txt b/core/api/current.txt
index f9d505c..7fb4821 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13806,6 +13806,7 @@
 
   public final class SharedLibraryInfo implements android.os.Parcelable {
     method public int describeContents();
+    method @FlaggedApi("android.content.pm.sdk_dependency_installer") @NonNull public java.util.List<java.lang.String> getCertDigests();
     method @NonNull public android.content.pm.VersionedPackage getDeclaringPackage();
     method @NonNull public java.util.List<android.content.pm.VersionedPackage> getDependentPackages();
     method @IntRange(from=0xffffffff) public long getLongVersion();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e4f1582..61f9b89 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4594,6 +4594,24 @@
 
 }
 
+package android.content.pm.dependencyinstaller {
+
+  @FlaggedApi("android.content.pm.sdk_dependency_installer") public final class DependencyInstallerCallback implements android.os.Parcelable {
+    method public int describeContents();
+    method public void onAllDependenciesResolved(@NonNull int[]);
+    method public void onFailureToResolveAllDependencies();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.dependencyinstaller.DependencyInstallerCallback> CREATOR;
+  }
+
+  @FlaggedApi("android.content.pm.sdk_dependency_installer") public abstract class DependencyInstallerService extends android.app.Service {
+    ctor public DependencyInstallerService();
+    method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
+    method public abstract void onDependenciesRequired(@NonNull java.util.List<android.content.pm.SharedLibraryInfo>, @NonNull android.content.pm.dependencyinstaller.DependencyInstallerCallback);
+  }
+
+}
+
 package android.content.pm.dex {
 
   public class ArtManager {
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index f7191e6..5dfec98 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -105,6 +105,8 @@
     private final List<VersionedPackage> mOptionalDependentPackages;
     private List<SharedLibraryInfo> mDependencies;
 
+    private final List<String> mCertDigests;
+
     /**
      * Creates a new instance.
      *
@@ -134,6 +136,7 @@
         mDependencies = dependencies;
         mIsNative = isNative;
         mOptionalDependentPackages = null;
+        mCertDigests = null;
     }
 
     /**
@@ -165,6 +168,7 @@
         mDeclaringPackage = declaringPackage;
         mDependencies = dependencies;
         mIsNative = isNative;
+        mCertDigests = null;
 
         var allDependents = allDependentPackages.first;
         var usesLibOptional = allDependentPackages.second;
@@ -206,6 +210,7 @@
         mIsNative = parcel.readBoolean();
         mOptionalDependentPackages = parcel.readParcelableList(new ArrayList<>(),
                 VersionedPackage.class.getClassLoader(), VersionedPackage.class);
+        mCertDigests = parcel.createStringArrayList();
     }
 
     /**
@@ -214,6 +219,7 @@
      * @param versionMajor
      */
     public SharedLibraryInfo(String name, long versionMajor, int type) {
+        //TODO: change to this(name, versionMajor, type, /* certDigest= */null); after flag removal
         mPath = null;
         mPackageName = null;
         mName = name;
@@ -224,6 +230,29 @@
         mDependencies = null;
         mIsNative = false;
         mOptionalDependentPackages = null;
+        mCertDigests = null;
+    }
+
+    /**
+     * @hide
+     * @param name The lib name.
+     * @param versionMajor The lib major version.
+     * @param type The type of shared library.
+     * @param certDigests The list of certificate digests for this shared library.
+     */
+    @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+    public SharedLibraryInfo(String name, long versionMajor, int type, List<String> certDigests) {
+        mPath = null;
+        mPackageName = null;
+        mName = name;
+        mVersion = versionMajor;
+        mType = type;
+        mDeclaringPackage = null;
+        mDependentPackages = null;
+        mDependencies = null;
+        mIsNative = false;
+        mOptionalDependentPackages = null;
+        mCertDigests = certDigests;
     }
 
     /**
@@ -433,6 +462,19 @@
         return mOptionalDependentPackages;
     }
 
+    /**
+     * Gets the list of certificate digests for the shared library.
+     *
+     * @return The list of certificate digests
+     */
+    @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+    public @NonNull List<String> getCertDigests() {
+        if (mCertDigests == null) {
+            return Collections.emptyList();
+        }
+        return mCertDigests;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -463,6 +505,7 @@
         parcel.writeTypedList(mDependencies);
         parcel.writeBoolean(mIsNative);
         parcel.writeParcelableList(mOptionalDependentPackages, flags);
+        parcel.writeStringList(mCertDigests);
     }
 
     private static String typeToString(int type) {
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl
new file mode 100644
index 0000000..06fcabc
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dependencyinstaller;
+
+parcelable DependencyInstallerCallback;
\ No newline at end of file
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
new file mode 100644
index 0000000..ba089f7
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dependencyinstaller;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.Flags;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * Callbacks for {@link DependencyInstallerService}. The implementation of
+ * DependencyInstallerService uses this interface to indicate completion of the session creation
+ * request given by the system server.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+public final class DependencyInstallerCallback implements Parcelable {
+    private final IBinder mBinder;
+    private final IDependencyInstallerCallback mCallback;
+
+    /** @hide */
+    public DependencyInstallerCallback(IBinder binder) {
+        mBinder = binder;
+        mCallback = IDependencyInstallerCallback.Stub.asInterface(binder);
+    }
+
+    private DependencyInstallerCallback(Parcel in) {
+        mBinder = in.readStrongBinder();
+        mCallback = IDependencyInstallerCallback.Stub.asInterface(mBinder);
+    }
+
+    /**
+     * Callback to indicate that all the requested dependencies have been resolved and their
+     * sessions created. See {@link  DependencyInstallerService#onDependenciesRequired}.
+     *
+     * @param sessionIds the install session IDs for all requested dependencies
+     */
+    public void onAllDependenciesResolved(@NonNull int[] sessionIds) {
+        try {
+            mCallback.onAllDependenciesResolved(sessionIds);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Callback to indicate that at least one of the required dependencies could not be resolved
+     * and any associated sessions have been abandoned.
+     */
+    public void onFailureToResolveAllDependencies() {
+        try {
+            mCallback.onFailureToResolveAllDependencies();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeStrongBinder(mBinder);
+    }
+
+    public static final @NonNull Creator<DependencyInstallerCallback> CREATOR =
+            new Creator<>() {
+                @Override
+                public DependencyInstallerCallback createFromParcel(Parcel in) {
+                    return new DependencyInstallerCallback(in);
+                }
+
+                @Override
+                public DependencyInstallerCallback[] newArray(int size) {
+                    return new DependencyInstallerCallback[size];
+                }
+            };
+}
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java
new file mode 100644
index 0000000..1186415
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dependencyinstaller;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.Flags;
+import android.content.pm.SharedLibraryInfo;
+import android.os.IBinder;
+
+import java.util.List;
+
+/**
+ * Service that needs to be implemented by the holder of the DependencyInstaller role. This service
+ * will be invoked by the system during application installations if it depends on
+ * {@link android.content.pm.SharedLibraryInfo#TYPE_STATIC} or
+ * {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE} and those dependencies aren't
+ * already installed.
+ * <p>
+ * Below is an example manifest registration for a {@code DependencyInstallerService}.
+ * <pre>
+ * {@code
+ * <service android:name=".ExampleDependencyInstallerService"
+ *     android:permission="android.permission.BIND_DEPENDENCY_INSTALLER" >
+ *     ...
+ *     <intent-filter>
+ *         <action android:name="android.content.pm.action.INSTALL_DEPENDENCY" />
+ *     </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+public abstract class DependencyInstallerService extends Service {
+
+    private IDependencyInstallerService mBinder;
+
+    @Override
+    public final @NonNull IBinder onBind(@Nullable Intent intent) {
+        if (mBinder == null) {
+            mBinder = new IDependencyInstallerService.Stub() {
+                @Override
+                public void onDependenciesRequired(List<SharedLibraryInfo> neededLibraries,
+                        DependencyInstallerCallback callback) {
+                    DependencyInstallerService.this.onDependenciesRequired(neededLibraries,
+                            callback);
+                }
+            };
+        }
+        return mBinder.asBinder();
+    }
+
+    /**
+     * Notify the holder of the DependencyInstaller role of the missing dependencies required for
+     * the completion of an active install session.
+     *
+     * @param neededLibraries the list of shared library dependencies needed to be obtained and
+     *                        installed.
+     */
+    public abstract void onDependenciesRequired(@NonNull List<SharedLibraryInfo> neededLibraries,
+            @NonNull DependencyInstallerCallback callback);
+}
diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
new file mode 100644
index 0000000..92d1d9e
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dependencyinstaller;
+
+import java.util.List;
+
+/**
+* Callbacks for Dependency Installer. The app side invokes on this interface to indicate
+* completion of the async dependency install request given by the system server.
+*
+* {@hide}
+*/
+oneway interface IDependencyInstallerCallback {
+    /**
+     * Callback to indicate that all the requested dependencies have been resolved and have been
+     * committed for installation. See {@link  DependencyInstallerService#onDependenciesRequired}.
+     *
+     * @param sessionIds the install session IDs for all requested dependencies
+     */
+    void onAllDependenciesResolved(in int[] sessionIds);
+
+    /**
+     * Callback to indicate that at least one of the required dependencies could not be resolved
+     * and any associated sessions have been abandoned.
+     */
+    void onFailureToResolveAllDependencies();
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl
new file mode 100644
index 0000000..94f5bf4
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl
@@ -0,0 +1,37 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dependencyinstaller;
+
+import android.content.pm.dependencyinstaller.DependencyInstallerCallback;
+import android.content.pm.SharedLibraryInfo;
+import java.util.List;
+
+/**
+* Interface used to communicate with the application code that holds the Dependency Installer role.
+* {@hide}
+*/
+oneway interface IDependencyInstallerService {
+    /**
+     * Notify dependency installer of the required dependencies to complete the current install
+     * session.
+     *
+     * @param neededLibraries the list of shared library dependencies needed to be obtained and
+     *                        installed.
+     */
+    void onDependenciesRequired(in List<SharedLibraryInfo> neededLibraries,
+                in DependencyInstallerCallback callback);
+ }
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java b/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java
new file mode 100644
index 0000000..df5cd4e
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+@Presubmit
+@RunWith(JUnit4.class)
+public final class SharedLibraryInfoTest {
+
+    @Rule
+    public final CheckFlagsRule checkFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    private static final String LIBRARY_NAME = "name";
+    private static final long VERSION_MAJOR = 1L;
+    private static final List<String> CERT_DIGESTS = ImmutableList.of("digest1", "digest2");
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+    public void sharedLibraryInfo_serializedAndDeserialized_retainsCertDigestInfo() {
+        SharedLibraryInfo toParcel = new SharedLibraryInfo(LIBRARY_NAME, VERSION_MAJOR,
+                SharedLibraryInfo.TYPE_SDK_PACKAGE, CERT_DIGESTS);
+
+        SharedLibraryInfo fromParcel = parcelAndUnparcel(toParcel);
+
+        assertThat(fromParcel.getCertDigests().size()).isEqualTo(toParcel.getCertDigests().size());
+        assertThat(fromParcel.getCertDigests().get(0)).isEqualTo(toParcel.getCertDigests().get(0));
+        assertThat(fromParcel.getCertDigests().get(1)).isEqualTo(toParcel.getCertDigests().get(1));
+    }
+
+    private SharedLibraryInfo parcelAndUnparcel(SharedLibraryInfo sharedLibraryInfo) {
+        Parcel parcel = Parcel.obtain();
+        parcel.setDataPosition(0);
+        sharedLibraryInfo.writeToParcel(parcel, /* flags= */0);
+
+        parcel.setDataPosition(0);
+        return SharedLibraryInfo.CREATOR.createFromParcel(parcel);
+    }
+}