Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9a568700 authored by Samiul Islam's avatar Samiul Islam Committed by Android (Google) Code Review
Browse files

Merge "Bind to Sdk Dependency Installer Service" into main

parents 27f15a01 68cdac74
Loading
Loading
Loading
Loading
+203 −14
Original line number Diff line number Diff line
@@ -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 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);

    private final SharedLibrariesImpl mSharedLibraries;
    private final Context mContext;
    private final Object mRemoteServiceLock = new Object();

    InstallDependencyHelper(SharedLibrariesImpl sharedLibraries) {
    @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) {
        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 onError(CallOnceProxy callback, String msg) {
        PackageManagerException pe = new PackageManagerException(
                    INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage());
                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;
            }
        }

    private void bindToDependencyInstaller() {
        throw new IllegalStateException("Failed to bind to Dependency Installer");
        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;
                }
            }
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -347,7 +347,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        synchronized (mVerificationPolicyPerUser) {
            mVerificationPolicyPerUser.put(USER_SYSTEM, DEFAULT_VERIFICATION_POLICY);
        }
        mInstallDependencyHelper = new InstallDependencyHelper(
        mInstallDependencyHelper = new InstallDependencyHelper(mContext,
                mPm.mInjector.getSharedLibrariesImpl());

        LocalServices.getService(SystemServiceManager.class).startService(
+1 −1
Original line number Diff line number Diff line
@@ -3428,8 +3428,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

    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
+3 −2
Original line number Diff line number Diff line
@@ -1017,10 +1017,11 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
                    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 {
+15 −5
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.doThrow;
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.PackageLite;
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 @@ public class InstallDependencyHelperTest {
    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 @@ public class InstallDependencyHelperTest {

        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 @@ public class InstallDependencyHelperTest {
                .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 @@ public class InstallDependencyHelperTest {
                .thenReturn(missingDependency);

        CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true);
        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
                0, mHandler, callback);
        callback.assertSuccess();
    }