blob: 6971ba3ad0bf4638bfbfac5552a1e2b06b908350 [file] [log] [blame]
Dario Freni2e8dffc2019-02-06 14:55:16 +00001/*
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
17package com.android.server.pm;
18
Gavin Corkeryef441722019-05-09 17:02:10 +010019import android.annotation.IntDef;
Dario Freni2e8dffc2019-02-06 14:55:16 +000020import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.apex.ApexInfo;
23import android.apex.ApexInfoList;
24import android.apex.ApexSessionInfo;
25import android.apex.IApexService;
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +000026import android.content.BroadcastReceiver;
Dario Freni14f885b2019-02-25 12:48:47 +000027import android.content.Context;
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +000028import android.content.Intent;
29import android.content.IntentFilter;
Gavin Corkeryef441722019-05-09 17:02:10 +010030import android.content.pm.ApplicationInfo;
Dario Freni2e8dffc2019-02-06 14:55:16 +000031import android.content.pm.PackageInfo;
Mohammad Samiul Islam7aa7d2e2019-03-27 12:23:47 +000032import android.content.pm.PackageManager;
Dario Freni2e8dffc2019-02-06 14:55:16 +000033import android.content.pm.PackageParser;
34import android.content.pm.PackageParser.PackageParserException;
35import android.os.RemoteException;
36import android.os.ServiceManager;
Dario Frenia1c5f632019-02-14 15:37:54 +000037import android.os.ServiceManager.ServiceNotFoundException;
Dario Freni2ce84342019-04-26 13:06:22 +010038import android.sysprop.ApexProperties;
Nikita Ioffed3868dc2019-05-09 20:40:54 +010039import android.util.ArrayMap;
Dario Freni2e8dffc2019-02-06 14:55:16 +000040import android.util.Slog;
41
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +000042import com.android.internal.annotations.GuardedBy;
Dario Freni2e8dffc2019-02-06 14:55:16 +000043import com.android.internal.util.IndentingPrintWriter;
44
45import java.io.File;
46import java.io.PrintWriter;
Gavin Corkeryef441722019-05-09 17:02:10 +010047import java.lang.annotation.Retention;
48import java.lang.annotation.RetentionPolicy;
49import java.util.ArrayList;
Nikita Ioffef012a222019-03-05 22:37:55 +000050import java.util.Collections;
Gavin Corkeryef441722019-05-09 17:02:10 +010051import java.util.HashSet;
52import java.util.List;
53import java.util.stream.Collectors;
Dario Freni2e8dffc2019-02-06 14:55:16 +000054
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 */
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +000059class ApexManager {
60 static final String TAG = "ApexManager";
61 private final IApexService mApexService;
62 private final Context mContext;
63 private final Object mLock = new Object();
Nikita Ioffed3868dc2019-05-09 20:40:54 +010064 /**
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
Gavin Corkeryef441722019-05-09 17:02:10 +010069 * AndroidManifest.xml}.
Nikita Ioffed3868dc2019-05-09 20:40:54 +010070 */
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +000071 @GuardedBy("mLock")
Gavin Corkeryef441722019-05-09 17:02:10 +010072 private List<PackageInfo> mAllPackagesCache;
Nikita Ioffed3868dc2019-05-09 20:40:54 +010073 /**
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;
Dario Freni2e8dffc2019-02-06 14:55:16 +000083
Gavin Corkeryef441722019-05-09 17:02:10 +010084
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +000085 ApexManager(Context context) {
Dario Frenia1c5f632019-02-14 15:37:54 +000086 try {
87 mApexService = IApexService.Stub.asInterface(
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +000088 ServiceManager.getServiceOrThrow("apexservice"));
Dario Frenia1c5f632019-02-14 15:37:54 +000089 } catch (ServiceNotFoundException e) {
90 throw new IllegalStateException("Required service apexservice not available");
91 }
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +000092 mContext = context;
Dario Freni14f885b2019-02-25 12:48:47 +000093 }
94
Gavin Corkeryef441722019-05-09 17:02:10 +010095 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
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +0000104 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));
Dario Freni2e8dffc2019-02-06 14:55:16 +0000112 }
113
Gavin Corkeryef441722019-05-09 17:02:10 +0100114 private void populateAllPackagesCacheIfNeeded() {
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +0000115 synchronized (mLock) {
Gavin Corkeryef441722019-05-09 17:02:10 +0100116 if (mAllPackagesCache != null) {
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +0000117 return;
118 }
Nikita Ioffed3868dc2019-05-09 20:40:54 +0100119 mApexNameToPackageInfoCache = new ArrayMap<>();
Martijn Coenen3f629942019-02-22 13:15:46 +0100120 try {
Gavin Corkeryef441722019-05-09 17:02:10 +0100121 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) {
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +0000126 // 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 {
Nikita Ioffed3868dc2019-05-09 20:40:54 +0100132 final PackageInfo pkg = PackageParser.generatePackageInfoFromApex(
Gavin Corkeryef441722019-05-09 17:02:10 +0100133 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 }
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +0000154 } catch (PackageParserException pe) {
155 throw new IllegalStateException("Unable to parse: " + ai, pe);
156 }
157 }
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +0000158 } catch (RemoteException re) {
159 Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
160 throw new RuntimeException(re);
Martijn Coenen3f629942019-02-22 13:15:46 +0100161 }
Dario Freni2e8dffc2019-02-06 14:55:16 +0000162 }
163 }
164
165 /**
Gavin Corkeryef441722019-05-09 17:02:10 +0100166 * Retrieves information about an APEX package.
Dario Freni2e8dffc2019-02-06 14:55:16 +0000167 *
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).
Gavin Corkeryef441722019-05-09 17:02:10 +0100172 * @param flags the type of package to return. This may match to active packages
173 * and factory (pre-installed) packages.
Dario Freni2e8dffc2019-02-06 14:55:16 +0000174 * @return a PackageInfo object with the information about the package, or null if the package
175 * is not found.
176 */
Gavin Corkeryef441722019-05-09 17:02:10 +0100177 @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;
Dario Freni2e8dffc2019-02-06 14:55:16 +0000191 }
192
193 /**
Gavin Corkeryef441722019-05-09 17:02:10 +0100194 * Returns a {@link PackageInfo} for an active APEX package keyed by it's {@code apexName}.
Nikita Ioffed3868dc2019-05-09 20:40:54 +0100195 *
196 * @deprecated this API will soon be deleted, please don't depend on it.
197 */
198 // TODO(b/132324953): delete.
199 @Deprecated
200 @Nullable PackageInfo getPackageInfoForApexName(String apexName) {
Gavin Corkeryef441722019-05-09 17:02:10 +0100201 populateAllPackagesCacheIfNeeded();
Nikita Ioffed3868dc2019-05-09 20:40:54 +0100202 return mApexNameToPackageInfoCache.get(apexName);
203 }
204
205 /**
Dario Freni2e8dffc2019-02-06 14:55:16 +0000206 * Retrieves information about all active APEX packages.
207 *
Gavin Corkeryef441722019-05-09 17:02:10 +0100208 * @return a List of PackageInfo object, each one containing information about a different
Dario Freni2e8dffc2019-02-06 14:55:16 +0000209 * active package.
210 */
Gavin Corkeryef441722019-05-09 17:02:10 +0100211 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 */
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 */
239 List<PackageInfo> getInactivePackages() {
240 populateAllPackagesCacheIfNeeded();
241 return mAllPackagesCache
242 .stream()
243 .filter(item -> !isActive(item))
244 .collect(Collectors.toList());
Dario Freni2e8dffc2019-02-06 14:55:16 +0000245 }
246
247 /**
Nikita Ioffef012a222019-03-05 22:37:55 +0000248 * 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 */
253 boolean isApexPackage(String packageName) {
Gavin Corkeryef441722019-05-09 17:02:10 +0100254 populateAllPackagesCacheIfNeeded();
255 for (PackageInfo packageInfo : mAllPackagesCache) {
256 if (packageInfo.packageName.equals(packageName)) {
257 return true;
258 }
259 }
260 return false;
Nikita Ioffef012a222019-03-05 22:37:55 +0000261 }
262
263 /**
Dario Freni2e8dffc2019-02-06 14:55:16 +0000264 * 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 */
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 */
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 /**
Dario Frenia0e3dda2019-02-18 20:58:54 +0000310 * Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
Dario Freni2e8dffc2019-02-06 14:55:16 +0000311 * 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 */
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 /**
Nikita Ioffea820bd92019-02-15 14:22:44 +0000326 * 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 */
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 /**
Dario Freni83620602019-02-18 14:30:57 +0000347 * 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 */
351 boolean isApexSupported() {
Dario Freni2ce84342019-04-26 13:06:22 +0100352 return ApexProperties.updatable().orElse(false);
Dario Freni83620602019-02-18 14:30:57 +0000353 }
354
355 /**
shafik07205e32019-02-07 20:12:33 +0000356 * Abandons the (only) active session previously submitted.
357 *
358 * @return {@code true} upon success, {@code false} if any remote exception occurs
359 */
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 /**
Nikita Ioffef012a222019-03-05 22:37:55 +0000371 * 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 */
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 /**
Gavin Corkeryef441722019-05-09 17:02:10 +0100388 * 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 */
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 */
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 */
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 /**
Dario Freni2e8dffc2019-02-06 14:55:16 +0000437 * 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 */
443 void dump(PrintWriter pw, @Nullable String packageName) {
444 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
Dario Freni2e8dffc2019-02-06 14:55:16 +0000445 try {
Gavin Corkeryef441722019-05-09 17:02:10 +0100446 populateAllPackagesCacheIfNeeded();
Dario Freni2e8dffc2019-02-06 14:55:16 +0000447 ipw.println();
Gavin Corkeryef441722019-05-09 17:02:10 +0100448 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();
Dario Freni2e8dffc2019-02-06 14:55:16 +0000455 ipw.println("APEX session state:");
456 ipw.increaseIndent();
457 final ApexSessionInfo[] sessions = mApexService.getSessions();
458 for (ApexSessionInfo si : sessions) {
Nikita Ioffea820bd92019-02-15 14:22:44 +0000459 ipw.println("Session ID: " + si.sessionId);
Dario Freni2e8dffc2019-02-06 14:55:16 +0000460 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");
Dario Freni2e8dffc2019-02-06 14:55:16 +0000469 } else if (si.isActivationFailed) {
470 ipw.println("State: ACTIVATION FAILED");
Nikita Ioffe82742222019-02-26 12:14:04 +0000471 } 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");
Nikita Ioffe7a4f08e2019-04-09 15:10:18 +0100477 } else if (si.isRollbackFailed) {
478 ipw.println("State: ROLLBACK FAILED");
Dario Freni2e8dffc2019-02-06 14:55:16 +0000479 }
480 ipw.decreaseIndent();
481 }
482 ipw.decreaseIndent();
483 } catch (RemoteException e) {
484 ipw.println("Couldn't communicate with apexd.");
485 }
486 }
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +0000487
488 public void onBootCompleted() {
Gavin Corkeryef441722019-05-09 17:02:10 +0100489 populateAllPackagesCacheIfNeeded();
Mohammad Samiul Islambdccb752019-05-08 14:41:53 +0000490 }
Gavin Corkeryef441722019-05-09 17:02:10 +0100491}