1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm; 18 19 import static android.app.role.RoleManager.ROLE_SYSTEM_DEPENDENCY_INSTALLER; 20 import static android.content.pm.PackageInstaller.ACTION_INSTALL_DEPENDENCY; 21 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; 22 import static android.os.Process.SYSTEM_UID; 23 24 import android.annotation.NonNull; 25 import android.app.role.RoleManager; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.pm.PackageInstaller.SessionInfo; 30 import android.content.pm.ResolveInfo; 31 import android.content.pm.SharedLibraryInfo; 32 import android.content.pm.dependencyinstaller.DependencyInstallerCallback; 33 import android.content.pm.dependencyinstaller.IDependencyInstallerCallback; 34 import android.content.pm.dependencyinstaller.IDependencyInstallerService; 35 import android.content.pm.parsing.PackageLite; 36 import android.os.Binder; 37 import android.os.Handler; 38 import android.os.OutcomeReceiver; 39 import android.os.Process; 40 import android.os.RemoteException; 41 import android.os.UserHandle; 42 import android.util.ArrayMap; 43 import android.util.ArraySet; 44 import android.util.Slog; 45 46 import com.android.internal.annotations.GuardedBy; 47 import com.android.internal.infra.AndroidFuture; 48 import com.android.internal.infra.ServiceConnector; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 import java.util.concurrent.TimeUnit; 53 54 /** 55 * Helper class to interact with SDK Dependency Installer service. 56 */ 57 public class InstallDependencyHelper { 58 private static final String TAG = InstallDependencyHelper.class.getSimpleName(); 59 private static final boolean DEBUG = true; 60 // The maximum amount of time to wait before the system unbinds from the verifier. 61 private static final long UNBIND_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(6); 62 private static final long REQUEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1); 63 64 private final Context mContext; 65 private final SharedLibrariesImpl mSharedLibraries; 66 private final PackageInstallerService mPackageInstallerService; 67 @GuardedBy("mTrackers") 68 private final List<DependencyInstallTracker> mTrackers = new ArrayList<>(); 69 @GuardedBy("mRemoteServices") 70 private final ArrayMap<Integer, ServiceConnector<IDependencyInstallerService>> mRemoteServices = 71 new ArrayMap<>(); 72 InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries, PackageInstallerService packageInstallerService)73 InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries, 74 PackageInstallerService packageInstallerService) { 75 mContext = context; 76 mSharedLibraries = sharedLibraries; 77 mPackageInstallerService = packageInstallerService; 78 } 79 resolveLibraryDependenciesIfNeeded(List<SharedLibraryInfo> missingLibraries, PackageLite pkg, Computer snapshot, int userId, Handler handler, OutcomeReceiver<Void, PackageManagerException> origCallback)80 void resolveLibraryDependenciesIfNeeded(List<SharedLibraryInfo> missingLibraries, 81 PackageLite pkg, Computer snapshot, int userId, Handler handler, 82 OutcomeReceiver<Void, PackageManagerException> origCallback) { 83 CallOnceProxy callback = new CallOnceProxy(handler, origCallback); 84 try { 85 resolveLibraryDependenciesIfNeededInternal( 86 missingLibraries, pkg, snapshot, userId, handler, callback); 87 } catch (Exception e) { 88 onError(callback, e.getMessage()); 89 } 90 } 91 92 resolveLibraryDependenciesIfNeededInternal(List<SharedLibraryInfo> missing, PackageLite pkg, Computer snapshot, int userId, Handler handler, CallOnceProxy callback)93 private void resolveLibraryDependenciesIfNeededInternal(List<SharedLibraryInfo> missing, 94 PackageLite pkg, Computer snapshot, int userId, Handler handler, 95 CallOnceProxy callback) { 96 if (missing.isEmpty()) { 97 if (DEBUG) { 98 Slog.d(TAG, "No missing dependency for " + pkg.getPackageName()); 99 } 100 // No need for dependency resolution. Move to installation directly. 101 callback.onResult(null); 102 return; 103 } 104 105 if (DEBUG) { 106 Slog.d(TAG, "Missing dependencies found for pkg: " + pkg.getPackageName() 107 + " user: " + userId); 108 } 109 110 if (!bindToDependencyInstallerIfNeeded(userId, handler, snapshot)) { 111 onError(callback, "Dependency Installer Service not found"); 112 return; 113 } 114 115 IDependencyInstallerCallback serviceCallback = 116 new DependencyInstallerCallbackCallOnce(handler, callback, userId); 117 boolean scheduleSuccess; 118 synchronized (mRemoteServices) { 119 scheduleSuccess = mRemoteServices.get(userId).run(service -> { 120 service.onDependenciesRequired(missing, 121 new DependencyInstallerCallback(serviceCallback.asBinder())); 122 }); 123 } 124 if (!scheduleSuccess) { 125 onError(callback, "Failed to schedule job on Dependency Installer Service"); 126 } 127 } 128 getMissingSharedLibraries(PackageLite pkg)129 List<SharedLibraryInfo> getMissingSharedLibraries(PackageLite pkg) 130 throws PackageManagerException { 131 return mSharedLibraries.collectMissingSharedLibraryInfos(pkg); 132 } 133 notifySessionComplete(int sessionId)134 void notifySessionComplete(int sessionId) { 135 if (DEBUG) { 136 Slog.d(TAG, "Session complete for " + sessionId); 137 } 138 synchronized (mTrackers) { 139 List<DependencyInstallTracker> completedTrackers = new ArrayList<>(); 140 for (DependencyInstallTracker tracker: mTrackers) { 141 if (!tracker.onSessionComplete(sessionId)) { 142 completedTrackers.add(tracker); 143 } 144 } 145 mTrackers.removeAll(completedTrackers); 146 } 147 } 148 onError(CallOnceProxy callback, String msg)149 private static void onError(CallOnceProxy callback, String msg) { 150 PackageManagerException pe = new PackageManagerException( 151 INSTALL_FAILED_MISSING_SHARED_LIBRARY, msg); 152 callback.onError(pe); 153 if (DEBUG) { 154 Slog.i(TAG, "Orig session error: " + msg); 155 } 156 } 157 bindToDependencyInstallerIfNeeded(int userId, Handler handler, Computer snapshot)158 private boolean bindToDependencyInstallerIfNeeded(int userId, Handler handler, 159 Computer snapshot) { 160 synchronized (mRemoteServices) { 161 if (mRemoteServices.containsKey(userId)) { 162 if (DEBUG) { 163 Slog.i(TAG, "DependencyInstallerService for user " + userId + " already bound"); 164 } 165 return true; 166 } 167 } 168 169 Slog.i(TAG, "Attempting to bind to Dependency Installer Service for user " + userId); 170 171 RoleManager roleManager = mContext.getSystemService(RoleManager.class); 172 if (roleManager == null) { 173 Slog.w(TAG, "Cannot find RoleManager system service"); 174 return false; 175 } 176 List<String> holders = roleManager.getRoleHoldersAsUser( 177 ROLE_SYSTEM_DEPENDENCY_INSTALLER, UserHandle.of(userId)); 178 if (holders.isEmpty()) { 179 Slog.w(TAG, "No holders of ROLE_SYSTEM_DEPENDENCY_INSTALLER found for user " + userId); 180 return false; 181 } 182 183 Intent serviceIntent = new Intent(ACTION_INSTALL_DEPENDENCY); 184 serviceIntent.setPackage(holders.getFirst()); 185 List<ResolveInfo> resolvedIntents = snapshot.queryIntentServicesInternal( 186 serviceIntent, /*resolvedType=*/ null, /*flags=*/0, 187 userId, SYSTEM_UID, Process.INVALID_PID, 188 /*includeInstantApps*/ false, /*resolveForStart*/ false); 189 190 if (resolvedIntents.isEmpty()) { 191 Slog.w(TAG, "No package holding ROLE_SYSTEM_DEPENDENCY_INSTALLER found for user " 192 + userId); 193 return false; 194 } 195 196 ResolveInfo resolveInfo = resolvedIntents.getFirst(); 197 ComponentName componentName = resolveInfo.getComponentInfo().getComponentName(); 198 serviceIntent.setComponent(componentName); 199 200 ServiceConnector<IDependencyInstallerService> serviceConnector = 201 new ServiceConnector.Impl<IDependencyInstallerService>(mContext, serviceIntent, 202 Context.BIND_AUTO_CREATE, userId, 203 IDependencyInstallerService.Stub::asInterface) { 204 @Override 205 protected Handler getJobHandler() { 206 return handler; 207 } 208 209 @Override 210 protected long getRequestTimeoutMs() { 211 return REQUEST_TIMEOUT_MILLIS; 212 } 213 214 @Override 215 protected long getAutoDisconnectTimeoutMs() { 216 return UNBIND_TIMEOUT_MILLIS; 217 } 218 }; 219 220 221 synchronized (mRemoteServices) { 222 // Some other thread managed to connect to the service first 223 if (mRemoteServices.containsKey(userId)) { 224 return true; 225 } 226 mRemoteServices.put(userId, serviceConnector); 227 // Block the lock until we connect to the service 228 serviceConnector.setServiceLifecycleCallbacks( 229 new ServiceConnector.ServiceLifecycleCallbacks<>() { 230 @Override 231 public void onDisconnected(@NonNull IDependencyInstallerService service) { 232 Slog.w(TAG, 233 "DependencyInstallerService " + componentName + " is disconnected"); 234 destroy(); 235 } 236 237 @Override 238 public void onBinderDied() { 239 Slog.w(TAG, "DependencyInstallerService " + componentName + " has died"); 240 destroy(); 241 } 242 243 private void destroy() { 244 synchronized (mRemoteServices) { 245 if (mRemoteServices.containsKey(userId)) { 246 mRemoteServices.get(userId).unbind(); 247 mRemoteServices.remove(userId); 248 } 249 } 250 } 251 252 }); 253 AndroidFuture<IDependencyInstallerService> unusedFuture = serviceConnector.connect(); 254 } 255 Slog.i(TAG, "Successfully bound to Dependency Installer Service for user " + userId); 256 return true; 257 } 258 259 /** 260 * Ensure we call one of the outcomes only once, on the right handler. 261 * 262 * Repeated calls will be no-op. 263 */ 264 private static class CallOnceProxy implements OutcomeReceiver<Void, PackageManagerException> { 265 private final Handler mHandler; 266 private final OutcomeReceiver<Void, PackageManagerException> mCallback; 267 @GuardedBy("this") 268 private boolean mCalled = false; 269 CallOnceProxy(Handler handler, OutcomeReceiver<Void, PackageManagerException> callback)270 CallOnceProxy(Handler handler, OutcomeReceiver<Void, PackageManagerException> callback) { 271 mHandler = handler; 272 mCallback = callback; 273 } 274 275 @Override onResult(Void result)276 public void onResult(Void result) { 277 synchronized (this) { 278 if (!mCalled) { 279 mHandler.post(() -> { 280 mCallback.onResult(null); 281 }); 282 mCalled = true; 283 } 284 } 285 } 286 287 @Override onError(@onNull PackageManagerException error)288 public void onError(@NonNull PackageManagerException error) { 289 synchronized (this) { 290 if (!mCalled) { 291 mHandler.post(() -> { 292 mCallback.onError(error); 293 }); 294 mCalled = true; 295 } 296 } 297 } 298 } 299 300 /** 301 * Ensure we call one of the outcomes only once, on the right handler. 302 * 303 * Repeated calls will be no-op. 304 */ 305 private class DependencyInstallerCallbackCallOnce extends IDependencyInstallerCallback.Stub { 306 307 private final Handler mHandler; 308 private final CallOnceProxy mCallback; 309 private final int mUserId; 310 311 @GuardedBy("this") 312 private boolean mDependencyInstallerCallbackInvoked = false; 313 DependencyInstallerCallbackCallOnce(Handler handler, CallOnceProxy callback, int userId)314 DependencyInstallerCallbackCallOnce(Handler handler, CallOnceProxy callback, int userId) { 315 mHandler = handler; 316 mCallback = callback; 317 mUserId = userId; 318 } 319 320 @Override onAllDependenciesResolved(int[] sessionIds)321 public void onAllDependenciesResolved(int[] sessionIds) throws RemoteException { 322 synchronized (this) { 323 if (mDependencyInstallerCallbackInvoked) { 324 throw new IllegalStateException( 325 "Callback is being or has been already processed"); 326 } 327 mDependencyInstallerCallbackInvoked = true; 328 } 329 330 331 if (DEBUG) { 332 Slog.d(TAG, "onAllDependenciesResolved started"); 333 } 334 335 try { 336 // Before creating any tracker, validate the arguments 337 ArraySet<Integer> validSessionIds = validateSessionIds(sessionIds); 338 339 if (validSessionIds.isEmpty()) { 340 mCallback.onResult(null); 341 return; 342 } 343 344 // Create a tracker now if there are any pending sessions remaining. 345 DependencyInstallTracker tracker = new DependencyInstallTracker( 346 mCallback, validSessionIds); 347 synchronized (mTrackers) { 348 mTrackers.add(tracker); 349 } 350 351 // By the time the tracker was created, some of the sessions in validSessionIds 352 // could have finished. Avoid waiting for them indefinitely. 353 for (int sessionId : validSessionIds) { 354 SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId); 355 356 // Don't wait for sessions that finished already 357 if (sessionInfo == null) { 358 Binder.withCleanCallingIdentity(() -> { 359 notifySessionComplete(sessionId); 360 }); 361 } 362 } 363 } catch (Exception e) { 364 // Allow calling the callback again 365 synchronized (this) { 366 mDependencyInstallerCallbackInvoked = false; 367 } 368 throw e; 369 } 370 } 371 372 @Override onFailureToResolveAllDependencies()373 public void onFailureToResolveAllDependencies() throws RemoteException { 374 synchronized (this) { 375 if (mDependencyInstallerCallbackInvoked) { 376 throw new IllegalStateException( 377 "Callback is being or has been already processed"); 378 } 379 mDependencyInstallerCallbackInvoked = true; 380 } 381 382 Binder.withCleanCallingIdentity(() -> { 383 onError(mCallback, "Failed to resolve all dependencies automatically"); 384 }); 385 } 386 validateSessionIds(int[] sessionIds)387 private ArraySet<Integer> validateSessionIds(int[] sessionIds) { 388 // Before creating any tracker, validate the arguments 389 ArraySet<Integer> validSessionIds = new ArraySet<>(); 390 391 List<SessionInfo> historicalSessions = null; 392 for (int i = 0; i < sessionIds.length; i++) { 393 int sessionId = sessionIds[i]; 394 SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId); 395 396 // Continue waiting if session exists and hasn't passed or failed yet. 397 if (sessionInfo != null) { 398 if (sessionInfo.isSessionFailed) { 399 throw new IllegalArgumentException("Session already finished: " 400 + sessionId); 401 } 402 403 // Wait for session to finish install if it's not already successful. 404 if (!sessionInfo.isSessionApplied) { 405 if (DEBUG) { 406 Slog.d(TAG, "onAllDependenciesResolved pending session: " + sessionId); 407 } 408 validSessionIds.add(sessionId); 409 } 410 411 // An applied session found. No need to check historical session anymore. 412 continue; 413 } 414 415 if (DEBUG) { 416 Slog.d(TAG, "onAllDependenciesResolved cleaning up finished" 417 + " session: " + sessionId); 418 } 419 420 if (historicalSessions == null) { 421 historicalSessions = mPackageInstallerService.getHistoricalSessions( 422 mUserId).getList(); 423 } 424 425 sessionInfo = historicalSessions.stream().filter( 426 s -> s.sessionId == sessionId).findFirst().orElse(null); 427 428 if (sessionInfo == null) { 429 throw new IllegalArgumentException("Failed to find session: " + sessionId); 430 } 431 432 // Historical session must have been successful, otherwise throw IAE. 433 if (!sessionInfo.isSessionApplied) { 434 throw new IllegalArgumentException("Session already finished: " + sessionId); 435 } 436 } 437 438 return validSessionIds; 439 } 440 } 441 442 /** 443 * Tracks a list of session ids against a particular callback. 444 * 445 * If all the sessions completes successfully, it invokes the positive flow. If any of the 446 * sessions fails, it invokes the failure flow immediately. 447 */ 448 // TODO(b/372862145): Determine and add support for rebooting while dependency is being resolved 449 private static class DependencyInstallTracker { 450 private final CallOnceProxy mCallback; 451 @GuardedBy("this") 452 private final ArraySet<Integer> mPendingSessionIds; 453 DependencyInstallTracker(CallOnceProxy callback, ArraySet<Integer> pendingSessionIds)454 DependencyInstallTracker(CallOnceProxy callback, ArraySet<Integer> pendingSessionIds) { 455 mCallback = callback; 456 mPendingSessionIds = pendingSessionIds; 457 } 458 459 /** 460 * Process a session complete event. 461 * 462 * Returns true if we still need to continue tracking. 463 */ onSessionComplete(int sessionId)464 public boolean onSessionComplete(int sessionId) { 465 synchronized (this) { 466 if (!mPendingSessionIds.contains(sessionId)) { 467 // This had no impact on tracker, so continue tracking 468 return true; 469 } 470 471 mPendingSessionIds.remove(sessionId); 472 if (mPendingSessionIds.isEmpty()) { 473 mCallback.onResult(null); 474 return false; // Nothing to track anymore 475 } 476 return true; // Keep on tracking 477 } 478 } 479 } 480 } 481