• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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