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

Commit 160cdc8e authored by Samiul Islam's avatar Samiul Islam
Browse files

Add multi-user support for Dependency Installer Service

We now maintain a map between user-id and DIS. For each user, we bind to
DIS installed for the user.

Few more issues fixed in the same CL:
  - We now clear calling identity when DIS calls back into system.
  - To be safe, we now allow any exception to enable the callback again.
    Otherwise, if we throw anything other than IAE, it's possible to
    hang indefinitely. For example, in the validation logic, if any of
    the calls throw a SecurityException, we would get stuck in old
    logic.

Bug: 372862145
Test: atest PackageManagerShellCommandInstallTest
FLAG: android.content.pm.sdk_dependency_installer
Change-Id: Ib5f2c9e9609ac2f83a4cc863d8765e98d72d9101
parent 42b63887
Loading
Loading
Loading
Loading
+69 −51
Original line number Diff line number Diff line
@@ -32,11 +32,13 @@ 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.Binder;
import android.os.Handler;
import android.os.OutcomeReceiver;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;

@@ -63,12 +65,11 @@ public class InstallDependencyHelper {
    private final Context mContext;
    private final SharedLibrariesImpl mSharedLibraries;
    private final PackageInstallerService mPackageInstallerService;
    private final Object mRemoteServiceLock = new Object();
    @GuardedBy("mTrackers")
    private final List<DependencyInstallTracker> mTrackers = new ArrayList<>();

    @GuardedBy("mRemoteServiceLock")
    private ServiceConnector<IDependencyInstallerService> mRemoteService = null;
    @GuardedBy("mRemoteServices")
    private final ArrayMap<Integer, ServiceConnector<IDependencyInstallerService>> mRemoteServices =
            new ArrayMap<>();

    InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries,
            PackageInstallerService packageInstallerService) {
@@ -97,13 +98,18 @@ public class InstallDependencyHelper {

        if (missing.isEmpty()) {
            if (DEBUG) {
                Slog.d(TAG, "No missing dependency for " + pkg);
                Slog.d(TAG, "No missing dependency for " + pkg.getPackageName());
            }
            // No need for dependency resolution. Move to installation directly.
            callback.onResult(null);
            return;
        }

        if (DEBUG) {
            Slog.d(TAG, "Missing dependencies found for pkg: " + pkg.getPackageName()
                    + " user: " + userId);
        }

        if (!bindToDependencyInstallerIfNeeded(userId, handler, snapshot)) {
            onError(callback, "Dependency Installer Service not found");
            return;
@@ -112,8 +118,8 @@ public class InstallDependencyHelper {
        IDependencyInstallerCallback serviceCallback =
                new DependencyInstallerCallbackCallOnce(handler, callback, userId);
        boolean scheduleSuccess;
        synchronized (mRemoteServiceLock) {
            scheduleSuccess = mRemoteService.run(service -> {
        synchronized (mRemoteServices) {
            scheduleSuccess = mRemoteServices.get(userId).run(service -> {
                service.onDependenciesRequired(missing,
                        new DependencyInstallerCallback(serviceCallback.asBinder()));
            });
@@ -149,15 +155,17 @@ public class InstallDependencyHelper {

    private boolean bindToDependencyInstallerIfNeeded(int userId, Handler handler,
            Computer snapshot) {
        synchronized (mRemoteServiceLock) {
            if (mRemoteService != null) {
        synchronized (mRemoteServices) {
            if (mRemoteServices.containsKey(userId)) {
                if (DEBUG) {
                    Slog.i(TAG, "DependencyInstallerService already bound");
                    Slog.i(TAG, "DependencyInstallerService for user " + userId + " already bound");
                }
                return true;
            }
        }

        Slog.i(TAG, "Attempting to bind to Dependency Installer Service for user " + userId);

        RoleManager roleManager = mContext.getSystemService(RoleManager.class);
        if (roleManager == null) {
            Slog.w(TAG, "Cannot find RoleManager system service");
@@ -166,7 +174,7 @@ public class InstallDependencyHelper {
        List<String> holders = roleManager.getRoleHoldersAsUser(
                ROLE_SYSTEM_DEPENDENCY_INSTALLER, UserHandle.of(userId));
        if (holders.isEmpty()) {
            Slog.w(TAG, "No holders of ROLE_SYSTEM_DEPENDENCY_INSTALLER found");
            Slog.w(TAG, "No holders of ROLE_SYSTEM_DEPENDENCY_INSTALLER found for user " + userId);
            return false;
        }

@@ -178,6 +186,8 @@ public class InstallDependencyHelper {
                /*includeInstantApps*/ false, /*resolveForStart*/ false);

        if (resolvedIntents.isEmpty()) {
            Slog.w(TAG, "No package holding ROLE_SYSTEM_DEPENDENCY_INSTALLER found for user "
                    + userId);
            return false;
        }

@@ -206,13 +216,14 @@ public class InstallDependencyHelper {
                };


        synchronized (mRemoteServiceLock) {
        synchronized (mRemoteServices) {
            // Some other thread managed to connect to the service first
            if (mRemoteService != null) {
            if (mRemoteServices.containsKey(userId)) {
                return true;
            }
            mRemoteService = serviceConnector;
            mRemoteService.setServiceLifecycleCallbacks(
            mRemoteServices.put(userId, serviceConnector);
            // Block the lock until we connect to the service
            serviceConnector.setServiceLifecycleCallbacks(
                new ServiceConnector.ServiceLifecycleCallbacks<>() {
                    @Override
                    public void onDisconnected(@NonNull IDependencyInstallerService service) {
@@ -228,17 +239,18 @@ public class InstallDependencyHelper {
                    }

                    private void destroy() {
                        synchronized (mRemoteServiceLock) {
                            if (mRemoteService != null) {
                                mRemoteService.unbind();
                                mRemoteService = null;
                        synchronized (mRemoteServices) {
                            if (mRemoteServices.containsKey(userId)) {
                                mRemoteServices.get(userId).unbind();
                                mRemoteServices.remove(userId);
                            }
                        }
                    }

                });
            AndroidFuture<IDependencyInstallerService> unusedFuture = mRemoteService.connect();
            AndroidFuture<IDependencyInstallerService> unusedFuture = serviceConnector.connect();
        }
        Slog.i(TAG, "Successfully bound to Dependency Installer Service for user " + userId);
        return true;
    }

@@ -318,6 +330,7 @@ public class InstallDependencyHelper {
                Slog.d(TAG, "onAllDependenciesResolved started");
            }

            try {
                // Before creating any tracker, validate the arguments
                ArraySet<Integer> validSessionIds = validateSessionIds(sessionIds);

@@ -340,9 +353,18 @@ public class InstallDependencyHelper {

                    // Don't wait for sessions that finished already
                    if (sessionInfo == null) {
                        Binder.withCleanCallingIdentity(() -> {
                            notifySessionComplete(sessionId, /*success=*/ true);
                        });
                    }
                }
            } catch (Exception e) {
                // Allow calling the callback again
                synchronized (this) {
                    mDependencyInstallerCallbackInvoked = false;
                }
                throw e;
            }
        }

        @Override
@@ -354,7 +376,10 @@ public class InstallDependencyHelper {
                }
                mDependencyInstallerCallbackInvoked = true;
            }

            Binder.withCleanCallingIdentity(() -> {
                onError(mCallback, "Failed to resolve all dependencies automatically");
            });
        }

        private ArraySet<Integer> validateSessionIds(int[] sessionIds) {
@@ -369,7 +394,8 @@ public class InstallDependencyHelper {
                // Continue waiting if session exists and hasn't passed or failed yet.
                if (sessionInfo != null) {
                    if (sessionInfo.isSessionFailed) {
                        throwValidationError("Session already finished: " + sessionId);
                        throw new IllegalArgumentException("Session already finished: "
                                + sessionId);
                    }

                    // Wait for session to finish install if it's not already successful.
@@ -398,25 +424,17 @@ public class InstallDependencyHelper {
                        s -> s.sessionId == sessionId).findFirst().orElse(null);

                if (sessionInfo == null) {
                    throwValidationError("Failed to find session: " + sessionId);
                    throw new IllegalArgumentException("Failed to find session: " + sessionId);
                }

                // Historical session must have been successful, otherwise throw IAE.
                if (!sessionInfo.isSessionApplied) {
                    throwValidationError("Session already finished: " + sessionId);
                    throw new IllegalArgumentException("Session already finished: " + sessionId);
                }
            }

            return validSessionIds;
        }

        private void throwValidationError(String msg) {
            // Allow client to invoke callback again.
            synchronized (this) {
                mDependencyInstallerCallbackInvoked = false;
            }
            throw new IllegalArgumentException(msg);
        }
    }

    /**