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