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