blob: 342c90719916e794cb52b07f8379d4e6b2e552eb [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;
Winsonf00c7552020-01-28 12:52:01 -080032import android.content.pm.ApplicationInfo;
Dario Freni2e8dffc2019-02-06 14:55:16 +000033import android.content.pm.PackageInfo;
Dario Frenibe98c3f2018-12-22 15:25:27 +000034import android.content.pm.PackageInstaller;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000035import android.content.pm.PackageInstaller.SessionInfo;
36import android.content.pm.PackageManager;
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +000037import android.content.pm.PackageManagerInternal;
Nikita Ioffee0dbc982019-05-09 19:33:35 +010038import android.content.pm.PackageParser;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000039import android.content.pm.PackageParser.PackageParserException;
40import android.content.pm.PackageParser.SigningDetails;
41import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
Dario Frenibe98c3f2018-12-22 15:25:27 +000042import android.content.pm.ParceledListSlice;
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;
Gavin Corkery333136c2020-02-03 19:03:43 +000060import android.text.TextUtils;
Nikita Ioffee0dbc982019-05-09 19:33:35 +010061import android.util.IntArray;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000062import android.util.Slog;
Dario Frenibe98c3f2018-12-22 15:25:27 +000063import android.util.SparseArray;
Oli Lanc72b0bb2019-12-02 14:03:55 +000064import android.util.SparseIntArray;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000065import android.util.apk.ApkSignatureVerifier;
Dario Frenibe98c3f2018-12-22 15:25:27 +000066
67import com.android.internal.annotations.GuardedBy;
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +000068import com.android.internal.content.PackageHelper;
Narayan Kamath94c93ab2019-01-04 10:47:00 +000069import com.android.internal.os.BackgroundThread;
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +000070import com.android.server.LocalServices;
Winson5e0a1d52020-01-24 12:00:33 -080071import com.android.server.pm.parsing.pkg.AndroidPackage;
Winsone0756292020-01-31 12:21:54 -080072import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
Gavin Corkery333136c2020-02-03 19:03:43 +000073import com.android.server.rollback.WatchdogRollbackLogger;
Dario Frenibe98c3f2018-12-22 15:25:27 +000074
Dario Freni4b572c02019-01-29 09:40:31 +000075import java.io.File;
76import java.io.IOException;
Dario Frenibe98c3f2018-12-22 15:25:27 +000077import java.util.ArrayList;
Dario Freni015f9352019-01-14 21:56:17 +000078import java.util.Arrays;
Dario Frenibe98c3f2018-12-22 15:25:27 +000079import java.util.List;
Dario Freni4b572c02019-01-29 09:40:31 +000080import java.util.concurrent.LinkedBlockingQueue;
81import java.util.concurrent.TimeUnit;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +010082import java.util.function.Consumer;
Dario Frenif141aab2019-02-04 14:48:30 +000083import java.util.function.Predicate;
Dario Freni015f9352019-01-14 21:56:17 +000084import java.util.stream.Collectors;
Dario Frenibe98c3f2018-12-22 15:25:27 +000085
86/**
87 * This class handles staged install sessions, i.e. install sessions that require packages to
88 * be installed only after a reboot.
89 */
90public class StagingManager {
91
92 private static final String TAG = "StagingManager";
93
Dario Freni4b572c02019-01-29 09:40:31 +000094 private final PackageInstallerService mPi;
Dario Freni2e8dffc2019-02-06 14:55:16 +000095 private final ApexManager mApexManager;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000096 private final PowerManager mPowerManager;
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +000097 private final Context mContext;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +010098 private final PreRebootVerificationHandler mPreRebootVerificationHandler;
Dario Frenibe98c3f2018-12-22 15:25:27 +000099
Dario Frenibe98c3f2018-12-22 15:25:27 +0000100 @GuardedBy("mStagedSessions")
101 private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
102
Oli Lanc72b0bb2019-12-02 14:03:55 +0000103 @GuardedBy("mStagedSessions")
104 private final SparseIntArray mSessionRollbackIds = new SparseIntArray();
105
Gavin Corkery333136c2020-02-03 19:03:43 +0000106 @GuardedBy("mFailedPackageNames")
107 private final List<String> mFailedPackageNames = new ArrayList<>();
108 private String mNativeFailureReason;
109
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000110 StagingManager(PackageInstallerService pi, Context context) {
Dario Freni4b572c02019-01-29 09:40:31 +0000111 mPi = pi;
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +0000112 mContext = context;
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000113
114 mApexManager = ApexManager.getInstance();
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000115 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100116 mPreRebootVerificationHandler = new PreRebootVerificationHandler(
117 BackgroundThread.get().getLooper());
Dario Frenibe98c3f2018-12-22 15:25:27 +0000118 }
119
120 private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
121 synchronized (mStagedSessions) {
122 PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId);
Narayan Kamath94c93ab2019-01-04 10:47:00 +0000123 // storedSession might be null if a call to abortSession was made before the session
124 // is updated.
125 if (storedSession != null) {
126 mStagedSessions.put(sessionInfo.sessionId, sessionInfo);
Dario Frenibe98c3f2018-12-22 15:25:27 +0000127 }
Dario Frenibe98c3f2018-12-22 15:25:27 +0000128 }
129 }
130
Patrick Baumann6bc126b2020-03-06 10:34:17 -0800131 ParceledListSlice<PackageInstaller.SessionInfo> getSessions(int callingUid) {
Dario Frenibe98c3f2018-12-22 15:25:27 +0000132 final List<PackageInstaller.SessionInfo> result = new ArrayList<>();
133 synchronized (mStagedSessions) {
134 for (int i = 0; i < mStagedSessions.size(); i++) {
Patrick Baumann6bc126b2020-03-06 10:34:17 -0800135 final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
136 result.add(stagedSession.generateInfoForCaller(false /*icon*/, callingUid));
Dario Frenibe98c3f2018-12-22 15:25:27 +0000137 }
138 }
139 return new ParceledListSlice<>(result);
140 }
141
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100142 /**
143 * Validates the signature used to sign the container of the new apex package
144 *
145 * @param newApexPkg The new apex package that is being installed
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100146 * @throws PackageManagerException
147 */
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000148 private void validateApexSignature(PackageInfo newApexPkg)
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100149 throws PackageManagerException {
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100150 // Get signing details of the new package
151 final String apexPath = newApexPkg.applicationInfo.sourceDir;
152 final String packageName = newApexPkg.packageName;
Michael Groover33df7c42020-01-24 19:00:01 -0800153 int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
154 newApexPkg.applicationInfo.targetSdkVersion);
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100155
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000156 final SigningDetails newSigningDetails;
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000157 try {
Michael Groover33df7c42020-01-24 19:00:01 -0800158 newSigningDetails = ApkSignatureVerifier.verify(apexPath, minSignatureScheme);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000159 } catch (PackageParserException e) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100160 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
161 "Failed to parse APEX package " + apexPath, e);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000162 }
163
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100164 // Get signing details of the existing package
165 final PackageInfo existingApexPkg = mApexManager.getPackageInfo(packageName,
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100166 ApexManager.MATCH_ACTIVE_PACKAGE);
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100167 if (existingApexPkg == null) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100168 // This should never happen, because submitSessionToApexService ensures that no new
169 // apexes were installed.
170 throw new IllegalStateException("Unknown apex package " + packageName);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000171 }
172
173 final SigningDetails existingSigningDetails;
174 try {
175 existingSigningDetails = ApkSignatureVerifier.verify(
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100176 existingApexPkg.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000177 } catch (PackageParserException e) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100178 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100179 "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir, e);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000180 }
181
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100182 // Verify signing details for upgrade
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000183 if (newSigningDetails.checkCapability(existingSigningDetails,
184 SigningDetails.CertCapabilities.INSTALLED_DATA)
185 || existingSigningDetails.checkCapability(newSigningDetails,
186 SigningDetails.CertCapabilities.ROLLBACK)) {
Mohammad Samiul Islam8e35db12019-08-22 15:43:55 +0100187 return;
188 }
189
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100190 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100191 "APK-container signature of APEX package " + packageName + " with version "
192 + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not"
193 + " compatible with the one currently installed on device");
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000194 }
195
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100196 private List<PackageInfo> submitSessionToApexService(
197 @NonNull PackageInstallerSession session) throws PackageManagerException {
Oli Lanc72b0bb2019-12-02 14:03:55 +0000198 final IntArray childSessionIds = new IntArray();
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100199 if (session.isMultiPackage()) {
200 for (int id : session.getChildSessionIds()) {
201 if (isApexSession(mStagedSessions.get(id))) {
Oli Lanc72b0bb2019-12-02 14:03:55 +0000202 childSessionIds.add(id);
203 }
204 }
205 }
206 ApexSessionParams apexSessionParams = new ApexSessionParams();
207 apexSessionParams.sessionId = session.sessionId;
208 apexSessionParams.childSessionIds = childSessionIds.toArray();
209 if (session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
210 apexSessionParams.isRollback = true;
211 apexSessionParams.rollbackId = retrieveRollbackIdForCommitSession(session.sessionId);
212 } else {
213 synchronized (mStagedSessions) {
214 int rollbackId = mSessionRollbackIds.get(session.sessionId, -1);
215 if (rollbackId != -1) {
216 apexSessionParams.hasRollbackEnabled = true;
217 apexSessionParams.rollbackId = rollbackId;
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100218 }
Nikita Ioffe8bbb8152019-02-21 15:54:54 +0000219 }
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100220 }
Nikita Ioffe4e7d24a2019-07-05 15:49:45 +0100221 // submitStagedSession will throw a PackageManagerException if apexd verification fails,
222 // which will be propagated to populate stagedSessionErrorMessage of this session.
Oli Lanc72b0bb2019-12-02 14:03:55 +0000223 final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100224 final List<PackageInfo> result = new ArrayList<>();
Oli Lanc2c7a222019-07-31 15:27:22 +0100225 for (ApexInfo apexInfo : apexInfoList.apexInfos) {
226 final PackageInfo packageInfo;
227 int flags = PackageManager.GET_META_DATA;
228 PackageParser.Package pkg;
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100229 try {
Oli Lanc2c7a222019-07-31 15:27:22 +0100230 File apexFile = new File(apexInfo.modulePath);
231 PackageParser pp = new PackageParser();
232 pkg = pp.parsePackage(apexFile, flags, false);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100233 } catch (PackageParserException e) {
234 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Oli Lanc2c7a222019-07-31 15:27:22 +0100235 "Failed to parse APEX package " + apexInfo.modulePath, e);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100236 }
Oli Lanc2c7a222019-07-31 15:27:22 +0100237 packageInfo = PackageParser.generatePackageInfo(pkg, apexInfo, flags);
238 final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName,
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100239 ApexManager.MATCH_ACTIVE_PACKAGE);
240 if (activePackage == null) {
Oli Lanc2c7a222019-07-31 15:27:22 +0100241 Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100242 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
243 "It is forbidden to install new APEX packages.");
244 }
245 checkRequiredVersionCode(session, activePackage);
Oli Lanc2c7a222019-07-31 15:27:22 +0100246 checkDowngrade(session, activePackage, packageInfo);
247 result.add(packageInfo);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100248 }
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100249 Slog.d(TAG, "Session " + session.sessionId + " has following APEX packages: ["
250 + result.stream().map(p -> p.packageName).collect(Collectors.joining(",")) + "]");
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100251 return result;
252 }
253
Oli Lanc72b0bb2019-12-02 14:03:55 +0000254 private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException {
255 RollbackManager rm = mContext.getSystemService(RollbackManager.class);
256
257 List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks();
258 for (RollbackInfo rollback : rollbacks) {
259 if (rollback.getCommittedSessionId() == sessionId) {
260 return rollback.getRollbackId();
261 }
262 }
263 throw new PackageManagerException(
264 "Could not find rollback id for commit session: " + sessionId);
265 }
266
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100267 private void checkRequiredVersionCode(final PackageInstallerSession session,
268 final PackageInfo activePackage) throws PackageManagerException {
269 if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) {
270 return;
271 }
272 final long activeVersion = activePackage.applicationInfo.longVersionCode;
273 if (activeVersion != session.params.requiredInstalledVersionCode) {
Mohammad Samiul Islam44ad95b2019-11-20 15:14:36 +0000274 if (!mApexManager.abortStagedSession(session.sessionId)) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100275 Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
276 }
277 throw new PackageManagerException(
278 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
279 "Installed version of APEX package " + activePackage.packageName
Richard Uhler2124d4b2019-04-25 13:01:39 +0100280 + " does not match required. Active version: " + activeVersion
281 + " required: " + session.params.requiredInstalledVersionCode);
Nikita Ioffe8bbb8152019-02-21 15:54:54 +0000282 }
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100283 }
284
285 private void checkDowngrade(final PackageInstallerSession session,
286 final PackageInfo activePackage, final PackageInfo newPackage)
287 throws PackageManagerException {
288 final long activeVersion = activePackage.applicationInfo.longVersionCode;
289 final long newVersionCode = newPackage.applicationInfo.longVersionCode;
Winsonf00c7552020-01-28 12:52:01 -0800290 boolean isAppDebuggable = (activePackage.applicationInfo.flags
291 & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100292 final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
Winsonf00c7552020-01-28 12:52:01 -0800293 session.params.installFlags, isAppDebuggable);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100294 if (activeVersion > newVersionCode && !allowsDowngrade) {
Mohammad Samiul Islam44ad95b2019-11-20 15:14:36 +0000295 if (!mApexManager.abortStagedSession(session.sessionId)) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100296 Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
297 }
298 throw new PackageManagerException(
299 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
300 "Downgrade of APEX package " + newPackage.packageName
301 + " is not allowed. Active version: " + activeVersion
302 + " attempted: " + newVersionCode);
303 }
Dario Freni015f9352019-01-14 21:56:17 +0000304 }
305
Dario Freni015f9352019-01-14 21:56:17 +0000306 private static boolean isApexSession(@NonNull PackageInstallerSession session) {
307 return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
308 }
309
Dario Frenif141aab2019-02-04 14:48:30 +0000310 private boolean sessionContains(@NonNull PackageInstallerSession session,
311 Predicate<PackageInstallerSession> filter) {
Dario Freni4b572c02019-01-29 09:40:31 +0000312 if (!session.isMultiPackage()) {
Dario Frenif141aab2019-02-04 14:48:30 +0000313 return filter.test(session);
Dario Freni4b572c02019-01-29 09:40:31 +0000314 }
315 synchronized (mStagedSessions) {
316 return !(Arrays.stream(session.getChildSessionIds())
317 // Retrieve cached sessions matching ids.
318 .mapToObj(i -> mStagedSessions.get(i))
319 // Filter only the ones containing APEX.
Dario Frenif141aab2019-02-04 14:48:30 +0000320 .filter(childSession -> filter.test(childSession))
Dario Freni4b572c02019-01-29 09:40:31 +0000321 .collect(Collectors.toList())
322 .isEmpty());
323 }
324 }
325
Dario Frenif141aab2019-02-04 14:48:30 +0000326 private boolean sessionContainsApex(@NonNull PackageInstallerSession session) {
327 return sessionContains(session, (s) -> isApexSession(s));
328 }
329
330 private boolean sessionContainsApk(@NonNull PackageInstallerSession session) {
331 return sessionContains(session, (s) -> !isApexSession(s));
332 }
333
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000334 // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
Mohammad Samiul Islam166243f2020-03-06 16:19:22 +0000335 private void abortCheckpoint(String errorMsg) {
336 Slog.e(TAG, "Aborting checkpoint: " + errorMsg);
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000337 try {
338 if (supportsCheckpoint() && needsCheckpoint()) {
339 mApexManager.revertActiveSessions();
340 PackageHelper.getStorageManager().abortChanges(
341 "StagingManager initiated", false /*retry*/);
342 }
343 } catch (Exception e) {
344 Slog.wtf(TAG, "Failed to abort checkpoint", e);
345 mApexManager.revertActiveSessions();
346 mPowerManager.reboot(null);
347 }
348 }
349
350 private boolean supportsCheckpoint() throws RemoteException {
351 return PackageHelper.getStorageManager().supportsCheckpoint();
352 }
353
354 private boolean needsCheckpoint() throws RemoteException {
355 return PackageHelper.getStorageManager().needsCheckpoint();
356 }
357
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000358 /**
Oli Lan7bbd8b42020-01-14 10:11:42 +0000359 * Perform snapshot and restore as required both for APEXes themselves and for apks in APEX.
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000360 * Apks inside apex are not installed using apk-install flow. They are scanned from the system
361 * directory directly by PackageManager, as such, RollbackManager need to handle their data
362 * separately here.
363 */
Oli Lan7bbd8b42020-01-14 10:11:42 +0000364 private void snapshotAndRestoreForApexSession(PackageInstallerSession session) {
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000365 if (!sessionContainsApex(session)) {
366 return;
367 }
368
369 boolean doSnapshotOrRestore =
370 (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
371 || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
372 if (!doSnapshotOrRestore) {
373 return;
374 }
375
376 // Find all the apex sessions that needs processing
377 List<PackageInstallerSession> apexSessions = new ArrayList<>();
378 if (session.isMultiPackage()) {
379 List<PackageInstallerSession> childrenSessions = new ArrayList<>();
380 synchronized (mStagedSessions) {
381 for (int childSessionId : session.getChildSessionIds()) {
382 PackageInstallerSession childSession = mStagedSessions.get(childSessionId);
383 if (childSession != null) {
384 childrenSessions.add(childSession);
385 }
386 }
387 }
388 for (PackageInstallerSession childSession : childrenSessions) {
389 if (sessionContainsApex(childSession)) {
390 apexSessions.add(childSession);
391 }
392 }
393 } else {
394 apexSessions.add(session);
395 }
396
Oli Lan7bbd8b42020-01-14 10:11:42 +0000397 final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
398 final int[] allUsers = um.getUserIds();
399 IRollbackManager rm = IRollbackManager.Stub.asInterface(
400 ServiceManager.getService(Context.ROLLBACK_SERVICE));
401
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000402 for (PackageInstallerSession apexSession : apexSessions) {
Oli Lan7bbd8b42020-01-14 10:11:42 +0000403 String packageName = apexSession.getPackageName();
404 // Perform any snapshots or restores for the APEX itself
405 snapshotAndRestoreApexUserData(packageName, allUsers, rm);
406
407 // Process the apks inside the APEX
408 List<String> apksInApex = mApexManager.getApksInApex(packageName);
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000409 for (String apk: apksInApex) {
Oli Lan7bbd8b42020-01-14 10:11:42 +0000410 snapshotAndRestoreApkInApexUserData(apk, allUsers, rm);
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000411 }
412 }
413 }
414
Oli Lan7bbd8b42020-01-14 10:11:42 +0000415 private void snapshotAndRestoreApexUserData(
416 String packageName, int[] allUsers, IRollbackManager rm) {
417 try {
418 // appId, ceDataInode, and seInfo are not needed for APEXes
419 rm.snapshotAndRestoreUserData(packageName, allUsers, 0, 0,
420 null, 0 /*token*/);
421 } catch (RemoteException re) {
422 Slog.e(TAG, "Error snapshotting/restoring user data: " + re);
423 }
424 }
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000425
Oli Lan7bbd8b42020-01-14 10:11:42 +0000426 private void snapshotAndRestoreApkInApexUserData(
427 String packageName, int[] allUsers, IRollbackManager rm) {
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000428 PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class);
429 AndroidPackage pkg = mPmi.getPackage(packageName);
430 if (pkg == null) {
431 Slog.e(TAG, "Could not find package: " + packageName
432 + "for snapshotting/restoring user data.");
433 return;
434 }
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000435
436 int appId = -1;
437 long ceDataInode = -1;
Winsone0756292020-01-31 12:21:54 -0800438 final PackageSetting ps = mPmi.getPackageSetting(packageName);
Oli Lan7bbd8b42020-01-14 10:11:42 +0000439 if (ps != null) {
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000440 appId = ps.appId;
441 ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM);
442 // NOTE: We ignore the user specified in the InstallParam because we know this is
443 // an update, and hence need to restore data for all installed users.
444 final int[] installedUsers = ps.queryInstalledUsers(allUsers, true);
445
Winsone0756292020-01-31 12:21:54 -0800446 final String seInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000447 try {
448 rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode,
449 seInfo, 0 /*token*/);
450 } catch (RemoteException re) {
451 Slog.e(TAG, "Error snapshotting/restoring user data: " + re);
452 }
453 }
454 }
455
Gavin Corkery333136c2020-02-03 19:03:43 +0000456 /**
457 * Prepares for the logging of apexd reverts by storing the native failure reason if necessary,
458 * and adding the package name of the session which apexd reverted to the list of reverted
459 * session package names.
460 * Logging needs to wait until the ACTION_BOOT_COMPLETED broadcast is sent.
461 */
462 private void prepareForLoggingApexdRevert(@NonNull PackageInstallerSession session,
463 @NonNull String nativeFailureReason) {
464 synchronized (mFailedPackageNames) {
465 mNativeFailureReason = nativeFailureReason;
466 if (session.getPackageName() != null) {
467 mFailedPackageNames.add(session.getPackageName());
468 }
469 }
470 }
471
Dario Freni61b3e252019-01-11 23:05:39 +0000472 private void resumeSession(@NonNull PackageInstallerSession session) {
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100473 Slog.d(TAG, "Resuming session " + session.sessionId);
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000474
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100475 final boolean hasApex = sessionContainsApex(session);
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000476 ApexSessionInfo apexSessionInfo = null;
Nikita Ioffea820bd92019-02-15 14:22:44 +0000477 if (hasApex) {
Dario Freni2e8dffc2019-02-06 14:55:16 +0000478 // Check with apexservice whether the apex packages have been activated.
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000479 apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
480
Gavin Corkery333136c2020-02-03 19:03:43 +0000481 // Prepare for logging a native crash during boot, if one occurred.
482 if (apexSessionInfo != null && !TextUtils.isEmpty(
483 apexSessionInfo.crashingNativeProcess)) {
484 prepareForLoggingApexdRevert(session, apexSessionInfo.crashingNativeProcess);
485 }
486
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000487 if (apexSessionInfo != null && apexSessionInfo.isVerified) {
488 // Session has been previously submitted to apexd, but didn't complete all the
489 // pre-reboot verification, perhaps because the device rebooted in the meantime.
490 // Greedily re-trigger the pre-reboot verification. We want to avoid marking it as
491 // failed when not in checkpoint mode, hence it is being processed separately.
492 Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to "
493 + "be verified, resuming pre-reboot verification");
494 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
495 return;
496 }
497 }
498
499 // Before we resume session, we check if revert is needed or not. Typically, we enter file-
500 // system checkpoint mode when we reboot first time in order to install staged sessions. We
501 // want to install staged sessions in this mode as rebooting now will revert user data. If
502 // something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will
503 // have no effect on user data, so mark the sessions as failed instead.
504 try {
505 // If checkpoint is supported, then we only resume sessions if we are in checkpointing
506 // mode. If not, we fail all sessions.
507 if (supportsCheckpoint() && !needsCheckpoint()) {
508 // TODO(b/146343545): Persist failure reason across checkpoint reboot
Mohammad Samiul Islam166243f2020-03-06 16:19:22 +0000509 Slog.d(TAG, "Reverting back to safe state. Marking " + session.sessionId
510 + " as failed.");
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000511 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
512 "Reverting back to safe state");
513 return;
514 }
515 } catch (RemoteException e) {
516 // Cannot continue staged install without knowing if fs-checkpoint is supported
517 Slog.e(TAG, "Checkpoint support unknown. Aborting staged install for session "
518 + session.sessionId, e);
519 // TODO: Mark all staged sessions together and reboot only once
520 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
521 "Checkpoint support unknown. Aborting staged install.");
522 if (hasApex) {
523 mApexManager.revertActiveSessions();
524 }
525 mPowerManager.reboot("Checkpoint support unknown");
526 return;
527 }
528
529 if (hasApex) {
Dario Freni2e8dffc2019-02-06 14:55:16 +0000530 if (apexSessionInfo == null) {
Mohammad Samiul Islam166243f2020-03-06 16:19:22 +0000531 String errorMsg = "apexd did not know anything about a staged session supposed to"
532 + " be activated";
Dario Freni2e8dffc2019-02-06 14:55:16 +0000533 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
Mohammad Samiul Islam166243f2020-03-06 16:19:22 +0000534 errorMsg);
535 abortCheckpoint(errorMsg);
Dario Freni4b572c02019-01-29 09:40:31 +0000536 return;
537 }
Nikita Ioffe82742222019-02-26 12:14:04 +0000538 if (isApexSessionFailed(apexSessionInfo)) {
Mohammad Samiul Islam166243f2020-03-06 16:19:22 +0000539 String errorMsg = "APEX activation failed. Check logcat messages from apexd for "
540 + "more information.";
Dario Frenib6d28962019-01-31 15:52:24 +0000541 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
Mohammad Samiul Islam166243f2020-03-06 16:19:22 +0000542 errorMsg);
543 abortCheckpoint(errorMsg);
Dario Freni4b572c02019-01-29 09:40:31 +0000544 return;
545 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000546 if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000547 // Apexd did not apply the session for some unknown reason. There is no guarantee
548 // that apexd will install it next time. Safer to proactively mark as failed.
Mohammad Samiul Islam166243f2020-03-06 16:19:22 +0000549 String errorMsg = "Staged session " + session.sessionId + "at boot didn't "
550 + "activate nor fail. Marking it as failed anyway.";
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000551 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
Mohammad Samiul Islam166243f2020-03-06 16:19:22 +0000552 errorMsg);
553 abortCheckpoint(errorMsg);
Dario Freni4b572c02019-01-29 09:40:31 +0000554 return;
555 }
Oli Lan7bbd8b42020-01-14 10:11:42 +0000556 snapshotAndRestoreForApexSession(session);
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100557 Slog.i(TAG, "APEX packages in session " + session.sessionId
558 + " were successfully activated. Proceeding with APK packages, if any");
Dario Freni4b572c02019-01-29 09:40:31 +0000559 }
560 // The APEX part of the session is activated, proceed with the installation of APKs.
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100561 try {
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100562 Slog.d(TAG, "Installing APK packages in session " + session.sessionId);
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100563 installApksInSession(session);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100564 } catch (PackageManagerException e) {
565 session.setStagedSessionFailed(e.error, e.getMessage());
Mohammad Samiul Islam166243f2020-03-06 16:19:22 +0000566 abortCheckpoint(e.getMessage());
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000567
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000568 // If checkpoint is not supported, we have to handle failure for one staged session.
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000569 if (!hasApex) {
570 return;
571 }
572
Mohammad Samiul Islam1a96eb62019-11-21 10:38:06 +0000573 if (!mApexManager.revertActiveSessions()) {
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000574 Slog.e(TAG, "Failed to abort APEXd session");
575 } else {
576 Slog.e(TAG,
577 "Successfully aborted apexd session. Rebooting device in order to revert "
578 + "to the previous state of APEXd.");
579 mPowerManager.reboot(null);
580 }
Dario Freni61b3e252019-01-11 23:05:39 +0000581 return;
582 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000583
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100584 Slog.d(TAG, "Marking session " + session.sessionId + " as applied");
Dario Freni4b572c02019-01-29 09:40:31 +0000585 session.setStagedSessionApplied();
Nikita Ioffea820bd92019-02-15 14:22:44 +0000586 if (hasApex) {
587 mApexManager.markStagedSessionSuccessful(session.sessionId);
588 }
Dario Freni4b572c02019-01-29 09:40:31 +0000589 }
590
Dario Freni815bd212019-02-20 14:09:36 +0000591 private List<String> findAPKsInDir(File stageDir) {
592 List<String> ret = new ArrayList<>();
Dario Freni4b572c02019-01-29 09:40:31 +0000593 if (stageDir != null && stageDir.exists()) {
594 for (File file : stageDir.listFiles()) {
595 if (file.getAbsolutePath().toLowerCase().endsWith(".apk")) {
Dario Freni815bd212019-02-20 14:09:36 +0000596 ret.add(file.getAbsolutePath());
Dario Freni4b572c02019-01-29 09:40:31 +0000597 }
598 }
Dario Freni61b3e252019-01-11 23:05:39 +0000599 }
Dario Freni815bd212019-02-20 14:09:36 +0000600 return ret;
Dario Freni4b572c02019-01-29 09:40:31 +0000601 }
602
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100603 @NonNull
Dario Freni4b572c02019-01-29 09:40:31 +0000604 private PackageInstallerSession createAndWriteApkSession(
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100605 @NonNull PackageInstallerSession originalSession, boolean preReboot)
606 throws PackageManagerException {
607 final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
608 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
Dario Freni4b572c02019-01-29 09:40:31 +0000609 if (originalSession.stageDir == null) {
610 Slog.wtf(TAG, "Attempting to install a staged APK session with no staging dir");
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100611 throw new PackageManagerException(errorCode,
612 "Attempting to install a staged APK session with no staging dir");
Dario Freni1473bcb2019-01-25 14:27:13 +0000613 }
Dario Freni815bd212019-02-20 14:09:36 +0000614 List<String> apkFilePaths = findAPKsInDir(originalSession.stageDir);
615 if (apkFilePaths.isEmpty()) {
Dario Freni4b572c02019-01-29 09:40:31 +0000616 Slog.w(TAG, "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100617 throw new PackageManagerException(errorCode,
618 "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
Dario Freni61b3e252019-01-11 23:05:39 +0000619 }
Dario Freni4b572c02019-01-29 09:40:31 +0000620
621 PackageInstaller.SessionParams params = originalSession.params.copy();
622 params.isStaged = false;
Dario Frenia2afebf2019-05-01 16:03:17 +0100623 params.installFlags |= PackageManager.INSTALL_STAGED;
Dario Frenic3e68ea2019-04-02 11:45:13 +0100624 // TODO(b/129744602): use the userid from the original session.
Dario Frenif141aab2019-02-04 14:48:30 +0000625 if (preReboot) {
626 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
627 params.installFlags |= PackageManager.INSTALL_DRY_RUN;
628 } else {
629 params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
630 }
Dario Freni4b572c02019-01-29 09:40:31 +0000631 try {
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +0000632 int apkSessionId = mPi.createSession(
633 params, originalSession.getInstallerPackageName(),
634 0 /* UserHandle.SYSTEM */);
635 PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
Dario Freni4b572c02019-01-29 09:40:31 +0000636 apkSession.open();
Dario Freni815bd212019-02-20 14:09:36 +0000637 for (String apkFilePath : apkFilePaths) {
638 File apkFile = new File(apkFilePath);
639 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
640 ParcelFileDescriptor.MODE_READ_ONLY);
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +0000641 long sizeBytes = (pfd == null) ? -1 : pfd.getStatSize();
Dario Freni815bd212019-02-20 14:09:36 +0000642 if (sizeBytes < 0) {
643 Slog.e(TAG, "Unable to get size of: " + apkFilePath);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100644 throw new PackageManagerException(errorCode,
645 "Unable to get size of: " + apkFilePath);
Dario Freni815bd212019-02-20 14:09:36 +0000646 }
647 apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
Dario Freni4b572c02019-01-29 09:40:31 +0000648 }
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +0000649 return apkSession;
650 } catch (IOException | ParcelableException e) {
Dario Freni4b572c02019-01-29 09:40:31 +0000651 Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +0000652 throw new PackageManagerException(errorCode, "Failed to create/write APK session", e);
Dario Freni4b572c02019-01-29 09:40:31 +0000653 }
Dario Freni4b572c02019-01-29 09:40:31 +0000654 }
655
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100656 /**
657 * Extract apks in the given session into a new session. Returns {@code null} if there is no
658 * apks in the given session. Only parent session is returned for multi-package session.
659 */
660 @Nullable
661 private PackageInstallerSession extractApksInSession(PackageInstallerSession session,
662 boolean preReboot) throws PackageManagerException {
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100663 final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
664 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
Dario Freni4b572c02019-01-29 09:40:31 +0000665 if (!session.isMultiPackage() && !isApexSession(session)) {
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100666 return createAndWriteApkSession(session, preReboot);
Dario Freni4b572c02019-01-29 09:40:31 +0000667 } else if (session.isMultiPackage()) {
668 // For multi-package staged sessions containing APKs, we identify which child sessions
669 // contain an APK, and with those then create a new multi-package group of sessions,
670 // carrying over all the session parameters and unmarking them as staged. On commit the
671 // sessions will be installed atomically.
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100672 final List<PackageInstallerSession> childSessions;
Dario Freni4b572c02019-01-29 09:40:31 +0000673 synchronized (mStagedSessions) {
674 childSessions =
675 Arrays.stream(session.getChildSessionIds())
676 // Retrieve cached sessions matching ids.
677 .mapToObj(i -> mStagedSessions.get(i))
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000678 // Filter only the ones containing APKs.
Dario Freni4b572c02019-01-29 09:40:31 +0000679 .filter(childSession -> !isApexSession(childSession))
680 .collect(Collectors.toList());
681 }
682 if (childSessions.isEmpty()) {
683 // APEX-only multi-package staged session, nothing to do.
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100684 return null;
Dario Freni4b572c02019-01-29 09:40:31 +0000685 }
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100686 final PackageInstaller.SessionParams params = session.params.copy();
Dario Freni4b572c02019-01-29 09:40:31 +0000687 params.isStaged = false;
Dario Frenif141aab2019-02-04 14:48:30 +0000688 if (preReboot) {
689 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
690 }
Dario Frenic3e68ea2019-04-02 11:45:13 +0100691 // TODO(b/129744602): use the userid from the original session.
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100692 final int apkParentSessionId = mPi.createSession(
Dario Frenic3e68ea2019-04-02 11:45:13 +0100693 params, session.getInstallerPackageName(),
694 0 /* UserHandle.SYSTEM */);
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100695 final PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
Dario Freni4b572c02019-01-29 09:40:31 +0000696 try {
697 apkParentSession.open();
698 } catch (IOException e) {
699 Slog.e(TAG, "Unable to prepare multi-package session for staged session "
700 + session.sessionId);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100701 throw new PackageManagerException(errorCode,
702 "Unable to prepare multi-package session for staged session");
Dario Freni4b572c02019-01-29 09:40:31 +0000703 }
704
705 for (PackageInstallerSession sessionToClone : childSessions) {
Dario Frenif141aab2019-02-04 14:48:30 +0000706 PackageInstallerSession apkChildSession =
707 createAndWriteApkSession(sessionToClone, preReboot);
Dario Freni09660472019-02-18 16:44:23 +0000708 try {
709 apkParentSession.addChildSessionId(apkChildSession.sessionId);
Patrick Baumann00321b72019-04-09 15:07:25 -0700710 } catch (IllegalStateException e) {
Dario Freni09660472019-02-18 16:44:23 +0000711 Slog.e(TAG, "Failed to add a child session for installing the APK files", e);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100712 throw new PackageManagerException(errorCode,
713 "Failed to add a child session " + apkChildSession.sessionId);
Dario Freni09660472019-02-18 16:44:23 +0000714 }
Dario Freni4b572c02019-01-29 09:40:31 +0000715 }
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100716 return apkParentSession;
Dario Freni4b572c02019-01-29 09:40:31 +0000717 }
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100718 return null;
719 }
720
721 private void verifyApksInSession(PackageInstallerSession session)
722 throws PackageManagerException {
723
724 final PackageInstallerSession apksToVerify = extractApksInSession(
725 session, /* preReboot */ true);
726 if (apksToVerify == null) {
727 return;
728 }
729
730 final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync(
731 (Intent result) -> {
732 int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
733 PackageInstaller.STATUS_FAILURE);
734 if (status != PackageInstaller.STATUS_SUCCESS) {
735 final String errorMessage = result.getStringExtra(
736 PackageInstaller.EXTRA_STATUS_MESSAGE);
737 Slog.e(TAG, "Failure to verify APK staged session "
738 + session.sessionId + " [" + errorMessage + "]");
739 session.setStagedSessionFailed(
740 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage);
741 return;
742 }
743 mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
744 session.sessionId);
745 });
746
747 apksToVerify.commit(receiver.getIntentSender(), false);
748 }
749
750 private void installApksInSession(@NonNull PackageInstallerSession session)
751 throws PackageManagerException {
752
753 final PackageInstallerSession apksToInstall = extractApksInSession(
754 session, /* preReboot */ false);
755 if (apksToInstall == null) {
756 return;
757 }
758
759 if ((apksToInstall.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
760 // If rollback is available for this session, notify the rollback
761 // manager of the apk session so it can properly enable rollback.
762 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
763 ServiceManager.getService(Context.ROLLBACK_SERVICE));
764 try {
765 rm.notifyStagedApkSession(session.sessionId, apksToInstall.sessionId);
766 } catch (RemoteException re) {
767 Slog.e(TAG, "Failed to notifyStagedApkSession for session: "
768 + session.sessionId, re);
769 }
770 }
771
772 final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
773 apksToInstall.commit(receiver.getIntentSender(), false);
774 final Intent result = receiver.getResult();
775 final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
776 PackageInstaller.STATUS_FAILURE);
777 if (status != PackageInstaller.STATUS_SUCCESS) {
778 final String errorMessage = result.getStringExtra(
779 PackageInstaller.EXTRA_STATUS_MESSAGE);
780 Slog.e(TAG, "Failure to install APK staged session "
781 + session.sessionId + " [" + errorMessage + "]");
782 throw new PackageManagerException(
783 SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage);
784 }
Dario Freni61b3e252019-01-11 23:05:39 +0000785 }
786
Nikita Ioffe8e5b703c2019-03-14 18:40:17 +0000787 void commitSession(@NonNull PackageInstallerSession session) {
Dario Freni276cd072019-01-09 11:13:44 +0000788 updateStoredSession(session);
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100789 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
Dario Frenibe98c3f2018-12-22 15:25:27 +0000790 }
791
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +0000792 private int parentOrOwnSessionId(PackageInstallerSession session) {
793 return session.hasParentSessionId() ? session.getParentSessionId() : session.sessionId;
794 }
795
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100796 /**
797 * <p> Check if the session provided is non-overlapping with the active staged sessions.
798 *
799 * <p> A session is non-overlapping if it meets one of the following conditions: </p>
800 * <ul>
801 * <li>It is a parent session</li>
802 * <li>It is already one of the active sessions</li>
803 * <li>Its package name is not same as any of the active sessions</li>
804 * </ul>
805 * @throws PackageManagerException if session fails the check
806 */
807 void checkNonOverlappingWithStagedSessions(@NonNull PackageInstallerSession session)
808 throws PackageManagerException {
809 if (session.isMultiPackage()) {
810 // We cannot say a parent session overlaps until we process its children
811 return;
812 }
813 if (session.getPackageName() == null) {
814 throw new PackageManagerException(PackageManager.INSTALL_FAILED_INVALID_APK,
815 "Cannot stage session " + session.sessionId + " with package name null");
816 }
817
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +0000818 boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService(
819 Context.STORAGE_SERVICE)).isCheckpointSupported();
820
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000821 synchronized (mStagedSessions) {
822 for (int i = 0; i < mStagedSessions.size(); i++) {
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100823 final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
824 if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()) {
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000825 continue;
826 }
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100827 if (stagedSession.isMultiPackage()) {
828 // This active parent staged session is useless as it doesn't have a package
829 // name and the session we are checking is not a parent session either.
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000830 continue;
831 }
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100832
833 // From here on, stagedSession is a non-parent active staged session
834
835 // Check if stagedSession has an active parent session or not
836 if (stagedSession.hasParentSessionId()) {
837 int parentId = stagedSession.getParentSessionId();
838 PackageInstallerSession parentSession = mStagedSessions.get(parentId);
839 if (parentSession == null || parentSession.isStagedAndInTerminalState()) {
840 // Parent session has been abandoned or terminated already
841 continue;
842 }
843 }
844
845 // Check if session is one of the active sessions
846 if (session.sessionId == stagedSession.sessionId) {
847 Slog.w(TAG, "Session " + session.sessionId + " is already staged");
848 continue;
849 }
850
851 // If session is not among the active sessions, then it cannot have same package
852 // name as any of the active sessions.
853 if (session.getPackageName().equals(stagedSession.getPackageName())) {
854 throw new PackageManagerException(
855 PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
856 "Package: " + session.getPackageName() + " in session: "
857 + session.sessionId + " has been staged already by session: "
858 + stagedSession.sessionId, null);
859 }
860
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +0000861 // Staging multiple root sessions is not allowed if device doesn't support
862 // checkpoint. If session and stagedSession do not have common ancestor, they are
863 // from two different root sessions.
864 if (!supportsCheckpoint
865 && parentOrOwnSessionId(session) != parentOrOwnSessionId(stagedSession)) {
866 throw new PackageManagerException(
867 PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
868 "Cannot stage multiple sessions without checkpoint support", null);
869 }
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000870 }
871 }
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000872 }
873
Dario Frenibe98c3f2018-12-22 15:25:27 +0000874 void createSession(@NonNull PackageInstallerSession sessionInfo) {
875 synchronized (mStagedSessions) {
876 mStagedSessions.append(sessionInfo.sessionId, sessionInfo);
877 }
878 }
879
Dario Freni015f9352019-01-14 21:56:17 +0000880 void abortSession(@NonNull PackageInstallerSession session) {
Dario Frenibe98c3f2018-12-22 15:25:27 +0000881 synchronized (mStagedSessions) {
Dario Freni015f9352019-01-14 21:56:17 +0000882 mStagedSessions.remove(session.sessionId);
Oli Lanc72b0bb2019-12-02 14:03:55 +0000883 mSessionRollbackIds.delete(session.sessionId);
Dario Frenibe98c3f2018-12-22 15:25:27 +0000884 }
885 }
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000886
shafik07205e32019-02-07 20:12:33 +0000887 void abortCommittedSession(@NonNull PackageInstallerSession session) {
Nikita Ioffe1e523b52019-03-13 16:07:48 +0000888 if (session.isStagedSessionApplied()) {
889 Slog.w(TAG, "Cannot abort applied session : " + session.sessionId);
shafik07205e32019-02-07 20:12:33 +0000890 return;
891 }
shafik07205e32019-02-07 20:12:33 +0000892 abortSession(session);
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000893
894 boolean hasApex = sessionContainsApex(session);
895 if (hasApex) {
896 ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId);
897 if (apexSession == null || isApexSessionFinalized(apexSession)) {
898 Slog.w(TAG,
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100899 "Cannot abort session " + session.sessionId
900 + " because it is not active or APEXD is not reachable");
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000901 return;
902 }
Mohammad Samiul Islam44ad95b2019-11-20 15:14:36 +0000903 try {
904 mApexManager.abortStagedSession(session.sessionId);
905 } catch (Exception ignore) {
906 }
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000907 }
shafik07205e32019-02-07 20:12:33 +0000908 }
909
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000910 private boolean isApexSessionFinalized(ApexSessionInfo session) {
shafik07205e32019-02-07 20:12:33 +0000911 /* checking if the session is in a final state, i.e., not active anymore */
Nikita Ioffe82742222019-02-26 12:14:04 +0000912 return session.isUnknown || session.isActivationFailed || session.isSuccess
Mohammad Samiul Islam2dfc86a2019-11-20 13:53:07 +0000913 || session.isReverted;
Nikita Ioffe82742222019-02-26 12:14:04 +0000914 }
915
916 private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) {
Mohammad Samiul Islam2dfc86a2019-11-20 13:53:07 +0000917 // isRevertInProgress is included to cover the scenario, when a device is rebooted
918 // during the revert, and apexd fails to resume the revert after reboot.
Nikita Ioffe82742222019-02-26 12:14:04 +0000919 return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown
Mohammad Samiul Islam2dfc86a2019-11-20 13:53:07 +0000920 || apexSessionInfo.isReverted || apexSessionInfo.isRevertInProgress
921 || apexSessionInfo.isRevertFailed;
shafik07205e32019-02-07 20:12:33 +0000922 }
923
Dario Freni015f9352019-01-14 21:56:17 +0000924 @GuardedBy("mStagedSessions")
925 private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) {
926 // This method assumes that the argument is either a parent session of a multi-package
927 // i.e. isMultiPackage() returns true, or that it is a child session, i.e.
928 // hasParentSessionId() returns true.
929 if (session.isMultiPackage()) {
930 // Parent session of a multi-package group. Check that we restored all the children.
931 for (int childSession : session.getChildSessionIds()) {
932 if (mStagedSessions.get(childSession) == null) {
933 return false;
934 }
935 }
936 return true;
937 }
938 if (session.hasParentSessionId()) {
939 PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId());
940 if (parent == null) {
941 return false;
942 }
943 return isMultiPackageSessionComplete(parent);
944 }
945 Slog.wtf(TAG, "Attempting to restore an invalid multi-package session.");
946 return false;
947 }
948
Rhed Jao1fc8b362020-01-16 18:38:17 +0800949 void restoreSession(@NonNull PackageInstallerSession session, boolean isDeviceUpgrading) {
Dario Freni015f9352019-01-14 21:56:17 +0000950 PackageInstallerSession sessionToResume = session;
951 synchronized (mStagedSessions) {
952 mStagedSessions.append(session.sessionId, session);
953 // For multi-package sessions, we don't know in which order they will be restored. We
954 // need to wait until we have restored all the session in a group before restoring them.
955 if (session.isMultiPackage() || session.hasParentSessionId()) {
956 if (!isMultiPackageSessionComplete(session)) {
957 // Still haven't recovered all sessions of the group, return.
958 return;
959 }
960 // Group recovered, find the parent if necessary and resume the installation.
961 if (session.hasParentSessionId()) {
962 sessionToResume = mStagedSessions.get(session.getParentSessionId());
963 }
964 }
965 }
Rhed Jao1fc8b362020-01-16 18:38:17 +0800966 // The preconditions used during pre-reboot verification might have changed when device
967 // is upgrading. Updated staged sessions to activation failed before we resume the session.
968 if (isDeviceUpgrading && !sessionToResume.isStagedAndInTerminalState()) {
969 sessionToResume.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
970 "Build fingerprint has changed");
971 return;
972 }
Dario Freni015f9352019-01-14 21:56:17 +0000973 checkStateAndResume(sessionToResume);
974 }
975
976 private void checkStateAndResume(@NonNull PackageInstallerSession session) {
Dario Freni47799f42019-03-13 18:06:24 +0000977 if (!session.isCommitted()) {
978 // Session hasn't been committed yet, ignore.
979 return;
980 }
Dario Freni61b3e252019-01-11 23:05:39 +0000981 // Check the state of the session and decide what to do next.
982 if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
983 // Final states, nothing to do.
984 return;
985 }
986 if (!session.isStagedSessionReady()) {
987 // The framework got restarted before the pre-reboot verification could complete,
988 // restart the verification.
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100989 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
Dario Freni61b3e252019-01-11 23:05:39 +0000990 } else {
991 // Session had already being marked ready. Start the checks to verify if there is any
992 // follow-up work.
Dario Freni4df010e2019-01-31 19:34:46 +0000993 resumeSession(session);
Dario Freni61b3e252019-01-11 23:05:39 +0000994 }
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000995 }
Dario Freni4b572c02019-01-29 09:40:31 +0000996
Gavin Corkery333136c2020-02-03 19:03:43 +0000997 private void logFailedApexSessionsIfNecessary() {
998 synchronized (mFailedPackageNames) {
999 if (!mFailedPackageNames.isEmpty()) {
1000 WatchdogRollbackLogger.logApexdRevert(mContext,
1001 mFailedPackageNames, mNativeFailureReason);
1002 }
1003 }
1004 }
1005
Rhed Jaob793c212019-10-30 20:54:37 +08001006 void systemReady() {
1007 // Register the receiver of boot completed intent for staging manager.
1008 mContext.registerReceiver(new BroadcastReceiver() {
1009 @Override
1010 public void onReceive(Context ctx, Intent intent) {
1011 mPreRebootVerificationHandler.readyToStart();
Gavin Corkery333136c2020-02-03 19:03:43 +00001012 BackgroundThread.getExecutor().execute(
1013 () -> logFailedApexSessionsIfNecessary());
Rhed Jaob793c212019-10-30 20:54:37 +08001014 ctx.unregisterReceiver(this);
1015 }
1016 }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
1017 }
1018
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001019 private static class LocalIntentReceiverAsync {
1020 final Consumer<Intent> mConsumer;
1021
1022 LocalIntentReceiverAsync(Consumer<Intent> consumer) {
1023 mConsumer = consumer;
1024 }
1025
1026 private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
1027 @Override
1028 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
1029 IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
1030 mConsumer.accept(intent);
1031 }
1032 };
1033
1034 public IntentSender getIntentSender() {
1035 return new IntentSender((IIntentSender) mLocalSender);
1036 }
1037 }
1038
1039 private static class LocalIntentReceiverSync {
Dario Freni4b572c02019-01-29 09:40:31 +00001040 private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
1041
1042 private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
1043 @Override
1044 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001045 IIntentReceiver finishedReceiver, String requiredPermission,
1046 Bundle options) {
Dario Freni4b572c02019-01-29 09:40:31 +00001047 try {
1048 mResult.offer(intent, 5, TimeUnit.SECONDS);
1049 } catch (InterruptedException e) {
1050 throw new RuntimeException(e);
1051 }
1052 }
1053 };
1054
1055 public IntentSender getIntentSender() {
1056 return new IntentSender((IIntentSender) mLocalSender);
1057 }
1058
1059 public Intent getResult() {
1060 try {
1061 return mResult.take();
1062 } catch (InterruptedException e) {
1063 throw new RuntimeException(e);
1064 }
1065 }
1066 }
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001067
1068 private final class PreRebootVerificationHandler extends Handler {
Rhed Jaob793c212019-10-30 20:54:37 +08001069 // Hold session ids before handler gets ready to do the verification.
1070 private IntArray mPendingSessionIds;
1071 private boolean mIsReady;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001072
1073 PreRebootVerificationHandler(Looper looper) {
1074 super(looper);
1075 }
1076
1077 /**
1078 * Handler for states of pre reboot verification. The states are arranged linearly (shown
1079 * below) with each state either calling the next state, or calling some other method that
1080 * eventually calls the next state.
1081 *
1082 * <p><ul>
1083 * <li>MSG_PRE_REBOOT_VERIFICATION_START</li>
1084 * <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li>
1085 * <li>MSG_PRE_REBOOT_VERIFICATION_APK</li>
1086 * <li>MSG_PRE_REBOOT_VERIFICATION_END</li>
1087 * </ul></p>
1088 *
1089 * Details about each of state can be found in corresponding handler of node.
1090 */
1091 private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
1092 private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
1093 private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3;
1094 private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4;
1095
1096 @Override
1097 public void handleMessage(Message msg) {
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001098 final int sessionId = msg.arg1;
1099 final PackageInstallerSession session;
1100 synchronized (mStagedSessions) {
1101 session = mStagedSessions.get(sessionId);
1102 }
1103 // Maybe session was aborted before pre-reboot verification was complete
1104 if (session == null) {
1105 Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId);
1106 return;
1107 }
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001108 switch (msg.what) {
1109 case MSG_PRE_REBOOT_VERIFICATION_START:
1110 handlePreRebootVerification_Start(session);
1111 break;
1112 case MSG_PRE_REBOOT_VERIFICATION_APEX:
1113 handlePreRebootVerification_Apex(session);
1114 break;
1115 case MSG_PRE_REBOOT_VERIFICATION_APK:
1116 handlePreRebootVerification_Apk(session);
1117 break;
1118 case MSG_PRE_REBOOT_VERIFICATION_END:
1119 handlePreRebootVerification_End(session);
1120 break;
1121 }
1122 }
1123
Rhed Jaob793c212019-10-30 20:54:37 +08001124 // Notify the handler that system is ready, and reschedule the pre-reboot verifications.
1125 private synchronized void readyToStart() {
1126 mIsReady = true;
1127 if (mPendingSessionIds != null) {
1128 for (int i = 0; i < mPendingSessionIds.size(); i++) {
1129 startPreRebootVerification(mPendingSessionIds.get(i));
1130 }
1131 mPendingSessionIds = null;
1132 }
1133 }
1134
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001135 // Method for starting the pre-reboot verification
Rhed Jaob793c212019-10-30 20:54:37 +08001136 private synchronized void startPreRebootVerification(int sessionId) {
1137 if (!mIsReady) {
1138 if (mPendingSessionIds == null) {
1139 mPendingSessionIds = new IntArray();
1140 }
1141 mPendingSessionIds.add(sessionId);
1142 return;
1143 }
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001144 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001145 }
1146
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001147 private void notifyPreRebootVerification_Start_Complete(int sessionId) {
1148 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001149 }
1150
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001151 private void notifyPreRebootVerification_Apex_Complete(int sessionId) {
1152 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001153 }
1154
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001155 private void notifyPreRebootVerification_Apk_Complete(int sessionId) {
1156 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001157 }
1158
1159 /**
1160 * A dummy state for starting the pre reboot verification.
1161 *
1162 * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification
1163 */
1164 private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
1165 Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
Oli Lanc72b0bb2019-12-02 14:03:55 +00001166
1167 if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
1168 // If rollback is enabled for this session, we call through to the RollbackManager
1169 // with the list of sessions it must enable rollback for. Note that
1170 // notifyStagedSession is a synchronous operation.
1171 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
1172 ServiceManager.getService(Context.ROLLBACK_SERVICE));
1173 try {
1174 // NOTE: To stay consistent with the non-staged install flow, we don't fail the
1175 // entire install if rollbacks can't be enabled.
1176 int rollbackId = rm.notifyStagedSession(session.sessionId);
1177 if (rollbackId != -1) {
1178 synchronized (mStagedSessions) {
1179 mSessionRollbackIds.put(session.sessionId, rollbackId);
1180 }
1181 }
1182 } catch (RemoteException re) {
1183 Slog.e(TAG, "Failed to notifyStagedSession for session: "
1184 + session.sessionId, re);
1185 }
1186 }
1187
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001188 notifyPreRebootVerification_Start_Complete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001189 }
1190
1191 /**
1192 * Pre-reboot verification state for apex files:
1193 *
1194 * <p><ul>
1195 * <li>submits session to apex service</li>
1196 * <li>validates signatures of apex files</li>
1197 * </ul></p>
1198 */
1199 private void handlePreRebootVerification_Apex(@NonNull PackageInstallerSession session) {
1200 final boolean hasApex = sessionContainsApex(session);
1201
1202 // APEX checks. For single-package sessions, check if they contain an APEX. For
1203 // multi-package sessions, find all the child sessions that contain an APEX.
1204 if (hasApex) {
1205 try {
1206 final List<PackageInfo> apexPackages =
1207 submitSessionToApexService(session);
1208 for (PackageInfo apexPackage : apexPackages) {
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +00001209 validateApexSignature(apexPackage);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001210 }
1211 } catch (PackageManagerException e) {
1212 session.setStagedSessionFailed(e.error, e.getMessage());
1213 return;
1214 }
1215 }
1216
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001217 notifyPreRebootVerification_Apex_Complete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001218 }
1219
1220 /**
1221 * Pre-reboot verification state for apk files:
1222 * <p><ul>
1223 * <li>performs a dry-run install of apk</li>
1224 * </ul></p>
1225 */
1226 private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) {
1227 if (!sessionContainsApk(session)) {
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001228 notifyPreRebootVerification_Apk_Complete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001229 return;
1230 }
1231
1232 try {
1233 Slog.d(TAG, "Running a pre-reboot verification for APKs in session "
1234 + session.sessionId + " by performing a dry-run install");
1235
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +01001236 // verifyApksInSession will notify the handler when APK verification is complete
1237 verifyApksInSession(session);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001238 // TODO(b/118865310): abort the session on apexd.
1239 } catch (PackageManagerException e) {
1240 session.setStagedSessionFailed(e.error, e.getMessage());
1241 }
1242 }
1243
1244 /**
1245 * Pre-reboot verification state for wrapping up:
1246 * <p><ul>
1247 * <li>enables rollback if required</li>
1248 * <li>marks session as ready</li>
1249 * </ul></p>
1250 */
1251 private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) {
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +00001252 // Before marking the session as ready, start checkpoint service if available
1253 try {
1254 IStorageManager storageManager = PackageHelper.getStorageManager();
1255 if (storageManager.supportsCheckpoint()) {
Gavin Corkeryab2544c2020-02-04 08:50:53 +00001256 storageManager.startCheckpoint(2);
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +00001257 }
1258 } catch (Exception e) {
1259 // Failed to get hold of StorageManager
1260 Slog.e(TAG, "Failed to get hold of StorageManager", e);
1261 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
1262 "Failed to get hold of StorageManager");
1263 return;
1264 }
1265
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001266 // Proactively mark session as ready before calling apexd. Although this call order
1267 // looks counter-intuitive, this is the easiest way to ensure that session won't end up
1268 // in the inconsistent state:
1269 // - If device gets rebooted right before call to apexd, then apexd will never activate
1270 // apex files of this staged session. This will result in StagingManager failing
1271 // the session.
1272 // On the other hand, if the order of the calls was inverted (first call apexd, then
1273 // mark session as ready), then if a device gets rebooted right after the call to apexd,
1274 // only apex part of the train will be applied, leaving device in an inconsistent state.
1275 Slog.d(TAG, "Marking session " + session.sessionId + " as ready");
1276 session.setStagedSessionReady();
1277 final boolean hasApex = sessionContainsApex(session);
1278 if (!hasApex) {
1279 // Session doesn't contain apex, nothing to do.
1280 return;
1281 }
1282 try {
1283 mApexManager.markStagedSessionReady(session.sessionId);
1284 } catch (PackageManagerException e) {
1285 session.setStagedSessionFailed(e.error, e.getMessage());
1286 }
1287 }
1288 }
Dario Frenibe98c3f2018-12-22 15:25:27 +00001289}