1 /*
2  * Copyright (C) 2019 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 android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.apex.ApexInfo;
23 import android.apex.ApexInfoList;
24 import android.apex.ApexSessionInfo;
25 import android.apex.ApexSessionParams;
26 import android.apex.CompressedApexInfoList;
27 import android.apex.IApexService;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.SigningDetails;
31 import android.content.pm.parsing.result.ParseResult;
32 import android.content.pm.parsing.result.ParseTypeImpl;
33 import android.os.Binder;
34 import android.os.Environment;
35 import android.os.RemoteException;
36 import android.os.ServiceManager;
37 import android.os.Trace;
38 import android.sysprop.ApexProperties;
39 import android.text.TextUtils;
40 import android.util.ArrayMap;
41 import android.util.ArraySet;
42 import android.util.Singleton;
43 import android.util.Slog;
44 import android.util.SparseArray;
45 import android.util.apk.ApkSignatureVerifier;
46 
47 import com.android.internal.annotations.GuardedBy;
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.internal.util.IndentingPrintWriter;
50 import com.android.internal.util.Preconditions;
51 import com.android.modules.utils.build.UnboundedSdkLevel;
52 import com.android.server.pm.pkg.AndroidPackage;
53 import com.android.server.pm.pkg.component.ParsedApexSystemService;
54 import com.android.server.utils.TimingsTraceAndSlog;
55 
56 import com.google.android.collect.Lists;
57 
58 import java.io.File;
59 import java.io.PrintWriter;
60 import java.lang.annotation.Retention;
61 import java.lang.annotation.RetentionPolicy;
62 import java.nio.file.Path;
63 import java.util.ArrayList;
64 import java.util.Collections;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Objects;
68 import java.util.Set;
69 
70 /**
71  * ApexManager class handles communications with the apex service to perform operation and queries,
72  * as well as providing caching to avoid unnecessary calls to the service.
73  */
74 public abstract class ApexManager {
75 
76     private static final String TAG = "ApexManager";
77 
78     public static final int MATCH_ACTIVE_PACKAGE = 1 << 0;
79     static final int MATCH_FACTORY_PACKAGE = 1 << 1;
80 
81     private static final Singleton<ApexManager> sApexManagerSingleton =
82             new Singleton<ApexManager>() {
83                 @Override
84                 protected ApexManager create() {
85                     if (ApexProperties.updatable().orElse(false)) {
86                         return new ApexManagerImpl();
87                     } else {
88                         return new ApexManagerFlattenedApex();
89                     }
90                 }
91             };
92 
93     /**
94      * Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerFlattenedApex}
95      * depending on whether this device supports APEX, i.e. {@link ApexProperties#updatable()}
96      * evaluates to {@code true}.
97      * @hide
98      */
getInstance()99     public static ApexManager getInstance() {
100         return sApexManagerSingleton.get();
101     }
102 
103     static class ScanResult {
104         public final ApexInfo apexInfo;
105         public final AndroidPackage pkg;
106         public final String packageName;
ScanResult(ApexInfo apexInfo, AndroidPackage pkg, String packageName)107         ScanResult(ApexInfo apexInfo, AndroidPackage pkg, String packageName) {
108             this.apexInfo = apexInfo;
109             this.pkg = pkg;
110             this.packageName = packageName;
111         }
112     }
113 
114     /**
115      * Minimal information about APEX mount points and the original APEX package they refer to.
116      * @hide
117      */
118     public static class ActiveApexInfo {
119         @Nullable public final String apexModuleName;
120         public final File apexDirectory;
121         public final File preInstalledApexPath;
122         public final boolean isFactory;
123         public final File apexFile;
124         public final boolean activeApexChanged;
125 
ActiveApexInfo(File apexDirectory, File preInstalledApexPath, File apexFile)126         private ActiveApexInfo(File apexDirectory, File preInstalledApexPath, File apexFile) {
127             this(null, apexDirectory, preInstalledApexPath, true, apexFile, false);
128         }
129 
ActiveApexInfo(@ullable String apexModuleName, File apexDirectory, File preInstalledApexPath, boolean isFactory, File apexFile, boolean activeApexChanged)130         private ActiveApexInfo(@Nullable String apexModuleName, File apexDirectory,
131                 File preInstalledApexPath, boolean isFactory, File apexFile,
132                 boolean activeApexChanged) {
133             this.apexModuleName = apexModuleName;
134             this.apexDirectory = apexDirectory;
135             this.preInstalledApexPath = preInstalledApexPath;
136             this.isFactory = isFactory;
137             this.apexFile = apexFile;
138             this.activeApexChanged = activeApexChanged;
139         }
140 
ActiveApexInfo(ApexInfo apexInfo)141         public ActiveApexInfo(ApexInfo apexInfo) {
142             this(
143                     apexInfo.moduleName,
144                     new File(Environment.getApexDirectory() + File.separator
145                             + apexInfo.moduleName),
146                     new File(apexInfo.preinstalledModulePath),
147                     apexInfo.isFactory,
148                     new File(apexInfo.modulePath),
149                     apexInfo.activeApexChanged);
150         }
151     }
152 
getAllApexInfos()153     abstract ApexInfo[] getAllApexInfos();
notifyScanResult(List<ScanResult> scanResults)154     abstract void notifyScanResult(List<ScanResult> scanResults);
155 
156     /**
157      * Returns {@link ActiveApexInfo} records relative to all active APEX packages.
158      *
159      * @hide
160      */
getActiveApexInfos()161     public abstract List<ActiveApexInfo> getActiveApexInfos();
162 
163     /**
164      * Returns the active apex package's name that contains the (apk) package.
165      *
166      * @param containedPackageName The (apk) package that might be in a apex
167      * @return the apex package's name of {@code null} if the {@code containedPackage} is not inside
168      *         any apex.
169      */
170     @Nullable
getActiveApexPackageNameContainingPackage( @onNull String containedPackageName)171     public abstract String getActiveApexPackageNameContainingPackage(
172             @NonNull String containedPackageName);
173 
174     /**
175      * Retrieves information about an apexd staged session i.e. the internal state used by apexd to
176      * track the different states of a session.
177      *
178      * @param sessionId the identifier of the session.
179      * @return an ApexSessionInfo object, or null if the session is not known.
180      */
181     @Nullable
getStagedSessionInfo(int sessionId)182     abstract ApexSessionInfo getStagedSessionInfo(int sessionId);
183 
184     /**
185      * Returns array of all staged sessions known to apexd.
186      */
187     @NonNull
getSessions()188     abstract SparseArray<ApexSessionInfo> getSessions();
189 
190     /**
191      * Submit a staged session to apex service. This causes the apex service to perform some initial
192      * verification and accept or reject the session. Submitting a session successfully is not
193      * enough for it to be activated at the next boot, the caller needs to call
194      * {@link #markStagedSessionReady(int)}.
195      *
196      * @throws PackageManagerException if call to apexd fails
197      */
submitStagedSession(ApexSessionParams params)198     abstract ApexInfoList submitStagedSession(ApexSessionParams params)
199             throws PackageManagerException;
200 
201     /**
202      * Returns {@code ApeInfo} about apex sessions that have been marked ready via
203      * {@link #markStagedSessionReady(int)}
204      *
205      * Returns empty array if there is no staged apex session or if there is any error.
206      */
getStagedApexInfos(ApexSessionParams params)207     abstract ApexInfo[] getStagedApexInfos(ApexSessionParams params);
208 
209     /**
210      * Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
211      * applied at next reboot.
212      *
213      * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready.
214      * @throws PackageManagerException if call to apexd fails
215      */
markStagedSessionReady(int sessionId)216     abstract void markStagedSessionReady(int sessionId) throws PackageManagerException;
217 
218     /**
219      * Marks a staged session as successful.
220      *
221      * <p>Only activated session can be marked as successful.
222      *
223      * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as
224      *                  successful.
225      */
markStagedSessionSuccessful(int sessionId)226     abstract void markStagedSessionSuccessful(int sessionId);
227 
228     /**
229      * Whether the current device supports the management of APEX packages.
230      *
231      * @return true if APEX packages can be managed on this device, false otherwise.
232      */
isApexSupported()233     abstract boolean isApexSupported();
234 
235     /**
236      * Abandons the (only) active session previously submitted.
237      *
238      * @return {@code true} upon success, {@code false} if any remote exception occurs
239      */
revertActiveSessions()240     abstract boolean revertActiveSessions();
241 
242     /**
243      * Abandons the staged session with the given sessionId. Client should handle {@code false}
244      * return value carefully as failure here can leave device in inconsistent state.
245      *
246      * @return {@code true} upon success, {@code false} if any exception occurs
247      */
abortStagedSession(int sessionId)248     abstract boolean abortStagedSession(int sessionId);
249 
250     /**
251      * Uninstalls given {@code apexPackage}.
252      *
253      * <p>NOTE. Device must be rebooted in order for uninstall to take effect.
254      *
255      * @param apexPackagePath package to uninstall.
256      * @return {@code true} upon successful uninstall, {@code false} otherwise.
257      */
uninstallApex(String apexPackagePath)258     abstract boolean uninstallApex(String apexPackagePath);
259 
260     /**
261      * Registers an APK package as an embedded apk of apex.
262      */
registerApkInApex(AndroidPackage pkg)263     abstract void registerApkInApex(AndroidPackage pkg);
264 
265     /**
266      * Reports error raised during installation of apk-in-apex.
267      *
268      * @param scanDirPath the directory of the apex inside which apk-in-apex resides.
269      * @param errorMsg the actual error that occurred when scanning the path
270      */
reportErrorWithApkInApex(String scanDirPath, String errorMsg)271     abstract void reportErrorWithApkInApex(String scanDirPath, String errorMsg);
272 
273     /**
274      * Returns null if there were no errors when installing apk-in-apex inside
275      * {@param apexPackageName}, otherwise returns the error as string
276      *
277      * @param apexPackageName Package name of the apk container of apex
278      */
279     @Nullable
getApkInApexInstallError(String apexPackageName)280     abstract String getApkInApexInstallError(String apexPackageName);
281 
282     /**
283      * Returns list of {@code packageName} of apks inside the given apex.
284      * @param apexPackageName Package name of the apk container of apex
285      */
getApksInApex(String apexPackageName)286     abstract List<String> getApksInApex(String apexPackageName);
287 
288     /**
289      * Returns the apex module name for the given package name, if the package is an APEX. Otherwise
290      * returns {@code null}.
291      */
292     @Nullable
getApexModuleNameForPackageName(String apexPackageName)293     public abstract String getApexModuleNameForPackageName(String apexPackageName);
294 
295     /**
296      * Returns the package name of the active APEX whose name is {@code apexModuleName}. If not
297      * found, returns {@code null}.
298      */
299     @Nullable
getActivePackageNameForApexModuleName(String apexModuleName)300     public abstract String getActivePackageNameForApexModuleName(String apexModuleName);
301 
302     /**
303      * Copies the CE apex data directory for the given {@code userId} to a backup location, for use
304      * in case of rollback.
305      *
306      * @return boolean true if the snapshot was successful
307      */
snapshotCeData(int userId, int rollbackId, String apexPackageName)308     public abstract boolean snapshotCeData(int userId, int rollbackId, String apexPackageName);
309 
310     /**
311      * Restores the snapshot of the CE apex data directory for the given {@code userId}.
312      * Note the snapshot will be deleted after restoration succeeded.
313      *
314      * @return boolean true if the restore was successful
315      */
restoreCeData(int userId, int rollbackId, String apexPackageName)316     public abstract boolean restoreCeData(int userId, int rollbackId, String apexPackageName);
317 
318     /**
319      * Deletes snapshots of the device encrypted apex data directories for the given
320      * {@code rollbackId}.
321      *
322      * @return boolean true if the delete was successful
323      */
destroyDeSnapshots(int rollbackId)324     public abstract boolean destroyDeSnapshots(int rollbackId);
325 
326     /**
327      *  Deletes snapshots of the credential encrypted apex data directories for the specified user,
328      *  for the given rollback id as long as the user is credential unlocked.
329      *
330      * @return boolean true if the delete was successful
331      */
destroyCeSnapshots(int userId, int rollbackId)332     public abstract boolean destroyCeSnapshots(int userId, int rollbackId);
333 
334     /**
335      * Deletes snapshots of the credential encrypted apex data directories for the specified user,
336      * where the rollback id is not included in {@code retainRollbackIds} as long as the user is
337      * credential unlocked.
338      *
339      * @return boolean true if the delete was successful
340      */
destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds)341     public abstract boolean destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds);
342 
343     /**
344      * Inform apexd that the boot has completed.
345      */
markBootCompleted()346     public abstract void markBootCompleted();
347 
348     /**
349      * Estimate how much storage space is needed on /data/ for decompressing apexes
350      * @param infoList List of apexes that are compressed in target build.
351      * @return Size, in bytes, the amount of space needed on /data/
352      */
calculateSizeForCompressedApex(CompressedApexInfoList infoList)353     public abstract long calculateSizeForCompressedApex(CompressedApexInfoList infoList)
354             throws RemoteException;
355 
356     /**
357      * Reserve space on /data so that apexes can be decompressed after OTA
358      * @param infoList List of apexes that are compressed in target build.
359      */
reserveSpaceForCompressedApex(CompressedApexInfoList infoList)360     public abstract void reserveSpaceForCompressedApex(CompressedApexInfoList infoList)
361             throws RemoteException;
362 
363     /**
364      * Performs a non-staged install of the given {@code apexFile}.
365      *
366      * @return {@code ApeInfo} about the newly installed APEX package.
367      */
installPackage(File apexFile)368     abstract ApexInfo installPackage(File apexFile) throws PackageManagerException;
369 
370     /**
371      * Get a list of apex system services implemented in an apex.
372      *
373      * <p>The list is sorted by initOrder for consistency.
374      */
getApexSystemServices()375     public abstract List<ApexSystemServiceInfo> getApexSystemServices();
376 
377     /**
378      * Returns an APEX file backing the mount point {@code file} is located on, or {@code null} if
379      * {@code file} doesn't belong to a {@code /apex} mount point.
380      *
381      * <p>Also returns {@code null} if device doesn't support updatable APEX packages.
382      */
383     @Nullable
getBackingApexFile(@onNull File file)384     public abstract File getBackingApexFile(@NonNull File file);
385 
386     /**
387      * Dumps various state information to the provided {@link PrintWriter} object.
388      *
389      * @param pw the {@link PrintWriter} object to send information to.
390      */
dump(PrintWriter pw)391     abstract void dump(PrintWriter pw);
392 
393     @IntDef(
394             flag = true,
395             prefix = { "MATCH_"},
396             value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE})
397     @Retention(RetentionPolicy.SOURCE)
398     @interface PackageInfoFlags{}
399 
400     /**
401      * An implementation of {@link ApexManager} that should be used in case device supports updating
402      * APEX packages.
403      */
404     @VisibleForTesting
405     protected static class ApexManagerImpl extends ApexManager {
406         private final Object mLock = new Object();
407 
408         // TODO(ioffe): this should be either List or ArrayMap.
409         @GuardedBy("mLock")
410         private Set<ActiveApexInfo> mActiveApexInfosCache;
411 
412         /**
413          * Map of all apex system services to the jar files they are contained in.
414          */
415         @GuardedBy("mLock")
416         private List<ApexSystemServiceInfo> mApexSystemServices = new ArrayList<>();
417 
418         /**
419          * Contains the list of {@code packageName}s of apks-in-apex for given
420          * {@code apexModuleName}. See {@link #mPackageNameToApexModuleName} to understand the
421          * difference between {@code packageName} and {@code apexModuleName}.
422          */
423         @GuardedBy("mLock")
424         private ArrayMap<String, List<String>> mApksInApex = new ArrayMap<>();
425 
426         /**
427          * Contains the list of {@code Exception}s that were raised when installing apk-in-apex
428          * inside {@code apexModuleName}.
429          */
430         @GuardedBy("mLock")
431         private Map<String, String> mErrorWithApkInApex = new ArrayMap<>();
432 
433         /**
434          * An APEX is a file format that delivers the apex-payload wrapped in an apk container. The
435          * apk container has a reference name, called {@code packageName}, which is found inside the
436          * {@code AndroidManifest.xml}. The apex payload inside the container also has a reference
437          * name, called {@code apexModuleName}, which is found in {@code apex_manifest.json} file.
438          *
439          * {@link #mPackageNameToApexModuleName} contains the mapping from {@code packageName} of
440          * the apk container to {@code apexModuleName} of the apex-payload inside.
441          */
442         @GuardedBy("mLock")
443         private ArrayMap<String, String> mPackageNameToApexModuleName;
444 
445         /**
446          * Reverse mapping of {@link #mPackageNameToApexModuleName}, for active packages only.
447          */
448         @GuardedBy("mLock")
449         private ArrayMap<String, String> mApexModuleNameToActivePackageName;
450 
451         /**
452          * Retrieve the service from ServiceManager. If the service is not running, it will be
453          * started, and this function will block until it is ready.
454          */
455         @VisibleForTesting
waitForApexService()456         protected IApexService waitForApexService() {
457             // Since apexd is a trusted platform component, synchronized calls are allowable
458             return IApexService.Stub.asInterface(
459                     Binder.allowBlocking(ServiceManager.waitForService("apexservice")));
460         }
461 
462         @Override
getAllApexInfos()463         ApexInfo[] getAllApexInfos() {
464             try {
465                 return waitForApexService().getAllPackages();
466             } catch (RemoteException re) {
467                 Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
468                 throw new RuntimeException(re);
469             }
470         }
471 
472         @Override
notifyScanResult(List<ScanResult> scanResults)473         void notifyScanResult(List<ScanResult> scanResults) {
474             synchronized (mLock) {
475                 notifyScanResultLocked(scanResults);
476             }
477         }
478 
479         @GuardedBy("mLock")
notifyScanResultLocked(List<ScanResult> scanResults)480         private void notifyScanResultLocked(List<ScanResult> scanResults) {
481             mPackageNameToApexModuleName = new ArrayMap<>();
482             mApexModuleNameToActivePackageName = new ArrayMap<>();
483             for (ScanResult scanResult : scanResults) {
484                 ApexInfo ai = scanResult.apexInfo;
485                 String packageName = scanResult.packageName;
486                 for (ParsedApexSystemService service :
487                         scanResult.pkg.getApexSystemServices()) {
488                     String minSdkVersion = service.getMinSdkVersion();
489                     if (minSdkVersion != null && !UnboundedSdkLevel.isAtLeast(minSdkVersion)) {
490                         Slog.d(TAG, String.format(
491                                 "ApexSystemService %s with min_sdk_version=%s is skipped",
492                                 service.getName(), service.getMinSdkVersion()));
493                         continue;
494                     }
495                     String maxSdkVersion = service.getMaxSdkVersion();
496                     if (maxSdkVersion != null && !UnboundedSdkLevel.isAtMost(maxSdkVersion)) {
497                         Slog.d(TAG, String.format(
498                                 "ApexSystemService %s with max_sdk_version=%s is skipped",
499                                 service.getName(), service.getMaxSdkVersion()));
500                         continue;
501                     }
502 
503                     if (ai.isActive) {
504                         String name = service.getName();
505                         for (int j = 0; j < mApexSystemServices.size(); j++) {
506                             ApexSystemServiceInfo info = mApexSystemServices.get(j);
507                             if (info.getName().equals(name)) {
508                                 throw new IllegalStateException(TextUtils.formatSimple(
509                                         "Duplicate apex-system-service %s from %s, %s", name,
510                                         info.mJarPath, service.getJarPath()));
511                             }
512                         }
513                         ApexSystemServiceInfo info = new ApexSystemServiceInfo(
514                                 service.getName(), service.getJarPath(),
515                                 service.getInitOrder());
516                         mApexSystemServices.add(info);
517                     }
518                 }
519                 Collections.sort(mApexSystemServices);
520                 mPackageNameToApexModuleName.put(packageName, ai.moduleName);
521                 if (ai.isActive) {
522                     if (mApexModuleNameToActivePackageName.containsKey(ai.moduleName)) {
523                         throw new IllegalStateException(
524                                 "Two active packages have the same APEX module name: "
525                                         + ai.moduleName);
526                     }
527                     mApexModuleNameToActivePackageName.put(
528                             ai.moduleName, packageName);
529                 }
530             }
531         }
532 
533         @Override
getActiveApexInfos()534         public List<ActiveApexInfo> getActiveApexInfos() {
535             final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
536                     Trace.TRACE_TAG_PACKAGE_MANAGER);
537             synchronized (mLock) {
538                 if (mActiveApexInfosCache == null) {
539                     t.traceBegin("getActiveApexInfos_noCache");
540                     try {
541                         mActiveApexInfosCache = new ArraySet<>();
542                         final ApexInfo[] activePackages = waitForApexService().getActivePackages();
543                         for (int i = 0; i < activePackages.length; i++) {
544                             ApexInfo apexInfo = activePackages[i];
545                             mActiveApexInfosCache.add(new ActiveApexInfo(apexInfo));
546                         }
547                     } catch (RemoteException e) {
548                         Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
549                     }
550                     t.traceEnd();
551                 }
552                 if (mActiveApexInfosCache != null) {
553                     return new ArrayList<>(mActiveApexInfosCache);
554                 } else {
555                     return Collections.emptyList();
556                 }
557             }
558         }
559 
560         @Override
561         @Nullable
getActiveApexPackageNameContainingPackage(String containedPackageName)562         public String getActiveApexPackageNameContainingPackage(String containedPackageName) {
563             Objects.requireNonNull(containedPackageName);
564             synchronized (mLock) {
565                 Preconditions.checkState(mPackageNameToApexModuleName != null,
566                         "APEX packages have not been scanned");
567                 int numApksInApex = mApksInApex.size();
568                 for (int apkInApexNum = 0; apkInApexNum < numApksInApex; apkInApexNum++) {
569                     if (mApksInApex.valueAt(apkInApexNum).contains(containedPackageName)) {
570                         String apexModuleName = mApksInApex.keyAt(apkInApexNum);
571 
572                         int numApexPkgs = mPackageNameToApexModuleName.size();
573                         for (int apexPkgNum = 0; apexPkgNum < numApexPkgs; apexPkgNum++) {
574                             if (mPackageNameToApexModuleName.valueAt(apexPkgNum).equals(
575                                     apexModuleName)) {
576                                 return mPackageNameToApexModuleName.keyAt(apexPkgNum);
577                             }
578                         }
579                     }
580                 }
581             }
582 
583             return null;
584         }
585 
586         @Override
getStagedSessionInfo(int sessionId)587         @Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
588             try {
589                 ApexSessionInfo apexSessionInfo =
590                         waitForApexService().getStagedSessionInfo(sessionId);
591                 if (apexSessionInfo.isUnknown) {
592                     return null;
593                 }
594                 return apexSessionInfo;
595             } catch (RemoteException re) {
596                 Slog.e(TAG, "Unable to contact apexservice", re);
597                 throw new RuntimeException(re);
598             }
599         }
600 
601         @Override
getSessions()602         SparseArray<ApexSessionInfo> getSessions() {
603             try {
604                 final ApexSessionInfo[] sessions = waitForApexService().getSessions();
605                 final SparseArray<ApexSessionInfo> result = new SparseArray<>(sessions.length);
606                 for (int i = 0; i < sessions.length; i++) {
607                     result.put(sessions[i].sessionId, sessions[i]);
608                 }
609                 return result;
610             } catch (RemoteException re) {
611                 Slog.e(TAG, "Unable to contact apexservice", re);
612                 throw new RuntimeException(re);
613             }
614         }
615 
616         @Override
submitStagedSession(ApexSessionParams params)617         ApexInfoList submitStagedSession(ApexSessionParams params) throws PackageManagerException {
618             try {
619                 final ApexInfoList apexInfoList = new ApexInfoList();
620                 waitForApexService().submitStagedSession(params, apexInfoList);
621                 return apexInfoList;
622             } catch (RemoteException re) {
623                 Slog.e(TAG, "Unable to contact apexservice", re);
624                 throw new RuntimeException(re);
625             } catch (Exception e) {
626                 throw new PackageManagerException(
627                         PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
628                         "apexd verification failed : " + e.getMessage());
629             }
630         }
631 
632         @Override
getStagedApexInfos(ApexSessionParams params)633         ApexInfo[] getStagedApexInfos(ApexSessionParams params) {
634             try {
635                 return waitForApexService().getStagedApexInfos(params);
636             } catch (RemoteException re) {
637                 Slog.w(TAG, "Unable to contact apexservice" + re.getMessage());
638                 throw new RuntimeException(re);
639             } catch (Exception e) {
640                 Slog.w(TAG, "Failed to collect staged apex infos" + e.getMessage());
641                 return new ApexInfo[0];
642             }
643         }
644 
645         @Override
markStagedSessionReady(int sessionId)646         void markStagedSessionReady(int sessionId) throws PackageManagerException {
647             try {
648                 waitForApexService().markStagedSessionReady(sessionId);
649             } catch (RemoteException re) {
650                 Slog.e(TAG, "Unable to contact apexservice", re);
651                 throw new RuntimeException(re);
652             } catch (Exception e) {
653                 throw new PackageManagerException(
654                         PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
655                         "Failed to mark apexd session as ready : " + e.getMessage());
656             }
657         }
658 
659         @Override
markStagedSessionSuccessful(int sessionId)660         void markStagedSessionSuccessful(int sessionId) {
661             try {
662                 waitForApexService().markStagedSessionSuccessful(sessionId);
663             } catch (RemoteException re) {
664                 Slog.e(TAG, "Unable to contact apexservice", re);
665                 throw new RuntimeException(re);
666             } catch (Exception e) {
667                 // It is fine to just log an exception in this case. APEXd will be able to recover
668                 // in case markStagedSessionSuccessful fails.
669                 Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e);
670             }
671         }
672 
673         @Override
isApexSupported()674         boolean isApexSupported() {
675             return true;
676         }
677 
678         @Override
revertActiveSessions()679         boolean revertActiveSessions() {
680             try {
681                 waitForApexService().revertActiveSessions();
682                 return true;
683             } catch (RemoteException re) {
684                 Slog.e(TAG, "Unable to contact apexservice", re);
685                 return false;
686             } catch (Exception e) {
687                 Slog.e(TAG, e.getMessage(), e);
688                 return false;
689             }
690         }
691 
692         @Override
abortStagedSession(int sessionId)693         boolean abortStagedSession(int sessionId) {
694             try {
695                 waitForApexService().abortStagedSession(sessionId);
696                 return true;
697             } catch (Exception e) {
698                 Slog.e(TAG, e.getMessage(), e);
699                 return false;
700             }
701         }
702 
703         @Override
uninstallApex(String apexPackagePath)704         boolean uninstallApex(String apexPackagePath) {
705             try {
706                 waitForApexService().unstagePackages(Collections.singletonList(apexPackagePath));
707                 return true;
708             } catch (Exception e) {
709                 return false;
710             }
711         }
712 
713         @Override
registerApkInApex(AndroidPackage pkg)714         void registerApkInApex(AndroidPackage pkg) {
715             synchronized (mLock) {
716                 for (ActiveApexInfo aai : mActiveApexInfosCache) {
717                     if (pkg.getBaseApkPath().startsWith(
718                             aai.apexDirectory.getAbsolutePath() + File.separator)) {
719                         List<String> apks = mApksInApex.get(aai.apexModuleName);
720                         if (apks == null) {
721                             apks = Lists.newArrayList();
722                             mApksInApex.put(aai.apexModuleName, apks);
723                         }
724                         Slog.i(TAG, "Registering " + pkg.getPackageName() + " as apk-in-apex of "
725                                 + aai.apexModuleName);
726                         apks.add(pkg.getPackageName());
727                     }
728                 }
729             }
730         }
731 
732         @Override
reportErrorWithApkInApex(String scanDirPath, String errorMsg)733         void reportErrorWithApkInApex(String scanDirPath, String errorMsg) {
734             synchronized (mLock) {
735                 for (ActiveApexInfo aai : mActiveApexInfosCache) {
736                     if (scanDirPath.startsWith(aai.apexDirectory.getAbsolutePath())) {
737                         mErrorWithApkInApex.put(aai.apexModuleName, errorMsg);
738                     }
739                 }
740             }
741         }
742 
743         @Override
744         @Nullable
getApkInApexInstallError(String apexPackageName)745         String getApkInApexInstallError(String apexPackageName) {
746             synchronized (mLock) {
747                 Preconditions.checkState(mPackageNameToApexModuleName != null,
748                         "APEX packages have not been scanned");
749                 String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
750                 if (moduleName == null) {
751                     return null;
752                 }
753                 return mErrorWithApkInApex.get(moduleName);
754             }
755         }
756 
757         @Override
getApksInApex(String apexPackageName)758         List<String> getApksInApex(String apexPackageName) {
759             synchronized (mLock) {
760                 Preconditions.checkState(mPackageNameToApexModuleName != null,
761                         "APEX packages have not been scanned");
762                 String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
763                 if (moduleName == null) {
764                     return Collections.emptyList();
765                 }
766                 return mApksInApex.getOrDefault(moduleName, Collections.emptyList());
767             }
768         }
769 
770         @Override
771         @Nullable
getApexModuleNameForPackageName(String apexPackageName)772         public String getApexModuleNameForPackageName(String apexPackageName) {
773             synchronized (mLock) {
774                 Preconditions.checkState(mPackageNameToApexModuleName != null,
775                         "APEX packages have not been scanned");
776                 return mPackageNameToApexModuleName.get(apexPackageName);
777             }
778         }
779 
780         @Override
781         @Nullable
getActivePackageNameForApexModuleName(String apexModuleName)782         public String getActivePackageNameForApexModuleName(String apexModuleName) {
783             synchronized (mLock) {
784                 Preconditions.checkState(mApexModuleNameToActivePackageName != null,
785                         "APEX packages have not been scanned");
786                 return mApexModuleNameToActivePackageName.get(apexModuleName);
787             }
788         }
789 
790         @Override
snapshotCeData(int userId, int rollbackId, String apexPackageName)791         public boolean snapshotCeData(int userId, int rollbackId, String apexPackageName) {
792             String apexModuleName;
793             synchronized (mLock) {
794                 Preconditions.checkState(mPackageNameToApexModuleName != null,
795                         "APEX packages have not been scanned");
796                 apexModuleName = mPackageNameToApexModuleName.get(apexPackageName);
797             }
798             if (apexModuleName == null) {
799                 Slog.e(TAG, "Invalid apex package name: " + apexPackageName);
800                 return false;
801             }
802             try {
803                 waitForApexService().snapshotCeData(userId, rollbackId, apexModuleName);
804                 return true;
805             } catch (Exception e) {
806                 Slog.e(TAG, e.getMessage(), e);
807                 return false;
808             }
809         }
810 
811         @Override
restoreCeData(int userId, int rollbackId, String apexPackageName)812         public boolean restoreCeData(int userId, int rollbackId, String apexPackageName) {
813             String apexModuleName;
814             synchronized (mLock) {
815                 Preconditions.checkState(mPackageNameToApexModuleName != null,
816                         "APEX packages have not been scanned");
817                 apexModuleName = mPackageNameToApexModuleName.get(apexPackageName);
818             }
819             if (apexModuleName == null) {
820                 Slog.e(TAG, "Invalid apex package name: " + apexPackageName);
821                 return false;
822             }
823             try {
824                 waitForApexService().restoreCeData(userId, rollbackId, apexModuleName);
825                 return true;
826             } catch (Exception e) {
827                 Slog.e(TAG, e.getMessage(), e);
828                 return false;
829             }
830         }
831 
832         @Override
destroyDeSnapshots(int rollbackId)833         public boolean destroyDeSnapshots(int rollbackId) {
834             try {
835                 waitForApexService().destroyDeSnapshots(rollbackId);
836                 return true;
837             } catch (Exception e) {
838                 Slog.e(TAG, e.getMessage(), e);
839                 return false;
840             }
841         }
842 
843         @Override
destroyCeSnapshots(int userId, int rollbackId)844         public boolean destroyCeSnapshots(int userId, int rollbackId) {
845             try {
846                 waitForApexService().destroyCeSnapshots(userId, rollbackId);
847                 return true;
848             } catch (Exception e) {
849                 Slog.e(TAG, e.getMessage(), e);
850                 return false;
851             }
852         }
853 
854         @Override
destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds)855         public boolean destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds) {
856             try {
857                 waitForApexService().destroyCeSnapshotsNotSpecified(userId, retainRollbackIds);
858                 return true;
859             } catch (Exception e) {
860                 Slog.e(TAG, e.getMessage(), e);
861                 return false;
862             }
863         }
864 
865         @Override
markBootCompleted()866         public void markBootCompleted() {
867             try {
868                 waitForApexService().markBootCompleted();
869             } catch (RemoteException re) {
870                 Slog.e(TAG, "Unable to contact apexservice", re);
871             }
872         }
873 
874         @Override
calculateSizeForCompressedApex(CompressedApexInfoList infoList)875         public long calculateSizeForCompressedApex(CompressedApexInfoList infoList)
876                 throws RemoteException {
877             return waitForApexService().calculateSizeForCompressedApex(infoList);
878         }
879 
880         @Override
reserveSpaceForCompressedApex(CompressedApexInfoList infoList)881         public void reserveSpaceForCompressedApex(CompressedApexInfoList infoList)
882                 throws RemoteException {
883             waitForApexService().reserveSpaceForCompressedApex(infoList);
884         }
885 
getSigningDetails(PackageInfo pkg)886         private SigningDetails getSigningDetails(PackageInfo pkg) throws PackageManagerException {
887             final int minSignatureScheme =
888                     ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
889                             pkg.applicationInfo.targetSdkVersion);
890             final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
891             final ParseResult<SigningDetails> result = ApkSignatureVerifier.verify(
892                     input, pkg.applicationInfo.sourceDir, minSignatureScheme);
893             if (result.isError()) {
894                 throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
895                         result.getException());
896             }
897             return result.getResult();
898         }
899 
checkApexSignature(PackageInfo existingApexPkg, PackageInfo newApexPkg)900         private void checkApexSignature(PackageInfo existingApexPkg, PackageInfo newApexPkg)
901                 throws PackageManagerException {
902             final SigningDetails existingSigningDetails = getSigningDetails(existingApexPkg);
903             final SigningDetails newSigningDetails = getSigningDetails(newApexPkg);
904             if (!newSigningDetails.checkCapability(existingSigningDetails,
905                       SigningDetails.CertCapabilities.INSTALLED_DATA)) {
906                 throw new PackageManagerException(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
907                           "APK container signature of " + newApexPkg.applicationInfo.sourceDir
908                                    + " is not compatible with currently installed on device");
909             }
910         }
911 
912         @Override
installPackage(File apexFile)913         ApexInfo installPackage(File apexFile)
914                 throws PackageManagerException {
915             try {
916                 return waitForApexService().installAndActivatePackage(apexFile.getAbsolutePath());
917             } catch (RemoteException e) {
918                 throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
919                         "apexservice not available");
920             } catch (Exception e) {
921                 // TODO(b/187864524): is INSTALL_FAILED_INTERNAL_ERROR is the right error code here?
922                 throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
923                         e.getMessage());
924             }
925         }
926 
927         @Override
getApexSystemServices()928         public List<ApexSystemServiceInfo> getApexSystemServices() {
929             synchronized (mLock) {
930                 Preconditions.checkState(mApexSystemServices != null,
931                         "APEX packages have not been scanned");
932                 return mApexSystemServices;
933             }
934         }
935 
936         @Override
getBackingApexFile(File file)937         public File getBackingApexFile(File file) {
938             Path path = file.toPath();
939             if (!path.startsWith(Environment.getApexDirectory().toPath())) {
940                 return null;
941             }
942             if (path.getNameCount() < 2) {
943                 return null;
944             }
945             String moduleName = file.toPath().getName(1).toString();
946             final List<ActiveApexInfo> apexes = getActiveApexInfos();
947             for (int i = 0; i < apexes.size(); i++) {
948                 if (apexes.get(i).apexModuleName.equals(moduleName)) {
949                     return apexes.get(i).apexFile;
950                 }
951             }
952             return null;
953         }
954 
955         @Override
dump(PrintWriter pw)956         void dump(PrintWriter pw) {
957             final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
958             try {
959                 ipw.println();
960                 ipw.println("APEX session state:");
961                 ipw.increaseIndent();
962                 final ApexSessionInfo[] sessions = waitForApexService().getSessions();
963                 for (ApexSessionInfo si : sessions) {
964                     ipw.println("Session ID: " + si.sessionId);
965                     ipw.increaseIndent();
966                     if (si.isUnknown) {
967                         ipw.println("State: UNKNOWN");
968                     } else if (si.isVerified) {
969                         ipw.println("State: VERIFIED");
970                     } else if (si.isStaged) {
971                         ipw.println("State: STAGED");
972                     } else if (si.isActivated) {
973                         ipw.println("State: ACTIVATED");
974                     } else if (si.isActivationFailed) {
975                         ipw.println("State: ACTIVATION FAILED");
976                     } else if (si.isSuccess) {
977                         ipw.println("State: SUCCESS");
978                     } else if (si.isRevertInProgress) {
979                         ipw.println("State: REVERT IN PROGRESS");
980                     } else if (si.isReverted) {
981                         ipw.println("State: REVERTED");
982                     } else if (si.isRevertFailed) {
983                         ipw.println("State: REVERT FAILED");
984                     }
985                     ipw.decreaseIndent();
986                 }
987                 ipw.decreaseIndent();
988                 ipw.println();
989             } catch (RemoteException e) {
990                 ipw.println("Couldn't communicate with apexd.");
991             }
992         }
993     }
994 
995     /**
996      * An implementation of {@link ApexManager} that should be used in case device does not support
997      * updating APEX packages.
998      */
999     @VisibleForTesting
1000     static final class ApexManagerFlattenedApex extends ApexManager {
1001         @Override
getAllApexInfos()1002         ApexInfo[] getAllApexInfos() {
1003             return null;
1004         }
1005 
1006         @Override
notifyScanResult(List<ScanResult> scanResults)1007         void notifyScanResult(List<ScanResult> scanResults) {
1008             // No-op
1009         }
1010 
1011         @Override
getActiveApexInfos()1012         public List<ActiveApexInfo> getActiveApexInfos() {
1013             // There is no apexd running in case of flattened apex
1014             // We look up the /apex directory and identify the active APEX modules from there.
1015             // As "preinstalled" path, we just report /system since in the case of flattened APEX
1016             // the /apex directory is just a symlink to /system/apex.
1017             List<ActiveApexInfo> result = new ArrayList<>();
1018             File apexDir = Environment.getApexDirectory();
1019             if (apexDir.isDirectory()) {
1020                 File[] files = apexDir.listFiles();
1021                 // listFiles might be null if system server doesn't have permission to read
1022                 // a directory.
1023                 if (files != null) {
1024                     for (File file : files) {
1025                         if (file.isDirectory() && !file.getName().contains("@")
1026                                 // In flattened configuration, init special-cases the art directory
1027                                 // and bind-mounts com.android.art.debug to com.android.art.
1028                                 && !file.getName().equals("com.android.art.debug")) {
1029                             result.add(
1030                                     new ActiveApexInfo(file, Environment.getRootDirectory(), file));
1031                         }
1032                     }
1033                 }
1034             }
1035             return result;
1036         }
1037 
1038         @Override
1039         @Nullable
getActiveApexPackageNameContainingPackage( @onNull String containedPackageName)1040         public String getActiveApexPackageNameContainingPackage(
1041                 @NonNull String containedPackageName) {
1042             Objects.requireNonNull(containedPackageName);
1043 
1044             return null;
1045         }
1046 
1047         @Override
getStagedSessionInfo(int sessionId)1048         ApexSessionInfo getStagedSessionInfo(int sessionId) {
1049             throw new UnsupportedOperationException();
1050         }
1051 
1052         @Override
getSessions()1053         SparseArray<ApexSessionInfo> getSessions() {
1054             return new SparseArray<>(0);
1055         }
1056 
1057         @Override
submitStagedSession(ApexSessionParams params)1058         ApexInfoList submitStagedSession(ApexSessionParams params)
1059                 throws PackageManagerException {
1060             throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
1061                     "Device doesn't support updating APEX");
1062         }
1063 
1064         @Override
getStagedApexInfos(ApexSessionParams params)1065         ApexInfo[] getStagedApexInfos(ApexSessionParams params) {
1066             throw new UnsupportedOperationException();
1067         }
1068 
1069         @Override
markStagedSessionReady(int sessionId)1070         void markStagedSessionReady(int sessionId) {
1071             throw new UnsupportedOperationException();
1072         }
1073 
1074         @Override
markStagedSessionSuccessful(int sessionId)1075         void markStagedSessionSuccessful(int sessionId) {
1076             throw new UnsupportedOperationException();
1077         }
1078 
1079         @Override
isApexSupported()1080         boolean isApexSupported() {
1081             return false;
1082         }
1083 
1084         @Override
revertActiveSessions()1085         boolean revertActiveSessions() {
1086             throw new UnsupportedOperationException();
1087         }
1088 
1089         @Override
abortStagedSession(int sessionId)1090         boolean abortStagedSession(int sessionId) {
1091             throw new UnsupportedOperationException();
1092         }
1093 
1094         @Override
uninstallApex(String apexPackagePath)1095         boolean uninstallApex(String apexPackagePath) {
1096             throw new UnsupportedOperationException();
1097         }
1098 
1099         @Override
registerApkInApex(AndroidPackage pkg)1100         void registerApkInApex(AndroidPackage pkg) {
1101             // No-op
1102         }
1103 
1104         @Override
reportErrorWithApkInApex(String scanDirPath, String errorMsg)1105         void reportErrorWithApkInApex(String scanDirPath, String errorMsg) {
1106             // No-op
1107         }
1108 
1109         @Override
1110         @Nullable
getApkInApexInstallError(String apexPackageName)1111         String getApkInApexInstallError(String apexPackageName) {
1112             return null;
1113         }
1114 
1115         @Override
getApksInApex(String apexPackageName)1116         List<String> getApksInApex(String apexPackageName) {
1117             return Collections.emptyList();
1118         }
1119 
1120         @Override
1121         @Nullable
getApexModuleNameForPackageName(String apexPackageName)1122         public String getApexModuleNameForPackageName(String apexPackageName) {
1123             return null;
1124         }
1125 
1126         @Override
1127         @Nullable
getActivePackageNameForApexModuleName(String apexModuleName)1128         public String getActivePackageNameForApexModuleName(String apexModuleName) {
1129             return null;
1130         }
1131 
1132         @Override
snapshotCeData(int userId, int rollbackId, String apexPackageName)1133         public boolean snapshotCeData(int userId, int rollbackId, String apexPackageName) {
1134             throw new UnsupportedOperationException();
1135         }
1136 
1137         @Override
restoreCeData(int userId, int rollbackId, String apexPackageName)1138         public boolean restoreCeData(int userId, int rollbackId, String apexPackageName) {
1139             throw new UnsupportedOperationException();
1140         }
1141 
1142         @Override
destroyDeSnapshots(int rollbackId)1143         public boolean destroyDeSnapshots(int rollbackId) {
1144             throw new UnsupportedOperationException();
1145         }
1146 
1147         @Override
destroyCeSnapshots(int userId, int rollbackId)1148         public boolean destroyCeSnapshots(int userId, int rollbackId) {
1149             return true;
1150         }
1151 
1152         @Override
destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds)1153         public boolean destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds) {
1154             return true;
1155         }
1156 
1157         @Override
markBootCompleted()1158         public void markBootCompleted() {
1159             // No-op
1160         }
1161 
1162         @Override
calculateSizeForCompressedApex(CompressedApexInfoList infoList)1163         public long calculateSizeForCompressedApex(CompressedApexInfoList infoList) {
1164             throw new UnsupportedOperationException();
1165         }
1166 
1167         @Override
reserveSpaceForCompressedApex(CompressedApexInfoList infoList)1168         public void reserveSpaceForCompressedApex(CompressedApexInfoList infoList) {
1169             throw new UnsupportedOperationException();
1170         }
1171 
1172         @Override
installPackage(File apexFile)1173         ApexInfo installPackage(File apexFile) {
1174             throw new UnsupportedOperationException("APEX updates are not supported");
1175         }
1176 
1177         @Override
getApexSystemServices()1178         public List<ApexSystemServiceInfo> getApexSystemServices() {
1179             // TODO(satayev): we can't really support flattened apex use case, and need to migrate
1180             // the manifest entries into system's manifest asap.
1181             return Collections.emptyList();
1182         }
1183 
1184         @Override
dump(PrintWriter pw)1185         void dump(PrintWriter pw) {
1186         }
1187 
1188         @Override
getBackingApexFile(File file)1189         public File getBackingApexFile(File file) {
1190             return null;
1191         }
1192     }
1193 }
1194