blob: 51c6a0e855616d14e913531b8311f47718549177 [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;
25import android.content.pm.PackageInfo;
26import android.content.pm.PackageParser;
27import android.content.pm.PackageParser.PackageParserException;
28import android.os.RemoteException;
29import android.os.ServiceManager;
Dario Frenia1c5f632019-02-14 15:37:54 +000030import android.os.ServiceManager.ServiceNotFoundException;
Dario Freni2e8dffc2019-02-06 14:55:16 +000031import android.util.Slog;
32
33import com.android.internal.util.IndentingPrintWriter;
34
35import java.io.File;
36import java.io.PrintWriter;
37import java.util.ArrayList;
38import java.util.Collection;
39import java.util.List;
40import java.util.Map;
41import java.util.function.Function;
42import java.util.stream.Collectors;
43
44/**
45 * ApexManager class handles communications with the apex service to perform operation and queries,
46 * as well as providing caching to avoid unnecessary calls to the service.
47 */
48class ApexManager {
49 static final String TAG = "ApexManager";
50 private final IApexService mApexService;
51 private final Map<String, PackageInfo> mActivePackagesCache;
52
53 ApexManager() {
Dario Frenia1c5f632019-02-14 15:37:54 +000054 try {
55 mApexService = IApexService.Stub.asInterface(
56 ServiceManager.getServiceOrThrow("apexservice"));
57 } catch (ServiceNotFoundException e) {
58 throw new IllegalStateException("Required service apexservice not available");
59 }
Dario Freni2e8dffc2019-02-06 14:55:16 +000060 mActivePackagesCache = populateActivePackagesCache();
61 }
62
63 @NonNull
64 private Map<String, PackageInfo> populateActivePackagesCache() {
65 try {
66 List<PackageInfo> list = new ArrayList<>();
67 final ApexInfo[] activePkgs = mApexService.getActivePackages();
68 for (ApexInfo ai : activePkgs) {
69 // If the device is using flattened APEX, don't report any APEX
70 // packages since they won't be managed or updated by PackageManager.
71 if ((new File(ai.packagePath)).isDirectory()) {
72 break;
73 }
74 try {
75 list.add(PackageParser.generatePackageInfoFromApex(
76 new File(ai.packagePath), true /* collect certs */));
77 } catch (PackageParserException pe) {
78 throw new IllegalStateException("Unable to parse: " + ai, pe);
79 }
80 }
81 return list.stream().collect(Collectors.toMap(p -> p.packageName, Function.identity()));
82 } catch (RemoteException re) {
83 Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
84 throw new RuntimeException(re);
85 }
86 }
87
88 /**
89 * Retrieves information about an active APEX package.
90 *
91 * @param packageName the package name to look for. Note that this is the package name reported
92 * in the APK container manifest (i.e. AndroidManifest.xml), which might
93 * differ from the one reported in the APEX manifest (i.e.
94 * apex_manifest.json).
95 * @return a PackageInfo object with the information about the package, or null if the package
96 * is not found.
97 */
98 @Nullable PackageInfo getActivePackage(String packageName) {
99 return mActivePackagesCache.get(packageName);
100 }
101
102 /**
103 * Retrieves information about all active APEX packages.
104 *
105 * @return a Collection of PackageInfo object, each one containing information about a different
106 * active package.
107 */
108 Collection<PackageInfo> getActivePackages() {
109 return mActivePackagesCache.values();
110 }
111
112 /**
113 * Retrieves information about an apexd staged session i.e. the internal state used by apexd to
114 * track the different states of a session.
115 *
116 * @param sessionId the identifier of the session.
117 * @return an ApexSessionInfo object, or null if the session is not known.
118 */
119 @Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
120 try {
121 ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId);
122 if (apexSessionInfo.isUnknown) {
123 return null;
124 }
125 return apexSessionInfo;
126 } catch (RemoteException re) {
127 Slog.e(TAG, "Unable to contact apexservice", re);
128 throw new RuntimeException(re);
129 }
130 }
131
132 /**
133 * Submit a staged session to apex service. This causes the apex service to perform some initial
134 * verification and accept or reject the session. Submitting a session successfully is not
135 * enough for it to be activated at the next boot, the caller needs to call
136 * {@link #markStagedSessionReady(int)}.
137 *
138 * @param sessionId the identifier of the {@link PackageInstallerSession} being submitted.
139 * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
140 * an array of identifiers of all the child sessions. Otherwise it should
141 * be an empty array.
142 * @param apexInfoList this is an output parameter, which needs to be initialized by tha caller
143 * and will be filled with a list of {@link ApexInfo} objects, each of which
144 * contains metadata about one of the packages being submitted as part of
145 * the session.
146 * @return whether the submission of the session was successful.
147 */
148 boolean submitStagedSession(
149 int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) {
150 try {
151 return mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
152 } catch (RemoteException re) {
153 Slog.e(TAG, "Unable to contact apexservice", re);
154 throw new RuntimeException(re);
155 }
156 }
157
158 /**
Dario Frenia0e3dda2019-02-18 20:58:54 +0000159 * Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
Dario Freni2e8dffc2019-02-06 14:55:16 +0000160 * applied at next reboot.
161 *
162 * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready.
163 * @return true upon success, false if the session is unknown.
164 */
165 boolean markStagedSessionReady(int sessionId) {
166 try {
167 return mApexService.markStagedSessionReady(sessionId);
168 } catch (RemoteException re) {
169 Slog.e(TAG, "Unable to contact apexservice", re);
170 throw new RuntimeException(re);
171 }
172 }
173
174 /**
Nikita Ioffea820bd92019-02-15 14:22:44 +0000175 * Marks a staged session as successful.
176 *
177 * <p>Only activated session can be marked as successful.
178 *
179 * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as
180 * successful.
181 */
182 void markStagedSessionSuccessful(int sessionId) {
183 try {
184 mApexService.markStagedSessionSuccessful(sessionId);
185 } catch (RemoteException re) {
186 Slog.e(TAG, "Unable to contact apexservice", re);
187 throw new RuntimeException(re);
188 } catch (Exception e) {
189 // It is fine to just log an exception in this case. APEXd will be able to recover in
190 // case markStagedSessionSuccessful fails.
191 Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e);
192 }
193 }
194
195 /**
Dario Freni83620602019-02-18 14:30:57 +0000196 * Whether the current device supports the management of APEX packages.
197 *
198 * @return true if APEX packages can be managed on this device, false otherwise.
199 */
200 boolean isApexSupported() {
201 // There is no system-wide property available to check if APEX are flattened and hence can't
202 // be updated. In absence of such property, we assume that if we didn't index APEX packages
203 // since they were flattened, no APEX management should be possible.
204 return !mActivePackagesCache.isEmpty();
205 }
206
207 /**
shafik07205e32019-02-07 20:12:33 +0000208 * Abandons the (only) active session previously submitted.
209 *
210 * @return {@code true} upon success, {@code false} if any remote exception occurs
211 */
212 boolean abortActiveSession() {
213 try {
214 mApexService.abortActiveSession();
215 return true;
216 } catch (RemoteException re) {
217 Slog.e(TAG, "Unable to contact apexservice", re);
218 return false;
219 }
220 }
221
222 /**
Dario Freni2e8dffc2019-02-06 14:55:16 +0000223 * Dumps various state information to the provided {@link PrintWriter} object.
224 *
225 * @param pw the {@link PrintWriter} object to send information to.
226 * @param packageName a {@link String} containing a package name, or {@code null}. If set, only
227 * information about that specific package will be dumped.
228 */
229 void dump(PrintWriter pw, @Nullable String packageName) {
230 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
231 ipw.println();
232 ipw.println("Active APEX packages:");
233 ipw.increaseIndent();
234 try {
235 populateActivePackagesCache();
236 for (PackageInfo pi : mActivePackagesCache.values()) {
237 if (packageName != null && !packageName.equals(pi.packageName)) {
238 continue;
239 }
240 ipw.println(pi.packageName);
241 ipw.increaseIndent();
242 ipw.println("Version: " + pi.versionCode);
243 ipw.println("Path: " + pi.applicationInfo.sourceDir);
244 ipw.decreaseIndent();
245 }
246 ipw.decreaseIndent();
247 ipw.println();
248 ipw.println("APEX session state:");
249 ipw.increaseIndent();
250 final ApexSessionInfo[] sessions = mApexService.getSessions();
251 for (ApexSessionInfo si : sessions) {
Nikita Ioffea820bd92019-02-15 14:22:44 +0000252 ipw.println("Session ID: " + si.sessionId);
Dario Freni2e8dffc2019-02-06 14:55:16 +0000253 ipw.increaseIndent();
254 if (si.isUnknown) {
255 ipw.println("State: UNKNOWN");
256 } else if (si.isVerified) {
257 ipw.println("State: VERIFIED");
258 } else if (si.isStaged) {
259 ipw.println("State: STAGED");
260 } else if (si.isActivated) {
261 ipw.println("State: ACTIVATED");
Dario Freni2e8dffc2019-02-06 14:55:16 +0000262 } else if (si.isActivationFailed) {
263 ipw.println("State: ACTIVATION FAILED");
264 }
265 ipw.decreaseIndent();
266 }
267 ipw.decreaseIndent();
268 } catch (RemoteException e) {
269 ipw.println("Couldn't communicate with apexd.");
270 }
271 }
272}