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.s
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.IApexService;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.pm.ApplicationInfo;
31 import android.content.pm.PackageInfo;
32 import android.content.pm.PackageManager;
33 import android.content.pm.PackageParser;
34 import android.content.pm.PackageParser.PackageParserException;
35 import android.os.RemoteException;
36 import android.os.ServiceManager;
37 import android.os.ServiceManager.ServiceNotFoundException;
38 import android.sysprop.ApexProperties;
39 import android.util.ArrayMap;
40 import android.util.Slog;
41 
42 import com.android.internal.annotations.GuardedBy;
43 import com.android.internal.util.IndentingPrintWriter;
44 
45 import java.io.File;
46 import java.io.PrintWriter;
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.stream.Collectors;
54 
55 /**
56  * ApexManager class handles communications with the apex service to perform operation and queries,
57  * as well as providing caching to avoid unnecessary calls to the service.
58  */
59 class ApexManager {
60     static final String TAG = "ApexManager";
61     private final IApexService mApexService;
62     private final Context mContext;
63     private final Object mLock = new Object();
64     /**
65      * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
66      * AndroidManifest.xml}
67      *
68      * <p>Note that key of this map is {@code packageName} field of the corresponding {@code
69      * AndroidManifest.xml}.
70       */
71     @GuardedBy("mLock")
72     private List<PackageInfo> mAllPackagesCache;
73     /**
74      * A map from {@code apexName} to the {@Link PackageInfo} generated from the {@code
75      * AndroidManifest.xml}.
76      *
77      * <p>Note that key of this map is {@code apexName} field which corresponds to the {@code name}
78      * field of {@code apex_manifest.json}.
79      */
80     // TODO(b/132324953): remove.
81     @GuardedBy("mLock")
82     private ArrayMap<String, PackageInfo> mApexNameToPackageInfoCache;
83 
84 
ApexManager(Context context)85     ApexManager(Context context) {
86         try {
87             mApexService = IApexService.Stub.asInterface(
88                 ServiceManager.getServiceOrThrow("apexservice"));
89         } catch (ServiceNotFoundException e) {
90             throw new IllegalStateException("Required service apexservice not available");
91         }
92         mContext = context;
93     }
94 
95     static final int MATCH_ACTIVE_PACKAGE = 1 << 0;
96     static final int MATCH_FACTORY_PACKAGE = 1 << 1;
97     @IntDef(
98             flag = true,
99             prefix = { "MATCH_"},
100             value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE})
101     @Retention(RetentionPolicy.SOURCE)
102     @interface PackageInfoFlags{}
103 
systemReady()104     void systemReady() {
105         mContext.registerReceiver(new BroadcastReceiver() {
106             @Override
107             public void onReceive(Context context, Intent intent) {
108                 onBootCompleted();
109                 mContext.unregisterReceiver(this);
110             }
111         }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
112     }
113 
populateAllPackagesCacheIfNeeded()114     private void populateAllPackagesCacheIfNeeded() {
115         synchronized (mLock) {
116             if (mAllPackagesCache != null) {
117                 return;
118             }
119             mApexNameToPackageInfoCache = new ArrayMap<>();
120             try {
121                 mAllPackagesCache = new ArrayList<>();
122                 HashSet<String> activePackagesSet = new HashSet<>();
123                 HashSet<String> factoryPackagesSet = new HashSet<>();
124                 final ApexInfo[] allPkgs = mApexService.getAllPackages();
125                 for (ApexInfo ai : allPkgs) {
126                     // If the device is using flattened APEX, don't report any APEX
127                     // packages since they won't be managed or updated by PackageManager.
128                     if ((new File(ai.packagePath)).isDirectory()) {
129                         break;
130                     }
131                     try {
132                         final PackageInfo pkg = PackageParser.generatePackageInfoFromApex(
133                                 ai, PackageManager.GET_META_DATA
134                                         | PackageManager.GET_SIGNING_CERTIFICATES);
135                         mAllPackagesCache.add(pkg);
136                         if (ai.isActive) {
137                             if (activePackagesSet.contains(pkg.packageName)) {
138                                 throw new IllegalStateException(
139                                         "Two active packages have the same name: "
140                                                 + pkg.packageName);
141                             }
142                             activePackagesSet.add(ai.packageName);
143                             // TODO(b/132324953): remove.
144                             mApexNameToPackageInfoCache.put(ai.packageName, pkg);
145                         }
146                         if (ai.isFactory) {
147                             if (factoryPackagesSet.contains(pkg.packageName)) {
148                                 throw new IllegalStateException(
149                                         "Two factory packages have the same name: "
150                                                 + pkg.packageName);
151                             }
152                             factoryPackagesSet.add(ai.packageName);
153                         }
154                     } catch (PackageParserException pe) {
155                         throw new IllegalStateException("Unable to parse: " + ai, pe);
156                     }
157                 }
158             } catch (RemoteException re) {
159                 Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
160                 throw new RuntimeException(re);
161             }
162         }
163     }
164 
165     /**
166      * Retrieves information about an APEX package.
167      *
168      * @param packageName the package name to look for. Note that this is the package name reported
169      *                    in the APK container manifest (i.e. AndroidManifest.xml), which might
170      *                    differ from the one reported in the APEX manifest (i.e.
171      *                    apex_manifest.json).
172      * @param flags the type of package to return. This may match to active packages
173      *              and factory (pre-installed) packages.
174      * @return a PackageInfo object with the information about the package, or null if the package
175      *         is not found.
176      */
getPackageInfo(String packageName, @PackageInfoFlags int flags)177     @Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) {
178         populateAllPackagesCacheIfNeeded();
179         boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
180         boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
181         for (PackageInfo packageInfo: mAllPackagesCache) {
182             if (!packageInfo.packageName.equals(packageName)) {
183                 continue;
184             }
185             if ((!matchActive || isActive(packageInfo))
186                     && (!matchFactory || isFactory(packageInfo))) {
187                 return packageInfo;
188             }
189         }
190         return null;
191     }
192 
193     /**
194      * Returns a {@link PackageInfo} for an active APEX package keyed by it's {@code apexName}.
195      *
196      * @deprecated this API will soon be deleted, please don't depend on it.
197      */
198     // TODO(b/132324953): delete.
199     @Deprecated
getPackageInfoForApexName(String apexName)200     @Nullable PackageInfo getPackageInfoForApexName(String apexName) {
201         populateAllPackagesCacheIfNeeded();
202         return mApexNameToPackageInfoCache.get(apexName);
203     }
204 
205     /**
206      * Retrieves information about all active APEX packages.
207      *
208      * @return a List of PackageInfo object, each one containing information about a different
209      *         active package.
210      */
getActivePackages()211     List<PackageInfo> getActivePackages() {
212         populateAllPackagesCacheIfNeeded();
213         return mAllPackagesCache
214                 .stream()
215                 .filter(item -> isActive(item))
216                 .collect(Collectors.toList());
217     }
218 
219     /**
220      * Retrieves information about all active pre-installed APEX packages.
221      *
222      * @return a List of PackageInfo object, each one containing information about a different
223      *         active pre-installed package.
224      */
getFactoryPackages()225     List<PackageInfo> getFactoryPackages() {
226         populateAllPackagesCacheIfNeeded();
227         return mAllPackagesCache
228                 .stream()
229                 .filter(item -> isFactory(item))
230                 .collect(Collectors.toList());
231     }
232 
233     /**
234      * Retrieves information about all inactive APEX packages.
235      *
236      * @return a List of PackageInfo object, each one containing information about a different
237      *         inactive package.
238      */
getInactivePackages()239     List<PackageInfo> getInactivePackages() {
240         populateAllPackagesCacheIfNeeded();
241         return mAllPackagesCache
242                 .stream()
243                 .filter(item -> !isActive(item))
244                 .collect(Collectors.toList());
245     }
246 
247     /**
248      * Checks if {@code packageName} is an apex package.
249      *
250      * @param packageName package to check.
251      * @return {@code true} if {@code packageName} is an apex package.
252      */
isApexPackage(String packageName)253     boolean isApexPackage(String packageName) {
254         populateAllPackagesCacheIfNeeded();
255         for (PackageInfo packageInfo : mAllPackagesCache) {
256             if (packageInfo.packageName.equals(packageName)) {
257                 return true;
258             }
259         }
260         return false;
261     }
262 
263     /**
264      * Retrieves information about an apexd staged session i.e. the internal state used by apexd to
265      * track the different states of a session.
266      *
267      * @param sessionId the identifier of the session.
268      * @return an ApexSessionInfo object, or null if the session is not known.
269      */
getStagedSessionInfo(int sessionId)270     @Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
271         try {
272             ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId);
273             if (apexSessionInfo.isUnknown) {
274                 return null;
275             }
276             return apexSessionInfo;
277         } catch (RemoteException re) {
278             Slog.e(TAG, "Unable to contact apexservice", re);
279             throw new RuntimeException(re);
280         }
281     }
282 
283     /**
284      * Submit a staged session to apex service. This causes the apex service to perform some initial
285      * verification and accept or reject the session. Submitting a session successfully is not
286      * enough for it to be activated at the next boot, the caller needs to call
287      * {@link #markStagedSessionReady(int)}.
288      *
289      * @param sessionId the identifier of the {@link PackageInstallerSession} being submitted.
290      * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
291      *                        an array of identifiers of all the child sessions. Otherwise it should
292      *                        be an empty array.
293      * @param apexInfoList this is an output parameter, which needs to be initialized by tha caller
294      *                     and will be filled with a list of {@link ApexInfo} objects, each of which
295      *                     contains metadata about one of the packages being submitted as part of
296      *                     the session.
297      * @return whether the submission of the session was successful.
298      */
submitStagedSession( int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList)299     boolean submitStagedSession(
300             int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) {
301         try {
302             return mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
303         } catch (RemoteException re) {
304             Slog.e(TAG, "Unable to contact apexservice", re);
305             throw new RuntimeException(re);
306         }
307     }
308 
309     /**
310      * Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
311      * applied at next reboot.
312      *
313      * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready.
314      * @return true upon success, false if the session is unknown.
315      */
markStagedSessionReady(int sessionId)316     boolean markStagedSessionReady(int sessionId) {
317         try {
318             return mApexService.markStagedSessionReady(sessionId);
319         } catch (RemoteException re) {
320             Slog.e(TAG, "Unable to contact apexservice", re);
321             throw new RuntimeException(re);
322         }
323     }
324 
325     /**
326      * Marks a staged session as successful.
327      *
328      * <p>Only activated session can be marked as successful.
329      *
330      * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as
331      *                  successful.
332      */
markStagedSessionSuccessful(int sessionId)333     void markStagedSessionSuccessful(int sessionId) {
334         try {
335             mApexService.markStagedSessionSuccessful(sessionId);
336         } catch (RemoteException re) {
337             Slog.e(TAG, "Unable to contact apexservice", re);
338             throw new RuntimeException(re);
339         } catch (Exception e) {
340             // It is fine to just log an exception in this case. APEXd will be able to recover in
341             // case markStagedSessionSuccessful fails.
342             Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e);
343         }
344     }
345 
346     /**
347      * Whether the current device supports the management of APEX packages.
348      *
349      * @return true if APEX packages can be managed on this device, false otherwise.
350      */
isApexSupported()351     boolean isApexSupported() {
352         return ApexProperties.updatable().orElse(false);
353     }
354 
355     /**
356      * Abandons the (only) active session previously submitted.
357      *
358      * @return {@code true} upon success, {@code false} if any remote exception occurs
359      */
abortActiveSession()360     boolean abortActiveSession() {
361         try {
362             mApexService.abortActiveSession();
363             return true;
364         } catch (RemoteException re) {
365             Slog.e(TAG, "Unable to contact apexservice", re);
366             return false;
367         }
368     }
369 
370     /**
371      * Uninstalls given {@code apexPackage}.
372      *
373      * <p>NOTE. Device must be rebooted in order for uninstall to take effect.
374      *
375      * @param apexPackagePath package to uninstall.
376      * @return {@code true} upon successful uninstall, {@code false} otherwise.
377      */
uninstallApex(String apexPackagePath)378     boolean uninstallApex(String apexPackagePath) {
379         try {
380             mApexService.unstagePackages(Collections.singletonList(apexPackagePath));
381             return true;
382         } catch (Exception e) {
383             return false;
384         }
385     }
386 
387     /**
388      * Whether an APEX package is active or not.
389      *
390      * @param packageInfo the package to check
391      * @return {@code true} if this package is active, {@code false} otherwise.
392      */
isActive(PackageInfo packageInfo)393     private static boolean isActive(PackageInfo packageInfo) {
394         return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
395     }
396 
397     /**
398      * Whether the APEX package is pre-installed or not.
399      *
400      * @param packageInfo the package to check
401      * @return {@code true} if this package is pre-installed, {@code false} otherwise.
402      */
isFactory(PackageInfo packageInfo)403     private static boolean isFactory(PackageInfo packageInfo) {
404         return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
405     }
406 
407     /**
408      * Dump information about the packages contained in a particular cache
409      * @param packagesCache the cache to print information about.
410      * @param packageName a {@link String} containing a package name, or {@code null}. If set, only
411      *                    information about that specific package will be dumped.
412      * @param ipw the {@link IndentingPrintWriter} object to send information to.
413      */
dumpFromPackagesCache( List<PackageInfo> packagesCache, @Nullable String packageName, IndentingPrintWriter ipw)414     void dumpFromPackagesCache(
415             List<PackageInfo> packagesCache,
416             @Nullable String packageName,
417             IndentingPrintWriter ipw) {
418         ipw.println();
419         ipw.increaseIndent();
420         for (PackageInfo pi : packagesCache) {
421             if (packageName != null && !packageName.equals(pi.packageName)) {
422                 continue;
423             }
424             ipw.println(pi.packageName);
425             ipw.increaseIndent();
426             ipw.println("Version: " + pi.versionCode);
427             ipw.println("Path: " + pi.applicationInfo.sourceDir);
428             ipw.println("IsActive: " + isActive(pi));
429             ipw.println("IsFactory: " + isFactory(pi));
430             ipw.decreaseIndent();
431         }
432         ipw.decreaseIndent();
433         ipw.println();
434     }
435 
436     /**
437      * Dumps various state information to the provided {@link PrintWriter} object.
438      *
439      * @param pw the {@link PrintWriter} object to send information to.
440      * @param packageName a {@link String} containing a package name, or {@code null}. If set, only
441      *                    information about that specific package will be dumped.
442      */
dump(PrintWriter pw, @Nullable String packageName)443     void dump(PrintWriter pw, @Nullable String packageName) {
444         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
445         try {
446             populateAllPackagesCacheIfNeeded();
447             ipw.println();
448             ipw.println("Active APEX packages:");
449             dumpFromPackagesCache(getActivePackages(), packageName, ipw);
450             ipw.println("Inactive APEX packages:");
451             dumpFromPackagesCache(getInactivePackages(), packageName, ipw);
452             ipw.println("Factory APEX packages:");
453             dumpFromPackagesCache(getFactoryPackages(), packageName, ipw);
454             ipw.increaseIndent();
455             ipw.println("APEX session state:");
456             ipw.increaseIndent();
457             final ApexSessionInfo[] sessions = mApexService.getSessions();
458             for (ApexSessionInfo si : sessions) {
459                 ipw.println("Session ID: " + si.sessionId);
460                 ipw.increaseIndent();
461                 if (si.isUnknown) {
462                     ipw.println("State: UNKNOWN");
463                 } else if (si.isVerified) {
464                     ipw.println("State: VERIFIED");
465                 } else if (si.isStaged) {
466                     ipw.println("State: STAGED");
467                 } else if (si.isActivated) {
468                     ipw.println("State: ACTIVATED");
469                 } else if (si.isActivationFailed) {
470                     ipw.println("State: ACTIVATION FAILED");
471                 } else if (si.isSuccess) {
472                     ipw.println("State: SUCCESS");
473                 } else if (si.isRollbackInProgress) {
474                     ipw.println("State: ROLLBACK IN PROGRESS");
475                 } else if (si.isRolledBack) {
476                     ipw.println("State: ROLLED BACK");
477                 } else if (si.isRollbackFailed) {
478                     ipw.println("State: ROLLBACK FAILED");
479                 }
480                 ipw.decreaseIndent();
481             }
482             ipw.decreaseIndent();
483         } catch (RemoteException e) {
484             ipw.println("Couldn't communicate with apexd.");
485         }
486     }
487 
onBootCompleted()488     public void onBootCompleted() {
489         populateAllPackagesCacheIfNeeded();
490     }
491 }