blob: 89354537526c5c58f0ed84d10bdead3551241e0e [file] [log] [blame]
Dario Frenibe98c3f2018-12-22 15:25:27 +00001/*
2 * Copyright (C) 2018 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.
15 */
16
17package com.android.server.pm;
18
19import android.annotation.NonNull;
Nikita Ioffeda998cf2019-03-04 22:54:30 +000020import android.annotation.Nullable;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000021import android.apex.ApexInfo;
Dario Freni276cd072019-01-09 11:13:44 +000022import android.apex.ApexInfoList;
Dario Freni61b3e252019-01-11 23:05:39 +000023import android.apex.ApexSessionInfo;
Oli Lanc72b0bb2019-12-02 14:03:55 +000024import android.apex.ApexSessionParams;
Rhed Jaob793c212019-10-30 20:54:37 +080025import android.content.BroadcastReceiver;
Narayan Kamathfcd4a042019-02-01 14:16:37 +000026import android.content.Context;
Dario Freni4b572c02019-01-29 09:40:31 +000027import android.content.IIntentReceiver;
28import android.content.IIntentSender;
29import android.content.Intent;
Rhed Jaob793c212019-10-30 20:54:37 +080030import android.content.IntentFilter;
Dario Freni4b572c02019-01-29 09:40:31 +000031import android.content.IntentSender;
Dario Freni2e8dffc2019-02-06 14:55:16 +000032import android.content.pm.PackageInfo;
Dario Frenibe98c3f2018-12-22 15:25:27 +000033import android.content.pm.PackageInstaller;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000034import android.content.pm.PackageInstaller.SessionInfo;
35import android.content.pm.PackageManager;
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +000036import android.content.pm.PackageManagerInternal;
Nikita Ioffee0dbc982019-05-09 19:33:35 +010037import android.content.pm.PackageParser;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000038import android.content.pm.PackageParser.PackageParserException;
39import android.content.pm.PackageParser.SigningDetails;
40import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
Dario Frenibe98c3f2018-12-22 15:25:27 +000041import android.content.pm.ParceledListSlice;
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +000042import android.content.pm.parsing.AndroidPackage;
Narayan Kamathfcd4a042019-02-01 14:16:37 +000043import android.content.rollback.IRollbackManager;
Oli Lanc72b0bb2019-12-02 14:03:55 +000044import android.content.rollback.RollbackInfo;
45import android.content.rollback.RollbackManager;
Dario Freni4b572c02019-01-29 09:40:31 +000046import android.os.Bundle;
Narayan Kamath94c93ab2019-01-04 10:47:00 +000047import android.os.Handler;
Dario Freni4b572c02019-01-29 09:40:31 +000048import android.os.IBinder;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +010049import android.os.Looper;
50import android.os.Message;
Dario Freni4b572c02019-01-29 09:40:31 +000051import android.os.ParcelFileDescriptor;
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +000052import android.os.ParcelableException;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000053import android.os.PowerManager;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000054import android.os.RemoteException;
55import android.os.ServiceManager;
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +000056import android.os.UserHandle;
57import android.os.UserManagerInternal;
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +000058import android.os.storage.IStorageManager;
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +000059import android.os.storage.StorageManager;
Nikita Ioffee0dbc982019-05-09 19:33:35 +010060import android.util.IntArray;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000061import android.util.Slog;
Dario Frenibe98c3f2018-12-22 15:25:27 +000062import android.util.SparseArray;
Oli Lanc72b0bb2019-12-02 14:03:55 +000063import android.util.SparseIntArray;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000064import android.util.apk.ApkSignatureVerifier;
Dario Frenibe98c3f2018-12-22 15:25:27 +000065
66import com.android.internal.annotations.GuardedBy;
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +000067import com.android.internal.content.PackageHelper;
Narayan Kamath94c93ab2019-01-04 10:47:00 +000068import com.android.internal.os.BackgroundThread;
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +000069import com.android.server.LocalServices;
Dario Frenibe98c3f2018-12-22 15:25:27 +000070
Dario Freni4b572c02019-01-29 09:40:31 +000071import java.io.File;
72import java.io.IOException;
Dario Frenibe98c3f2018-12-22 15:25:27 +000073import java.util.ArrayList;
Dario Freni015f9352019-01-14 21:56:17 +000074import java.util.Arrays;
Dario Frenibe98c3f2018-12-22 15:25:27 +000075import java.util.List;
Dario Freni4b572c02019-01-29 09:40:31 +000076import java.util.concurrent.LinkedBlockingQueue;
77import java.util.concurrent.TimeUnit;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +010078import java.util.function.Consumer;
Dario Frenif141aab2019-02-04 14:48:30 +000079import java.util.function.Predicate;
Dario Freni015f9352019-01-14 21:56:17 +000080import java.util.stream.Collectors;
Dario Frenibe98c3f2018-12-22 15:25:27 +000081
82/**
83 * This class handles staged install sessions, i.e. install sessions that require packages to
84 * be installed only after a reboot.
85 */
86public class StagingManager {
87
88 private static final String TAG = "StagingManager";
89
Dario Freni4b572c02019-01-29 09:40:31 +000090 private final PackageInstallerService mPi;
Dario Freni2e8dffc2019-02-06 14:55:16 +000091 private final ApexManager mApexManager;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000092 private final PowerManager mPowerManager;
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +000093 private final Context mContext;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +010094 private final PreRebootVerificationHandler mPreRebootVerificationHandler;
Dario Frenibe98c3f2018-12-22 15:25:27 +000095
Dario Frenibe98c3f2018-12-22 15:25:27 +000096 @GuardedBy("mStagedSessions")
97 private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
98
Oli Lanc72b0bb2019-12-02 14:03:55 +000099 @GuardedBy("mStagedSessions")
100 private final SparseIntArray mSessionRollbackIds = new SparseIntArray();
101
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000102 StagingManager(PackageInstallerService pi, Context context) {
Dario Freni4b572c02019-01-29 09:40:31 +0000103 mPi = pi;
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +0000104 mContext = context;
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000105
106 mApexManager = ApexManager.getInstance();
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000107 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100108 mPreRebootVerificationHandler = new PreRebootVerificationHandler(
109 BackgroundThread.get().getLooper());
Dario Frenibe98c3f2018-12-22 15:25:27 +0000110 }
111
112 private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
113 synchronized (mStagedSessions) {
114 PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId);
Narayan Kamath94c93ab2019-01-04 10:47:00 +0000115 // storedSession might be null if a call to abortSession was made before the session
116 // is updated.
117 if (storedSession != null) {
118 mStagedSessions.put(sessionInfo.sessionId, sessionInfo);
Dario Frenibe98c3f2018-12-22 15:25:27 +0000119 }
Dario Frenibe98c3f2018-12-22 15:25:27 +0000120 }
121 }
122
123 ParceledListSlice<PackageInstaller.SessionInfo> getSessions() {
124 final List<PackageInstaller.SessionInfo> result = new ArrayList<>();
125 synchronized (mStagedSessions) {
126 for (int i = 0; i < mStagedSessions.size(); i++) {
127 result.add(mStagedSessions.valueAt(i).generateInfo(false));
128 }
129 }
130 return new ParceledListSlice<>(result);
131 }
132
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100133 /**
134 * Validates the signature used to sign the container of the new apex package
135 *
136 * @param newApexPkg The new apex package that is being installed
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100137 * @throws PackageManagerException
138 */
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000139 private void validateApexSignature(PackageInfo newApexPkg)
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100140 throws PackageManagerException {
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100141 // Get signing details of the new package
142 final String apexPath = newApexPkg.applicationInfo.sourceDir;
143 final String packageName = newApexPkg.packageName;
Michael Groover33df7c42020-01-24 19:00:01 -0800144 int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
145 newApexPkg.applicationInfo.targetSdkVersion);
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100146
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000147 final SigningDetails newSigningDetails;
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000148 try {
Michael Groover33df7c42020-01-24 19:00:01 -0800149 newSigningDetails = ApkSignatureVerifier.verify(apexPath, minSignatureScheme);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000150 } catch (PackageParserException e) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100151 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
152 "Failed to parse APEX package " + apexPath, e);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000153 }
154
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100155 // Get signing details of the existing package
156 final PackageInfo existingApexPkg = mApexManager.getPackageInfo(packageName,
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100157 ApexManager.MATCH_ACTIVE_PACKAGE);
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100158 if (existingApexPkg == null) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100159 // This should never happen, because submitSessionToApexService ensures that no new
160 // apexes were installed.
161 throw new IllegalStateException("Unknown apex package " + packageName);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000162 }
163
164 final SigningDetails existingSigningDetails;
165 try {
166 existingSigningDetails = ApkSignatureVerifier.verify(
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100167 existingApexPkg.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000168 } catch (PackageParserException e) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100169 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100170 "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir, e);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000171 }
172
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100173 // Verify signing details for upgrade
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000174 if (newSigningDetails.checkCapability(existingSigningDetails,
175 SigningDetails.CertCapabilities.INSTALLED_DATA)
176 || existingSigningDetails.checkCapability(newSigningDetails,
177 SigningDetails.CertCapabilities.ROLLBACK)) {
Mohammad Samiul Islam8e35db12019-08-22 15:43:55 +0100178 return;
179 }
180
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100181 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100182 "APK-container signature of APEX package " + packageName + " with version "
183 + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not"
184 + " compatible with the one currently installed on device");
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000185 }
186
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100187 private List<PackageInfo> submitSessionToApexService(
188 @NonNull PackageInstallerSession session) throws PackageManagerException {
Oli Lanc72b0bb2019-12-02 14:03:55 +0000189 final IntArray childSessionIds = new IntArray();
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100190 if (session.isMultiPackage()) {
191 for (int id : session.getChildSessionIds()) {
192 if (isApexSession(mStagedSessions.get(id))) {
Oli Lanc72b0bb2019-12-02 14:03:55 +0000193 childSessionIds.add(id);
194 }
195 }
196 }
197 ApexSessionParams apexSessionParams = new ApexSessionParams();
198 apexSessionParams.sessionId = session.sessionId;
199 apexSessionParams.childSessionIds = childSessionIds.toArray();
200 if (session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
201 apexSessionParams.isRollback = true;
202 apexSessionParams.rollbackId = retrieveRollbackIdForCommitSession(session.sessionId);
203 } else {
204 synchronized (mStagedSessions) {
205 int rollbackId = mSessionRollbackIds.get(session.sessionId, -1);
206 if (rollbackId != -1) {
207 apexSessionParams.hasRollbackEnabled = true;
208 apexSessionParams.rollbackId = rollbackId;
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100209 }
Nikita Ioffe8bbb8152019-02-21 15:54:54 +0000210 }
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100211 }
Nikita Ioffe4e7d24a2019-07-05 15:49:45 +0100212 // submitStagedSession will throw a PackageManagerException if apexd verification fails,
213 // which will be propagated to populate stagedSessionErrorMessage of this session.
Oli Lanc72b0bb2019-12-02 14:03:55 +0000214 final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100215 final List<PackageInfo> result = new ArrayList<>();
Oli Lanc2c7a222019-07-31 15:27:22 +0100216 for (ApexInfo apexInfo : apexInfoList.apexInfos) {
217 final PackageInfo packageInfo;
218 int flags = PackageManager.GET_META_DATA;
219 PackageParser.Package pkg;
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100220 try {
Oli Lanc2c7a222019-07-31 15:27:22 +0100221 File apexFile = new File(apexInfo.modulePath);
222 PackageParser pp = new PackageParser();
223 pkg = pp.parsePackage(apexFile, flags, false);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100224 } catch (PackageParserException e) {
225 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Oli Lanc2c7a222019-07-31 15:27:22 +0100226 "Failed to parse APEX package " + apexInfo.modulePath, e);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100227 }
Oli Lanc2c7a222019-07-31 15:27:22 +0100228 packageInfo = PackageParser.generatePackageInfo(pkg, apexInfo, flags);
229 final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName,
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100230 ApexManager.MATCH_ACTIVE_PACKAGE);
231 if (activePackage == null) {
Oli Lanc2c7a222019-07-31 15:27:22 +0100232 Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100233 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
234 "It is forbidden to install new APEX packages.");
235 }
236 checkRequiredVersionCode(session, activePackage);
Oli Lanc2c7a222019-07-31 15:27:22 +0100237 checkDowngrade(session, activePackage, packageInfo);
238 result.add(packageInfo);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100239 }
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100240 Slog.d(TAG, "Session " + session.sessionId + " has following APEX packages: ["
241 + result.stream().map(p -> p.packageName).collect(Collectors.joining(",")) + "]");
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100242 return result;
243 }
244
Oli Lanc72b0bb2019-12-02 14:03:55 +0000245 private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException {
246 RollbackManager rm = mContext.getSystemService(RollbackManager.class);
247
248 List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks();
249 for (RollbackInfo rollback : rollbacks) {
250 if (rollback.getCommittedSessionId() == sessionId) {
251 return rollback.getRollbackId();
252 }
253 }
254 throw new PackageManagerException(
255 "Could not find rollback id for commit session: " + sessionId);
256 }
257
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100258 private void checkRequiredVersionCode(final PackageInstallerSession session,
259 final PackageInfo activePackage) throws PackageManagerException {
260 if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) {
261 return;
262 }
263 final long activeVersion = activePackage.applicationInfo.longVersionCode;
264 if (activeVersion != session.params.requiredInstalledVersionCode) {
Mohammad Samiul Islam44ad95b2019-11-20 15:14:36 +0000265 if (!mApexManager.abortStagedSession(session.sessionId)) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100266 Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
267 }
268 throw new PackageManagerException(
269 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
270 "Installed version of APEX package " + activePackage.packageName
Richard Uhler2124d4b2019-04-25 13:01:39 +0100271 + " does not match required. Active version: " + activeVersion
272 + " required: " + session.params.requiredInstalledVersionCode);
Nikita Ioffe8bbb8152019-02-21 15:54:54 +0000273 }
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100274 }
275
276 private void checkDowngrade(final PackageInstallerSession session,
277 final PackageInfo activePackage, final PackageInfo newPackage)
278 throws PackageManagerException {
279 final long activeVersion = activePackage.applicationInfo.longVersionCode;
280 final long newVersionCode = newPackage.applicationInfo.longVersionCode;
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100281 final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100282 session.params.installFlags, activePackage.applicationInfo.flags);
283 if (activeVersion > newVersionCode && !allowsDowngrade) {
Mohammad Samiul Islam44ad95b2019-11-20 15:14:36 +0000284 if (!mApexManager.abortStagedSession(session.sessionId)) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100285 Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
286 }
287 throw new PackageManagerException(
288 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
289 "Downgrade of APEX package " + newPackage.packageName
290 + " is not allowed. Active version: " + activeVersion
291 + " attempted: " + newVersionCode);
292 }
Dario Freni015f9352019-01-14 21:56:17 +0000293 }
294
Dario Freni015f9352019-01-14 21:56:17 +0000295 private static boolean isApexSession(@NonNull PackageInstallerSession session) {
296 return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
297 }
298
Dario Frenif141aab2019-02-04 14:48:30 +0000299 private boolean sessionContains(@NonNull PackageInstallerSession session,
300 Predicate<PackageInstallerSession> filter) {
Dario Freni4b572c02019-01-29 09:40:31 +0000301 if (!session.isMultiPackage()) {
Dario Frenif141aab2019-02-04 14:48:30 +0000302 return filter.test(session);
Dario Freni4b572c02019-01-29 09:40:31 +0000303 }
304 synchronized (mStagedSessions) {
305 return !(Arrays.stream(session.getChildSessionIds())
306 // Retrieve cached sessions matching ids.
307 .mapToObj(i -> mStagedSessions.get(i))
308 // Filter only the ones containing APEX.
Dario Frenif141aab2019-02-04 14:48:30 +0000309 .filter(childSession -> filter.test(childSession))
Dario Freni4b572c02019-01-29 09:40:31 +0000310 .collect(Collectors.toList())
311 .isEmpty());
312 }
313 }
314
Dario Frenif141aab2019-02-04 14:48:30 +0000315 private boolean sessionContainsApex(@NonNull PackageInstallerSession session) {
316 return sessionContains(session, (s) -> isApexSession(s));
317 }
318
319 private boolean sessionContainsApk(@NonNull PackageInstallerSession session) {
320 return sessionContains(session, (s) -> !isApexSession(s));
321 }
322
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000323 // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
324 private void abortCheckpoint() {
325 try {
326 if (supportsCheckpoint() && needsCheckpoint()) {
327 mApexManager.revertActiveSessions();
328 PackageHelper.getStorageManager().abortChanges(
329 "StagingManager initiated", false /*retry*/);
330 }
331 } catch (Exception e) {
332 Slog.wtf(TAG, "Failed to abort checkpoint", e);
333 mApexManager.revertActiveSessions();
334 mPowerManager.reboot(null);
335 }
336 }
337
338 private boolean supportsCheckpoint() throws RemoteException {
339 return PackageHelper.getStorageManager().supportsCheckpoint();
340 }
341
342 private boolean needsCheckpoint() throws RemoteException {
343 return PackageHelper.getStorageManager().needsCheckpoint();
344 }
345
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000346 /**
Oli Lan7bbd8b42020-01-14 10:11:42 +0000347 * Perform snapshot and restore as required both for APEXes themselves and for apks in APEX.
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000348 * Apks inside apex are not installed using apk-install flow. They are scanned from the system
349 * directory directly by PackageManager, as such, RollbackManager need to handle their data
350 * separately here.
351 */
Oli Lan7bbd8b42020-01-14 10:11:42 +0000352 private void snapshotAndRestoreForApexSession(PackageInstallerSession session) {
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000353 if (!sessionContainsApex(session)) {
354 return;
355 }
356
357 boolean doSnapshotOrRestore =
358 (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
359 || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
360 if (!doSnapshotOrRestore) {
361 return;
362 }
363
364 // Find all the apex sessions that needs processing
365 List<PackageInstallerSession> apexSessions = new ArrayList<>();
366 if (session.isMultiPackage()) {
367 List<PackageInstallerSession> childrenSessions = new ArrayList<>();
368 synchronized (mStagedSessions) {
369 for (int childSessionId : session.getChildSessionIds()) {
370 PackageInstallerSession childSession = mStagedSessions.get(childSessionId);
371 if (childSession != null) {
372 childrenSessions.add(childSession);
373 }
374 }
375 }
376 for (PackageInstallerSession childSession : childrenSessions) {
377 if (sessionContainsApex(childSession)) {
378 apexSessions.add(childSession);
379 }
380 }
381 } else {
382 apexSessions.add(session);
383 }
384
Oli Lan7bbd8b42020-01-14 10:11:42 +0000385 final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
386 final int[] allUsers = um.getUserIds();
387 IRollbackManager rm = IRollbackManager.Stub.asInterface(
388 ServiceManager.getService(Context.ROLLBACK_SERVICE));
389
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000390 for (PackageInstallerSession apexSession : apexSessions) {
Oli Lan7bbd8b42020-01-14 10:11:42 +0000391 String packageName = apexSession.getPackageName();
392 // Perform any snapshots or restores for the APEX itself
393 snapshotAndRestoreApexUserData(packageName, allUsers, rm);
394
395 // Process the apks inside the APEX
396 List<String> apksInApex = mApexManager.getApksInApex(packageName);
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000397 for (String apk: apksInApex) {
Oli Lan7bbd8b42020-01-14 10:11:42 +0000398 snapshotAndRestoreApkInApexUserData(apk, allUsers, rm);
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000399 }
400 }
401 }
402
Oli Lan7bbd8b42020-01-14 10:11:42 +0000403 private void snapshotAndRestoreApexUserData(
404 String packageName, int[] allUsers, IRollbackManager rm) {
405 try {
406 // appId, ceDataInode, and seInfo are not needed for APEXes
407 rm.snapshotAndRestoreUserData(packageName, allUsers, 0, 0,
408 null, 0 /*token*/);
409 } catch (RemoteException re) {
410 Slog.e(TAG, "Error snapshotting/restoring user data: " + re);
411 }
412 }
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000413
Oli Lan7bbd8b42020-01-14 10:11:42 +0000414 private void snapshotAndRestoreApkInApexUserData(
415 String packageName, int[] allUsers, IRollbackManager rm) {
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000416 PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class);
417 AndroidPackage pkg = mPmi.getPackage(packageName);
418 if (pkg == null) {
419 Slog.e(TAG, "Could not find package: " + packageName
420 + "for snapshotting/restoring user data.");
421 return;
422 }
423 final String seInfo = pkg.getSeInfo();
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000424
425 int appId = -1;
426 long ceDataInode = -1;
427 final PackageSetting ps = (PackageSetting) mPmi.getPackageSetting(packageName);
Oli Lan7bbd8b42020-01-14 10:11:42 +0000428 if (ps != null) {
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000429 appId = ps.appId;
430 ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM);
431 // NOTE: We ignore the user specified in the InstallParam because we know this is
432 // an update, and hence need to restore data for all installed users.
433 final int[] installedUsers = ps.queryInstalledUsers(allUsers, true);
434
435 try {
436 rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode,
437 seInfo, 0 /*token*/);
438 } catch (RemoteException re) {
439 Slog.e(TAG, "Error snapshotting/restoring user data: " + re);
440 }
441 }
442 }
443
Dario Freni61b3e252019-01-11 23:05:39 +0000444 private void resumeSession(@NonNull PackageInstallerSession session) {
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100445 Slog.d(TAG, "Resuming session " + session.sessionId);
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000446
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100447 final boolean hasApex = sessionContainsApex(session);
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000448 ApexSessionInfo apexSessionInfo = null;
Nikita Ioffea820bd92019-02-15 14:22:44 +0000449 if (hasApex) {
Dario Freni2e8dffc2019-02-06 14:55:16 +0000450 // Check with apexservice whether the apex packages have been activated.
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000451 apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
452
453 if (apexSessionInfo != null && apexSessionInfo.isVerified) {
454 // Session has been previously submitted to apexd, but didn't complete all the
455 // pre-reboot verification, perhaps because the device rebooted in the meantime.
456 // Greedily re-trigger the pre-reboot verification. We want to avoid marking it as
457 // failed when not in checkpoint mode, hence it is being processed separately.
458 Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to "
459 + "be verified, resuming pre-reboot verification");
460 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
461 return;
462 }
463 }
464
465 // Before we resume session, we check if revert is needed or not. Typically, we enter file-
466 // system checkpoint mode when we reboot first time in order to install staged sessions. We
467 // want to install staged sessions in this mode as rebooting now will revert user data. If
468 // something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will
469 // have no effect on user data, so mark the sessions as failed instead.
470 try {
471 // If checkpoint is supported, then we only resume sessions if we are in checkpointing
472 // mode. If not, we fail all sessions.
473 if (supportsCheckpoint() && !needsCheckpoint()) {
474 // TODO(b/146343545): Persist failure reason across checkpoint reboot
475 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
476 "Reverting back to safe state");
477 return;
478 }
479 } catch (RemoteException e) {
480 // Cannot continue staged install without knowing if fs-checkpoint is supported
481 Slog.e(TAG, "Checkpoint support unknown. Aborting staged install for session "
482 + session.sessionId, e);
483 // TODO: Mark all staged sessions together and reboot only once
484 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
485 "Checkpoint support unknown. Aborting staged install.");
486 if (hasApex) {
487 mApexManager.revertActiveSessions();
488 }
489 mPowerManager.reboot("Checkpoint support unknown");
490 return;
491 }
492
493 if (hasApex) {
Dario Freni2e8dffc2019-02-06 14:55:16 +0000494 if (apexSessionInfo == null) {
495 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
496 "apexd did not know anything about a staged session supposed to be"
497 + "activated");
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000498 abortCheckpoint();
Dario Freni4b572c02019-01-29 09:40:31 +0000499 return;
500 }
Nikita Ioffe82742222019-02-26 12:14:04 +0000501 if (isApexSessionFailed(apexSessionInfo)) {
Dario Frenib6d28962019-01-31 15:52:24 +0000502 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
Dario Freni4b572c02019-01-29 09:40:31 +0000503 "APEX activation failed. Check logcat messages from apexd for "
504 + "more information.");
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000505 abortCheckpoint();
Dario Freni4b572c02019-01-29 09:40:31 +0000506 return;
507 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000508 if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000509 // Apexd did not apply the session for some unknown reason. There is no guarantee
510 // that apexd will install it next time. Safer to proactively mark as failed.
511 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
512 "Staged session " + session.sessionId + "at boot didn't "
513 + "activate nor fail. Marking it as failed anyway.");
514 abortCheckpoint();
Dario Freni4b572c02019-01-29 09:40:31 +0000515 return;
516 }
Oli Lan7bbd8b42020-01-14 10:11:42 +0000517 snapshotAndRestoreForApexSession(session);
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100518 Slog.i(TAG, "APEX packages in session " + session.sessionId
519 + " were successfully activated. Proceeding with APK packages, if any");
Dario Freni4b572c02019-01-29 09:40:31 +0000520 }
521 // The APEX part of the session is activated, proceed with the installation of APKs.
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100522 try {
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100523 Slog.d(TAG, "Installing APK packages in session " + session.sessionId);
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100524 installApksInSession(session);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100525 } catch (PackageManagerException e) {
526 session.setStagedSessionFailed(e.error, e.getMessage());
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000527 abortCheckpoint();
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000528
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000529 // If checkpoint is not supported, we have to handle failure for one staged session.
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000530 if (!hasApex) {
531 return;
532 }
533
Mohammad Samiul Islam1a96eb62019-11-21 10:38:06 +0000534 if (!mApexManager.revertActiveSessions()) {
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000535 Slog.e(TAG, "Failed to abort APEXd session");
536 } else {
537 Slog.e(TAG,
538 "Successfully aborted apexd session. Rebooting device in order to revert "
539 + "to the previous state of APEXd.");
540 mPowerManager.reboot(null);
541 }
Dario Freni61b3e252019-01-11 23:05:39 +0000542 return;
543 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000544
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100545 Slog.d(TAG, "Marking session " + session.sessionId + " as applied");
Dario Freni4b572c02019-01-29 09:40:31 +0000546 session.setStagedSessionApplied();
Nikita Ioffea820bd92019-02-15 14:22:44 +0000547 if (hasApex) {
548 mApexManager.markStagedSessionSuccessful(session.sessionId);
549 }
Dario Freni4b572c02019-01-29 09:40:31 +0000550 }
551
Dario Freni815bd212019-02-20 14:09:36 +0000552 private List<String> findAPKsInDir(File stageDir) {
553 List<String> ret = new ArrayList<>();
Dario Freni4b572c02019-01-29 09:40:31 +0000554 if (stageDir != null && stageDir.exists()) {
555 for (File file : stageDir.listFiles()) {
556 if (file.getAbsolutePath().toLowerCase().endsWith(".apk")) {
Dario Freni815bd212019-02-20 14:09:36 +0000557 ret.add(file.getAbsolutePath());
Dario Freni4b572c02019-01-29 09:40:31 +0000558 }
559 }
Dario Freni61b3e252019-01-11 23:05:39 +0000560 }
Dario Freni815bd212019-02-20 14:09:36 +0000561 return ret;
Dario Freni4b572c02019-01-29 09:40:31 +0000562 }
563
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100564 @NonNull
Dario Freni4b572c02019-01-29 09:40:31 +0000565 private PackageInstallerSession createAndWriteApkSession(
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100566 @NonNull PackageInstallerSession originalSession, boolean preReboot)
567 throws PackageManagerException {
568 final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
569 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
Dario Freni4b572c02019-01-29 09:40:31 +0000570 if (originalSession.stageDir == null) {
571 Slog.wtf(TAG, "Attempting to install a staged APK session with no staging dir");
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100572 throw new PackageManagerException(errorCode,
573 "Attempting to install a staged APK session with no staging dir");
Dario Freni1473bcb2019-01-25 14:27:13 +0000574 }
Dario Freni815bd212019-02-20 14:09:36 +0000575 List<String> apkFilePaths = findAPKsInDir(originalSession.stageDir);
576 if (apkFilePaths.isEmpty()) {
Dario Freni4b572c02019-01-29 09:40:31 +0000577 Slog.w(TAG, "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100578 throw new PackageManagerException(errorCode,
579 "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
Dario Freni61b3e252019-01-11 23:05:39 +0000580 }
Dario Freni4b572c02019-01-29 09:40:31 +0000581
582 PackageInstaller.SessionParams params = originalSession.params.copy();
583 params.isStaged = false;
Dario Frenia2afebf2019-05-01 16:03:17 +0100584 params.installFlags |= PackageManager.INSTALL_STAGED;
Dario Frenic3e68ea2019-04-02 11:45:13 +0100585 // TODO(b/129744602): use the userid from the original session.
Dario Frenif141aab2019-02-04 14:48:30 +0000586 if (preReboot) {
587 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
588 params.installFlags |= PackageManager.INSTALL_DRY_RUN;
589 } else {
590 params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
591 }
Dario Freni4b572c02019-01-29 09:40:31 +0000592 try {
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +0000593 int apkSessionId = mPi.createSession(
594 params, originalSession.getInstallerPackageName(),
595 0 /* UserHandle.SYSTEM */);
596 PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
Dario Freni4b572c02019-01-29 09:40:31 +0000597 apkSession.open();
Dario Freni815bd212019-02-20 14:09:36 +0000598 for (String apkFilePath : apkFilePaths) {
599 File apkFile = new File(apkFilePath);
600 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
601 ParcelFileDescriptor.MODE_READ_ONLY);
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +0000602 long sizeBytes = (pfd == null) ? -1 : pfd.getStatSize();
Dario Freni815bd212019-02-20 14:09:36 +0000603 if (sizeBytes < 0) {
604 Slog.e(TAG, "Unable to get size of: " + apkFilePath);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100605 throw new PackageManagerException(errorCode,
606 "Unable to get size of: " + apkFilePath);
Dario Freni815bd212019-02-20 14:09:36 +0000607 }
608 apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
Dario Freni4b572c02019-01-29 09:40:31 +0000609 }
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +0000610 return apkSession;
611 } catch (IOException | ParcelableException e) {
Dario Freni4b572c02019-01-29 09:40:31 +0000612 Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +0000613 throw new PackageManagerException(errorCode, "Failed to create/write APK session", e);
Dario Freni4b572c02019-01-29 09:40:31 +0000614 }
Dario Freni4b572c02019-01-29 09:40:31 +0000615 }
616
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100617 /**
618 * Extract apks in the given session into a new session. Returns {@code null} if there is no
619 * apks in the given session. Only parent session is returned for multi-package session.
620 */
621 @Nullable
622 private PackageInstallerSession extractApksInSession(PackageInstallerSession session,
623 boolean preReboot) throws PackageManagerException {
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100624 final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
625 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
Dario Freni4b572c02019-01-29 09:40:31 +0000626 if (!session.isMultiPackage() && !isApexSession(session)) {
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100627 return createAndWriteApkSession(session, preReboot);
Dario Freni4b572c02019-01-29 09:40:31 +0000628 } else if (session.isMultiPackage()) {
629 // For multi-package staged sessions containing APKs, we identify which child sessions
630 // contain an APK, and with those then create a new multi-package group of sessions,
631 // carrying over all the session parameters and unmarking them as staged. On commit the
632 // sessions will be installed atomically.
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100633 final List<PackageInstallerSession> childSessions;
Dario Freni4b572c02019-01-29 09:40:31 +0000634 synchronized (mStagedSessions) {
635 childSessions =
636 Arrays.stream(session.getChildSessionIds())
637 // Retrieve cached sessions matching ids.
638 .mapToObj(i -> mStagedSessions.get(i))
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000639 // Filter only the ones containing APKs.
Dario Freni4b572c02019-01-29 09:40:31 +0000640 .filter(childSession -> !isApexSession(childSession))
641 .collect(Collectors.toList());
642 }
643 if (childSessions.isEmpty()) {
644 // APEX-only multi-package staged session, nothing to do.
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100645 return null;
Dario Freni4b572c02019-01-29 09:40:31 +0000646 }
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100647 final PackageInstaller.SessionParams params = session.params.copy();
Dario Freni4b572c02019-01-29 09:40:31 +0000648 params.isStaged = false;
Dario Frenif141aab2019-02-04 14:48:30 +0000649 if (preReboot) {
650 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
651 }
Dario Frenic3e68ea2019-04-02 11:45:13 +0100652 // TODO(b/129744602): use the userid from the original session.
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100653 final int apkParentSessionId = mPi.createSession(
Dario Frenic3e68ea2019-04-02 11:45:13 +0100654 params, session.getInstallerPackageName(),
655 0 /* UserHandle.SYSTEM */);
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100656 final PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
Dario Freni4b572c02019-01-29 09:40:31 +0000657 try {
658 apkParentSession.open();
659 } catch (IOException e) {
660 Slog.e(TAG, "Unable to prepare multi-package session for staged session "
661 + session.sessionId);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100662 throw new PackageManagerException(errorCode,
663 "Unable to prepare multi-package session for staged session");
Dario Freni4b572c02019-01-29 09:40:31 +0000664 }
665
666 for (PackageInstallerSession sessionToClone : childSessions) {
Dario Frenif141aab2019-02-04 14:48:30 +0000667 PackageInstallerSession apkChildSession =
668 createAndWriteApkSession(sessionToClone, preReboot);
Dario Freni09660472019-02-18 16:44:23 +0000669 try {
670 apkParentSession.addChildSessionId(apkChildSession.sessionId);
Patrick Baumann00321b72019-04-09 15:07:25 -0700671 } catch (IllegalStateException e) {
Dario Freni09660472019-02-18 16:44:23 +0000672 Slog.e(TAG, "Failed to add a child session for installing the APK files", e);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100673 throw new PackageManagerException(errorCode,
674 "Failed to add a child session " + apkChildSession.sessionId);
Dario Freni09660472019-02-18 16:44:23 +0000675 }
Dario Freni4b572c02019-01-29 09:40:31 +0000676 }
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100677 return apkParentSession;
Dario Freni4b572c02019-01-29 09:40:31 +0000678 }
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100679 return null;
680 }
681
682 private void verifyApksInSession(PackageInstallerSession session)
683 throws PackageManagerException {
684
685 final PackageInstallerSession apksToVerify = extractApksInSession(
686 session, /* preReboot */ true);
687 if (apksToVerify == null) {
688 return;
689 }
690
691 final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync(
692 (Intent result) -> {
693 int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
694 PackageInstaller.STATUS_FAILURE);
695 if (status != PackageInstaller.STATUS_SUCCESS) {
696 final String errorMessage = result.getStringExtra(
697 PackageInstaller.EXTRA_STATUS_MESSAGE);
698 Slog.e(TAG, "Failure to verify APK staged session "
699 + session.sessionId + " [" + errorMessage + "]");
700 session.setStagedSessionFailed(
701 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage);
702 return;
703 }
704 mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
705 session.sessionId);
706 });
707
708 apksToVerify.commit(receiver.getIntentSender(), false);
709 }
710
711 private void installApksInSession(@NonNull PackageInstallerSession session)
712 throws PackageManagerException {
713
714 final PackageInstallerSession apksToInstall = extractApksInSession(
715 session, /* preReboot */ false);
716 if (apksToInstall == null) {
717 return;
718 }
719
720 if ((apksToInstall.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
721 // If rollback is available for this session, notify the rollback
722 // manager of the apk session so it can properly enable rollback.
723 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
724 ServiceManager.getService(Context.ROLLBACK_SERVICE));
725 try {
726 rm.notifyStagedApkSession(session.sessionId, apksToInstall.sessionId);
727 } catch (RemoteException re) {
728 Slog.e(TAG, "Failed to notifyStagedApkSession for session: "
729 + session.sessionId, re);
730 }
731 }
732
733 final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
734 apksToInstall.commit(receiver.getIntentSender(), false);
735 final Intent result = receiver.getResult();
736 final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
737 PackageInstaller.STATUS_FAILURE);
738 if (status != PackageInstaller.STATUS_SUCCESS) {
739 final String errorMessage = result.getStringExtra(
740 PackageInstaller.EXTRA_STATUS_MESSAGE);
741 Slog.e(TAG, "Failure to install APK staged session "
742 + session.sessionId + " [" + errorMessage + "]");
743 throw new PackageManagerException(
744 SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage);
745 }
Dario Freni61b3e252019-01-11 23:05:39 +0000746 }
747
Nikita Ioffe8e5b703c2019-03-14 18:40:17 +0000748 void commitSession(@NonNull PackageInstallerSession session) {
Dario Freni276cd072019-01-09 11:13:44 +0000749 updateStoredSession(session);
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100750 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
Dario Frenibe98c3f2018-12-22 15:25:27 +0000751 }
752
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +0000753 private int parentOrOwnSessionId(PackageInstallerSession session) {
754 return session.hasParentSessionId() ? session.getParentSessionId() : session.sessionId;
755 }
756
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100757 /**
758 * <p> Check if the session provided is non-overlapping with the active staged sessions.
759 *
760 * <p> A session is non-overlapping if it meets one of the following conditions: </p>
761 * <ul>
762 * <li>It is a parent session</li>
763 * <li>It is already one of the active sessions</li>
764 * <li>Its package name is not same as any of the active sessions</li>
765 * </ul>
766 * @throws PackageManagerException if session fails the check
767 */
768 void checkNonOverlappingWithStagedSessions(@NonNull PackageInstallerSession session)
769 throws PackageManagerException {
770 if (session.isMultiPackage()) {
771 // We cannot say a parent session overlaps until we process its children
772 return;
773 }
774 if (session.getPackageName() == null) {
775 throw new PackageManagerException(PackageManager.INSTALL_FAILED_INVALID_APK,
776 "Cannot stage session " + session.sessionId + " with package name null");
777 }
778
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +0000779 boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService(
780 Context.STORAGE_SERVICE)).isCheckpointSupported();
781
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000782 synchronized (mStagedSessions) {
783 for (int i = 0; i < mStagedSessions.size(); i++) {
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100784 final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
785 if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()) {
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000786 continue;
787 }
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100788 if (stagedSession.isMultiPackage()) {
789 // This active parent staged session is useless as it doesn't have a package
790 // name and the session we are checking is not a parent session either.
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000791 continue;
792 }
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100793
794 // From here on, stagedSession is a non-parent active staged session
795
796 // Check if stagedSession has an active parent session or not
797 if (stagedSession.hasParentSessionId()) {
798 int parentId = stagedSession.getParentSessionId();
799 PackageInstallerSession parentSession = mStagedSessions.get(parentId);
800 if (parentSession == null || parentSession.isStagedAndInTerminalState()) {
801 // Parent session has been abandoned or terminated already
802 continue;
803 }
804 }
805
806 // Check if session is one of the active sessions
807 if (session.sessionId == stagedSession.sessionId) {
808 Slog.w(TAG, "Session " + session.sessionId + " is already staged");
809 continue;
810 }
811
812 // If session is not among the active sessions, then it cannot have same package
813 // name as any of the active sessions.
814 if (session.getPackageName().equals(stagedSession.getPackageName())) {
815 throw new PackageManagerException(
816 PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
817 "Package: " + session.getPackageName() + " in session: "
818 + session.sessionId + " has been staged already by session: "
819 + stagedSession.sessionId, null);
820 }
821
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +0000822 // Staging multiple root sessions is not allowed if device doesn't support
823 // checkpoint. If session and stagedSession do not have common ancestor, they are
824 // from two different root sessions.
825 if (!supportsCheckpoint
826 && parentOrOwnSessionId(session) != parentOrOwnSessionId(stagedSession)) {
827 throw new PackageManagerException(
828 PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
829 "Cannot stage multiple sessions without checkpoint support", null);
830 }
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000831 }
832 }
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000833 }
834
Dario Frenibe98c3f2018-12-22 15:25:27 +0000835 void createSession(@NonNull PackageInstallerSession sessionInfo) {
836 synchronized (mStagedSessions) {
837 mStagedSessions.append(sessionInfo.sessionId, sessionInfo);
838 }
839 }
840
Dario Freni015f9352019-01-14 21:56:17 +0000841 void abortSession(@NonNull PackageInstallerSession session) {
Dario Frenibe98c3f2018-12-22 15:25:27 +0000842 synchronized (mStagedSessions) {
Dario Freni015f9352019-01-14 21:56:17 +0000843 mStagedSessions.remove(session.sessionId);
Oli Lanc72b0bb2019-12-02 14:03:55 +0000844 mSessionRollbackIds.delete(session.sessionId);
Dario Frenibe98c3f2018-12-22 15:25:27 +0000845 }
846 }
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000847
shafik07205e32019-02-07 20:12:33 +0000848 void abortCommittedSession(@NonNull PackageInstallerSession session) {
Nikita Ioffe1e523b52019-03-13 16:07:48 +0000849 if (session.isStagedSessionApplied()) {
850 Slog.w(TAG, "Cannot abort applied session : " + session.sessionId);
shafik07205e32019-02-07 20:12:33 +0000851 return;
852 }
shafik07205e32019-02-07 20:12:33 +0000853 abortSession(session);
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000854
855 boolean hasApex = sessionContainsApex(session);
856 if (hasApex) {
857 ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId);
858 if (apexSession == null || isApexSessionFinalized(apexSession)) {
859 Slog.w(TAG,
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100860 "Cannot abort session " + session.sessionId
861 + " because it is not active or APEXD is not reachable");
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000862 return;
863 }
Mohammad Samiul Islam44ad95b2019-11-20 15:14:36 +0000864 try {
865 mApexManager.abortStagedSession(session.sessionId);
866 } catch (Exception ignore) {
867 }
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000868 }
shafik07205e32019-02-07 20:12:33 +0000869 }
870
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000871 private boolean isApexSessionFinalized(ApexSessionInfo session) {
shafik07205e32019-02-07 20:12:33 +0000872 /* checking if the session is in a final state, i.e., not active anymore */
Nikita Ioffe82742222019-02-26 12:14:04 +0000873 return session.isUnknown || session.isActivationFailed || session.isSuccess
Mohammad Samiul Islam2dfc86a2019-11-20 13:53:07 +0000874 || session.isReverted;
Nikita Ioffe82742222019-02-26 12:14:04 +0000875 }
876
877 private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) {
Mohammad Samiul Islam2dfc86a2019-11-20 13:53:07 +0000878 // isRevertInProgress is included to cover the scenario, when a device is rebooted
879 // during the revert, and apexd fails to resume the revert after reboot.
Nikita Ioffe82742222019-02-26 12:14:04 +0000880 return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown
Mohammad Samiul Islam2dfc86a2019-11-20 13:53:07 +0000881 || apexSessionInfo.isReverted || apexSessionInfo.isRevertInProgress
882 || apexSessionInfo.isRevertFailed;
shafik07205e32019-02-07 20:12:33 +0000883 }
884
Dario Freni015f9352019-01-14 21:56:17 +0000885 @GuardedBy("mStagedSessions")
886 private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) {
887 // This method assumes that the argument is either a parent session of a multi-package
888 // i.e. isMultiPackage() returns true, or that it is a child session, i.e.
889 // hasParentSessionId() returns true.
890 if (session.isMultiPackage()) {
891 // Parent session of a multi-package group. Check that we restored all the children.
892 for (int childSession : session.getChildSessionIds()) {
893 if (mStagedSessions.get(childSession) == null) {
894 return false;
895 }
896 }
897 return true;
898 }
899 if (session.hasParentSessionId()) {
900 PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId());
901 if (parent == null) {
902 return false;
903 }
904 return isMultiPackageSessionComplete(parent);
905 }
906 Slog.wtf(TAG, "Attempting to restore an invalid multi-package session.");
907 return false;
908 }
909
Rhed Jao1fc8b362020-01-16 18:38:17 +0800910 void restoreSession(@NonNull PackageInstallerSession session, boolean isDeviceUpgrading) {
Dario Freni015f9352019-01-14 21:56:17 +0000911 PackageInstallerSession sessionToResume = session;
912 synchronized (mStagedSessions) {
913 mStagedSessions.append(session.sessionId, session);
914 // For multi-package sessions, we don't know in which order they will be restored. We
915 // need to wait until we have restored all the session in a group before restoring them.
916 if (session.isMultiPackage() || session.hasParentSessionId()) {
917 if (!isMultiPackageSessionComplete(session)) {
918 // Still haven't recovered all sessions of the group, return.
919 return;
920 }
921 // Group recovered, find the parent if necessary and resume the installation.
922 if (session.hasParentSessionId()) {
923 sessionToResume = mStagedSessions.get(session.getParentSessionId());
924 }
925 }
926 }
Rhed Jao1fc8b362020-01-16 18:38:17 +0800927 // The preconditions used during pre-reboot verification might have changed when device
928 // is upgrading. Updated staged sessions to activation failed before we resume the session.
929 if (isDeviceUpgrading && !sessionToResume.isStagedAndInTerminalState()) {
930 sessionToResume.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
931 "Build fingerprint has changed");
932 return;
933 }
Dario Freni015f9352019-01-14 21:56:17 +0000934 checkStateAndResume(sessionToResume);
935 }
936
937 private void checkStateAndResume(@NonNull PackageInstallerSession session) {
Dario Freni47799f42019-03-13 18:06:24 +0000938 if (!session.isCommitted()) {
939 // Session hasn't been committed yet, ignore.
940 return;
941 }
Dario Freni61b3e252019-01-11 23:05:39 +0000942 // Check the state of the session and decide what to do next.
943 if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
944 // Final states, nothing to do.
945 return;
946 }
947 if (!session.isStagedSessionReady()) {
948 // The framework got restarted before the pre-reboot verification could complete,
949 // restart the verification.
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100950 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
Dario Freni61b3e252019-01-11 23:05:39 +0000951 } else {
952 // Session had already being marked ready. Start the checks to verify if there is any
953 // follow-up work.
Dario Freni4df010e2019-01-31 19:34:46 +0000954 resumeSession(session);
Dario Freni61b3e252019-01-11 23:05:39 +0000955 }
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000956 }
Dario Freni4b572c02019-01-29 09:40:31 +0000957
Rhed Jaob793c212019-10-30 20:54:37 +0800958 void systemReady() {
959 // Register the receiver of boot completed intent for staging manager.
960 mContext.registerReceiver(new BroadcastReceiver() {
961 @Override
962 public void onReceive(Context ctx, Intent intent) {
963 mPreRebootVerificationHandler.readyToStart();
964 ctx.unregisterReceiver(this);
965 }
966 }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
967 }
968
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100969 private static class LocalIntentReceiverAsync {
970 final Consumer<Intent> mConsumer;
971
972 LocalIntentReceiverAsync(Consumer<Intent> consumer) {
973 mConsumer = consumer;
974 }
975
976 private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
977 @Override
978 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
979 IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
980 mConsumer.accept(intent);
981 }
982 };
983
984 public IntentSender getIntentSender() {
985 return new IntentSender((IIntentSender) mLocalSender);
986 }
987 }
988
989 private static class LocalIntentReceiverSync {
Dario Freni4b572c02019-01-29 09:40:31 +0000990 private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
991
992 private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
993 @Override
994 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100995 IIntentReceiver finishedReceiver, String requiredPermission,
996 Bundle options) {
Dario Freni4b572c02019-01-29 09:40:31 +0000997 try {
998 mResult.offer(intent, 5, TimeUnit.SECONDS);
999 } catch (InterruptedException e) {
1000 throw new RuntimeException(e);
1001 }
1002 }
1003 };
1004
1005 public IntentSender getIntentSender() {
1006 return new IntentSender((IIntentSender) mLocalSender);
1007 }
1008
1009 public Intent getResult() {
1010 try {
1011 return mResult.take();
1012 } catch (InterruptedException e) {
1013 throw new RuntimeException(e);
1014 }
1015 }
1016 }
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001017
1018 private final class PreRebootVerificationHandler extends Handler {
Rhed Jaob793c212019-10-30 20:54:37 +08001019 // Hold session ids before handler gets ready to do the verification.
1020 private IntArray mPendingSessionIds;
1021 private boolean mIsReady;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001022
1023 PreRebootVerificationHandler(Looper looper) {
1024 super(looper);
1025 }
1026
1027 /**
1028 * Handler for states of pre reboot verification. The states are arranged linearly (shown
1029 * below) with each state either calling the next state, or calling some other method that
1030 * eventually calls the next state.
1031 *
1032 * <p><ul>
1033 * <li>MSG_PRE_REBOOT_VERIFICATION_START</li>
1034 * <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li>
1035 * <li>MSG_PRE_REBOOT_VERIFICATION_APK</li>
1036 * <li>MSG_PRE_REBOOT_VERIFICATION_END</li>
1037 * </ul></p>
1038 *
1039 * Details about each of state can be found in corresponding handler of node.
1040 */
1041 private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
1042 private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
1043 private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3;
1044 private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4;
1045
1046 @Override
1047 public void handleMessage(Message msg) {
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001048 final int sessionId = msg.arg1;
1049 final PackageInstallerSession session;
1050 synchronized (mStagedSessions) {
1051 session = mStagedSessions.get(sessionId);
1052 }
1053 // Maybe session was aborted before pre-reboot verification was complete
1054 if (session == null) {
1055 Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId);
1056 return;
1057 }
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001058 switch (msg.what) {
1059 case MSG_PRE_REBOOT_VERIFICATION_START:
1060 handlePreRebootVerification_Start(session);
1061 break;
1062 case MSG_PRE_REBOOT_VERIFICATION_APEX:
1063 handlePreRebootVerification_Apex(session);
1064 break;
1065 case MSG_PRE_REBOOT_VERIFICATION_APK:
1066 handlePreRebootVerification_Apk(session);
1067 break;
1068 case MSG_PRE_REBOOT_VERIFICATION_END:
1069 handlePreRebootVerification_End(session);
1070 break;
1071 }
1072 }
1073
Rhed Jaob793c212019-10-30 20:54:37 +08001074 // Notify the handler that system is ready, and reschedule the pre-reboot verifications.
1075 private synchronized void readyToStart() {
1076 mIsReady = true;
1077 if (mPendingSessionIds != null) {
1078 for (int i = 0; i < mPendingSessionIds.size(); i++) {
1079 startPreRebootVerification(mPendingSessionIds.get(i));
1080 }
1081 mPendingSessionIds = null;
1082 }
1083 }
1084
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001085 // Method for starting the pre-reboot verification
Rhed Jaob793c212019-10-30 20:54:37 +08001086 private synchronized void startPreRebootVerification(int sessionId) {
1087 if (!mIsReady) {
1088 if (mPendingSessionIds == null) {
1089 mPendingSessionIds = new IntArray();
1090 }
1091 mPendingSessionIds.add(sessionId);
1092 return;
1093 }
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001094 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001095 }
1096
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001097 private void notifyPreRebootVerification_Start_Complete(int sessionId) {
1098 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001099 }
1100
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001101 private void notifyPreRebootVerification_Apex_Complete(int sessionId) {
1102 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001103 }
1104
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001105 private void notifyPreRebootVerification_Apk_Complete(int sessionId) {
1106 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001107 }
1108
1109 /**
1110 * A dummy state for starting the pre reboot verification.
1111 *
1112 * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification
1113 */
1114 private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
1115 Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
Oli Lanc72b0bb2019-12-02 14:03:55 +00001116
1117 if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
1118 // If rollback is enabled for this session, we call through to the RollbackManager
1119 // with the list of sessions it must enable rollback for. Note that
1120 // notifyStagedSession is a synchronous operation.
1121 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
1122 ServiceManager.getService(Context.ROLLBACK_SERVICE));
1123 try {
1124 // NOTE: To stay consistent with the non-staged install flow, we don't fail the
1125 // entire install if rollbacks can't be enabled.
1126 int rollbackId = rm.notifyStagedSession(session.sessionId);
1127 if (rollbackId != -1) {
1128 synchronized (mStagedSessions) {
1129 mSessionRollbackIds.put(session.sessionId, rollbackId);
1130 }
1131 }
1132 } catch (RemoteException re) {
1133 Slog.e(TAG, "Failed to notifyStagedSession for session: "
1134 + session.sessionId, re);
1135 }
1136 }
1137
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001138 notifyPreRebootVerification_Start_Complete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001139 }
1140
1141 /**
1142 * Pre-reboot verification state for apex files:
1143 *
1144 * <p><ul>
1145 * <li>submits session to apex service</li>
1146 * <li>validates signatures of apex files</li>
1147 * </ul></p>
1148 */
1149 private void handlePreRebootVerification_Apex(@NonNull PackageInstallerSession session) {
1150 final boolean hasApex = sessionContainsApex(session);
1151
1152 // APEX checks. For single-package sessions, check if they contain an APEX. For
1153 // multi-package sessions, find all the child sessions that contain an APEX.
1154 if (hasApex) {
1155 try {
1156 final List<PackageInfo> apexPackages =
1157 submitSessionToApexService(session);
1158 for (PackageInfo apexPackage : apexPackages) {
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +00001159 validateApexSignature(apexPackage);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001160 }
1161 } catch (PackageManagerException e) {
1162 session.setStagedSessionFailed(e.error, e.getMessage());
1163 return;
1164 }
1165 }
1166
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001167 notifyPreRebootVerification_Apex_Complete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001168 }
1169
1170 /**
1171 * Pre-reboot verification state for apk files:
1172 * <p><ul>
1173 * <li>performs a dry-run install of apk</li>
1174 * </ul></p>
1175 */
1176 private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) {
1177 if (!sessionContainsApk(session)) {
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001178 notifyPreRebootVerification_Apk_Complete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001179 return;
1180 }
1181
1182 try {
1183 Slog.d(TAG, "Running a pre-reboot verification for APKs in session "
1184 + session.sessionId + " by performing a dry-run install");
1185
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +01001186 // verifyApksInSession will notify the handler when APK verification is complete
1187 verifyApksInSession(session);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001188 // TODO(b/118865310): abort the session on apexd.
1189 } catch (PackageManagerException e) {
1190 session.setStagedSessionFailed(e.error, e.getMessage());
1191 }
1192 }
1193
1194 /**
1195 * Pre-reboot verification state for wrapping up:
1196 * <p><ul>
1197 * <li>enables rollback if required</li>
1198 * <li>marks session as ready</li>
1199 * </ul></p>
1200 */
1201 private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) {
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +00001202 // Before marking the session as ready, start checkpoint service if available
1203 try {
1204 IStorageManager storageManager = PackageHelper.getStorageManager();
1205 if (storageManager.supportsCheckpoint()) {
1206 storageManager.startCheckpoint(1);
1207 }
1208 } catch (Exception e) {
1209 // Failed to get hold of StorageManager
1210 Slog.e(TAG, "Failed to get hold of StorageManager", e);
1211 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
1212 "Failed to get hold of StorageManager");
1213 return;
1214 }
1215
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001216 // Proactively mark session as ready before calling apexd. Although this call order
1217 // looks counter-intuitive, this is the easiest way to ensure that session won't end up
1218 // in the inconsistent state:
1219 // - If device gets rebooted right before call to apexd, then apexd will never activate
1220 // apex files of this staged session. This will result in StagingManager failing
1221 // the session.
1222 // On the other hand, if the order of the calls was inverted (first call apexd, then
1223 // mark session as ready), then if a device gets rebooted right after the call to apexd,
1224 // only apex part of the train will be applied, leaving device in an inconsistent state.
1225 Slog.d(TAG, "Marking session " + session.sessionId + " as ready");
1226 session.setStagedSessionReady();
1227 final boolean hasApex = sessionContainsApex(session);
1228 if (!hasApex) {
1229 // Session doesn't contain apex, nothing to do.
1230 return;
1231 }
1232 try {
1233 mApexManager.markStagedSessionReady(session.sessionId);
1234 } catch (PackageManagerException e) {
1235 session.setStagedSessionFailed(e.error, e.getMessage());
1236 }
1237 }
1238 }
Dario Frenibe98c3f2018-12-22 15:25:27 +00001239}