blob: 5fdd872409c026a5b21dbf859556787cb62b629c [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
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.apex.ApexInfo;
22import android.apex.ApexInfoList;
23import android.apex.ApexSessionInfo;
24import android.apex.IApexService;
Dario Freni14f885b2019-02-25 12:48:47 +000025import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
Dario Freni2e8dffc2019-02-06 14:55:16 +000029import android.content.pm.PackageInfo;
30import android.content.pm.PackageParser;
31import android.content.pm.PackageParser.PackageParserException;
32import android.os.RemoteException;
33import android.os.ServiceManager;
Dario Frenia1c5f632019-02-14 15:37:54 +000034import android.os.ServiceManager.ServiceNotFoundException;
Dario Freni2e8dffc2019-02-06 14:55:16 +000035import android.util.Slog;
36
Martijn Coenen3f629942019-02-22 13:15:46 +010037import com.android.internal.annotations.GuardedBy;
Dario Freni2e8dffc2019-02-06 14:55:16 +000038import com.android.internal.util.IndentingPrintWriter;
39
40import java.io.File;
41import java.io.PrintWriter;
42import java.util.ArrayList;
43import java.util.Collection;
Nikita Ioffef012a222019-03-05 22:37:55 +000044import java.util.Collections;
Dario Freni2e8dffc2019-02-06 14:55:16 +000045import java.util.List;
46import java.util.Map;
47import java.util.function.Function;
48import java.util.stream.Collectors;
49
50/**
51 * ApexManager class handles communications with the apex service to perform operation and queries,
52 * as well as providing caching to avoid unnecessary calls to the service.
53 */
54class ApexManager {
55 static final String TAG = "ApexManager";
56 private final IApexService mApexService;
Dario Freni14f885b2019-02-25 12:48:47 +000057 private final Context mContext;
Martijn Coenen3f629942019-02-22 13:15:46 +010058 private final Object mLock = new Object();
59 @GuardedBy("mLock")
60 private Map<String, PackageInfo> mActivePackagesCache;
Dario Freni2e8dffc2019-02-06 14:55:16 +000061
Dario Freni14f885b2019-02-25 12:48:47 +000062 ApexManager(Context context) {
Dario Frenia1c5f632019-02-14 15:37:54 +000063 try {
64 mApexService = IApexService.Stub.asInterface(
65 ServiceManager.getServiceOrThrow("apexservice"));
66 } catch (ServiceNotFoundException e) {
67 throw new IllegalStateException("Required service apexservice not available");
68 }
Dario Freni14f885b2019-02-25 12:48:47 +000069 mContext = context;
70 }
71
72 void systemReady() {
73 mContext.registerReceiver(new BroadcastReceiver() {
74 @Override
75 public void onReceive(Context context, Intent intent) {
76 onBootCompleted();
77 mContext.unregisterReceiver(this);
78 }
79 }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
Dario Freni2e8dffc2019-02-06 14:55:16 +000080 }
81
Martijn Coenen3f629942019-02-22 13:15:46 +010082 private void populateActivePackagesCacheIfNeeded() {
83 synchronized (mLock) {
84 if (mActivePackagesCache != null) {
85 return;
Dario Freni2e8dffc2019-02-06 14:55:16 +000086 }
Martijn Coenen3f629942019-02-22 13:15:46 +010087 try {
88 List<PackageInfo> list = new ArrayList<>();
89 final ApexInfo[] activePkgs = mApexService.getActivePackages();
90 for (ApexInfo ai : activePkgs) {
91 // If the device is using flattened APEX, don't report any APEX
92 // packages since they won't be managed or updated by PackageManager.
93 if ((new File(ai.packagePath)).isDirectory()) {
94 break;
95 }
96 try {
97 list.add(PackageParser.generatePackageInfoFromApex(
98 new File(ai.packagePath), true /* collect certs */));
99 } catch (PackageParserException pe) {
100 throw new IllegalStateException("Unable to parse: " + ai, pe);
101 }
102 }
103 mActivePackagesCache = list.stream().collect(
104 Collectors.toMap(p -> p.packageName, Function.identity()));
105 } catch (RemoteException re) {
106 Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
107 throw new RuntimeException(re);
108 }
Dario Freni2e8dffc2019-02-06 14:55:16 +0000109 }
110 }
111
112 /**
113 * Retrieves information about an active APEX package.
114 *
115 * @param packageName the package name to look for. Note that this is the package name reported
116 * in the APK container manifest (i.e. AndroidManifest.xml), which might
117 * differ from the one reported in the APEX manifest (i.e.
118 * apex_manifest.json).
119 * @return a PackageInfo object with the information about the package, or null if the package
120 * is not found.
121 */
122 @Nullable PackageInfo getActivePackage(String packageName) {
Martijn Coenen3f629942019-02-22 13:15:46 +0100123 populateActivePackagesCacheIfNeeded();
Dario Freni2e8dffc2019-02-06 14:55:16 +0000124 return mActivePackagesCache.get(packageName);
125 }
126
127 /**
128 * Retrieves information about all active APEX packages.
129 *
130 * @return a Collection of PackageInfo object, each one containing information about a different
131 * active package.
132 */
133 Collection<PackageInfo> getActivePackages() {
Martijn Coenen3f629942019-02-22 13:15:46 +0100134 populateActivePackagesCacheIfNeeded();
Dario Freni2e8dffc2019-02-06 14:55:16 +0000135 return mActivePackagesCache.values();
136 }
137
138 /**
Nikita Ioffef012a222019-03-05 22:37:55 +0000139 * Checks if {@code packageName} is an apex package.
140 *
141 * @param packageName package to check.
142 * @return {@code true} if {@code packageName} is an apex package.
143 */
144 boolean isApexPackage(String packageName) {
145 populateActivePackagesCacheIfNeeded();
146 return mActivePackagesCache.containsKey(packageName);
147 }
148
149 /**
Dario Freni2e8dffc2019-02-06 14:55:16 +0000150 * Retrieves information about an apexd staged session i.e. the internal state used by apexd to
151 * track the different states of a session.
152 *
153 * @param sessionId the identifier of the session.
154 * @return an ApexSessionInfo object, or null if the session is not known.
155 */
156 @Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
157 try {
158 ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId);
159 if (apexSessionInfo.isUnknown) {
160 return null;
161 }
162 return apexSessionInfo;
163 } catch (RemoteException re) {
164 Slog.e(TAG, "Unable to contact apexservice", re);
165 throw new RuntimeException(re);
166 }
167 }
168
169 /**
170 * Submit a staged session to apex service. This causes the apex service to perform some initial
171 * verification and accept or reject the session. Submitting a session successfully is not
172 * enough for it to be activated at the next boot, the caller needs to call
173 * {@link #markStagedSessionReady(int)}.
174 *
175 * @param sessionId the identifier of the {@link PackageInstallerSession} being submitted.
176 * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
177 * an array of identifiers of all the child sessions. Otherwise it should
178 * be an empty array.
179 * @param apexInfoList this is an output parameter, which needs to be initialized by tha caller
180 * and will be filled with a list of {@link ApexInfo} objects, each of which
181 * contains metadata about one of the packages being submitted as part of
182 * the session.
183 * @return whether the submission of the session was successful.
184 */
185 boolean submitStagedSession(
186 int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) {
187 try {
188 return mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
189 } catch (RemoteException re) {
190 Slog.e(TAG, "Unable to contact apexservice", re);
191 throw new RuntimeException(re);
192 }
193 }
194
195 /**
Dario Frenia0e3dda2019-02-18 20:58:54 +0000196 * Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
Dario Freni2e8dffc2019-02-06 14:55:16 +0000197 * applied at next reboot.
198 *
199 * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready.
200 * @return true upon success, false if the session is unknown.
201 */
202 boolean markStagedSessionReady(int sessionId) {
203 try {
204 return mApexService.markStagedSessionReady(sessionId);
205 } catch (RemoteException re) {
206 Slog.e(TAG, "Unable to contact apexservice", re);
207 throw new RuntimeException(re);
208 }
209 }
210
211 /**
Nikita Ioffea820bd92019-02-15 14:22:44 +0000212 * Marks a staged session as successful.
213 *
214 * <p>Only activated session can be marked as successful.
215 *
216 * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as
217 * successful.
218 */
219 void markStagedSessionSuccessful(int sessionId) {
220 try {
221 mApexService.markStagedSessionSuccessful(sessionId);
222 } catch (RemoteException re) {
223 Slog.e(TAG, "Unable to contact apexservice", re);
224 throw new RuntimeException(re);
225 } catch (Exception e) {
226 // It is fine to just log an exception in this case. APEXd will be able to recover in
227 // case markStagedSessionSuccessful fails.
228 Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e);
229 }
230 }
231
232 /**
Dario Freni83620602019-02-18 14:30:57 +0000233 * Whether the current device supports the management of APEX packages.
234 *
235 * @return true if APEX packages can be managed on this device, false otherwise.
236 */
237 boolean isApexSupported() {
Nikita Ioffe712be762019-02-26 17:15:36 +0000238 populateActivePackagesCacheIfNeeded();
Dario Freni83620602019-02-18 14:30:57 +0000239 // There is no system-wide property available to check if APEX are flattened and hence can't
240 // be updated. In absence of such property, we assume that if we didn't index APEX packages
241 // since they were flattened, no APEX management should be possible.
242 return !mActivePackagesCache.isEmpty();
243 }
244
245 /**
shafik07205e32019-02-07 20:12:33 +0000246 * Abandons the (only) active session previously submitted.
247 *
248 * @return {@code true} upon success, {@code false} if any remote exception occurs
249 */
250 boolean abortActiveSession() {
251 try {
252 mApexService.abortActiveSession();
253 return true;
254 } catch (RemoteException re) {
255 Slog.e(TAG, "Unable to contact apexservice", re);
256 return false;
257 }
258 }
259
260 /**
Nikita Ioffef012a222019-03-05 22:37:55 +0000261 * Uninstalls given {@code apexPackage}.
262 *
263 * <p>NOTE. Device must be rebooted in order for uninstall to take effect.
264 *
265 * @param apexPackagePath package to uninstall.
266 * @return {@code true} upon successful uninstall, {@code false} otherwise.
267 */
268 boolean uninstallApex(String apexPackagePath) {
269 try {
270 mApexService.unstagePackages(Collections.singletonList(apexPackagePath));
271 return true;
272 } catch (Exception e) {
273 return false;
274 }
275 }
276
277 /**
Dario Freni2e8dffc2019-02-06 14:55:16 +0000278 * Dumps various state information to the provided {@link PrintWriter} object.
279 *
280 * @param pw the {@link PrintWriter} object to send information to.
281 * @param packageName a {@link String} containing a package name, or {@code null}. If set, only
282 * information about that specific package will be dumped.
283 */
284 void dump(PrintWriter pw, @Nullable String packageName) {
285 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
286 ipw.println();
287 ipw.println("Active APEX packages:");
288 ipw.increaseIndent();
289 try {
Martijn Coenen3f629942019-02-22 13:15:46 +0100290 populateActivePackagesCacheIfNeeded();
Dario Freni2e8dffc2019-02-06 14:55:16 +0000291 for (PackageInfo pi : mActivePackagesCache.values()) {
292 if (packageName != null && !packageName.equals(pi.packageName)) {
293 continue;
294 }
295 ipw.println(pi.packageName);
296 ipw.increaseIndent();
297 ipw.println("Version: " + pi.versionCode);
298 ipw.println("Path: " + pi.applicationInfo.sourceDir);
299 ipw.decreaseIndent();
300 }
301 ipw.decreaseIndent();
302 ipw.println();
303 ipw.println("APEX session state:");
304 ipw.increaseIndent();
305 final ApexSessionInfo[] sessions = mApexService.getSessions();
306 for (ApexSessionInfo si : sessions) {
Nikita Ioffea820bd92019-02-15 14:22:44 +0000307 ipw.println("Session ID: " + si.sessionId);
Dario Freni2e8dffc2019-02-06 14:55:16 +0000308 ipw.increaseIndent();
309 if (si.isUnknown) {
310 ipw.println("State: UNKNOWN");
311 } else if (si.isVerified) {
312 ipw.println("State: VERIFIED");
313 } else if (si.isStaged) {
314 ipw.println("State: STAGED");
315 } else if (si.isActivated) {
316 ipw.println("State: ACTIVATED");
Dario Freni2e8dffc2019-02-06 14:55:16 +0000317 } else if (si.isActivationFailed) {
318 ipw.println("State: ACTIVATION FAILED");
Nikita Ioffe82742222019-02-26 12:14:04 +0000319 } else if (si.isSuccess) {
320 ipw.println("State: SUCCESS");
321 } else if (si.isRollbackInProgress) {
322 ipw.println("State: ROLLBACK IN PROGRESS");
323 } else if (si.isRolledBack) {
324 ipw.println("State: ROLLED BACK");
Dario Freni2e8dffc2019-02-06 14:55:16 +0000325 }
326 ipw.decreaseIndent();
327 }
328 ipw.decreaseIndent();
329 } catch (RemoteException e) {
330 ipw.println("Couldn't communicate with apexd.");
331 }
332 }
Martijn Coenen3f629942019-02-22 13:15:46 +0100333
334 public void onBootCompleted() {
335 populateActivePackagesCacheIfNeeded();
336 }
Dario Freni2e8dffc2019-02-06 14:55:16 +0000337}