blob: 6cd66c642a064bac110f988103214b3f4a17ecb2 [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;
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;
Rhed Jao269616b2020-03-30 13:23:14 +080042import android.content.pm.parsing.PackageInfoWithoutStateUtils;
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 Islama9225312020-06-10 18:00:24 +010056import android.os.SystemProperties;
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +000057import android.os.UserHandle;
58import android.os.UserManagerInternal;
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +000059import android.os.storage.IStorageManager;
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +000060import android.os.storage.StorageManager;
Gavin Corkery333136c2020-02-03 19:03:43 +000061import android.text.TextUtils;
Nikita Ioffee0dbc982019-05-09 19:33:35 +010062import android.util.IntArray;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000063import android.util.Slog;
Dario Frenibe98c3f2018-12-22 15:25:27 +000064import android.util.SparseArray;
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +010065import android.util.SparseBooleanArray;
Oli Lanc72b0bb2019-12-02 14:03:55 +000066import android.util.SparseIntArray;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000067import android.util.apk.ApkSignatureVerifier;
Dario Frenibe98c3f2018-12-22 15:25:27 +000068
69import com.android.internal.annotations.GuardedBy;
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +000070import com.android.internal.content.PackageHelper;
Narayan Kamath94c93ab2019-01-04 10:47:00 +000071import com.android.internal.os.BackgroundThread;
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +000072import com.android.server.LocalServices;
Gavin Corkery24b4ed52020-04-29 16:30:10 +010073import com.android.server.SystemService;
74import com.android.server.SystemServiceManager;
Rhed Jao269616b2020-03-30 13:23:14 +080075import com.android.server.pm.parsing.PackageParser2;
Winson5e0a1d52020-01-24 12:00:33 -080076import com.android.server.pm.parsing.pkg.AndroidPackage;
Winsone0756292020-01-31 12:21:54 -080077import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
Rhed Jao269616b2020-03-30 13:23:14 +080078import com.android.server.pm.parsing.pkg.ParsedPackage;
Gavin Corkery333136c2020-02-03 19:03:43 +000079import com.android.server.rollback.WatchdogRollbackLogger;
Dario Frenibe98c3f2018-12-22 15:25:27 +000080
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +010081import java.io.BufferedReader;
82import java.io.BufferedWriter;
Dario Freni4b572c02019-01-29 09:40:31 +000083import java.io.File;
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +010084import java.io.FileReader;
85import java.io.FileWriter;
Dario Freni4b572c02019-01-29 09:40:31 +000086import java.io.IOException;
Dario Frenibe98c3f2018-12-22 15:25:27 +000087import java.util.ArrayList;
88import java.util.List;
Dario Freni4b572c02019-01-29 09:40:31 +000089import java.util.concurrent.LinkedBlockingQueue;
90import java.util.concurrent.TimeUnit;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +010091import java.util.function.Consumer;
Dario Frenif141aab2019-02-04 14:48:30 +000092import java.util.function.Predicate;
Rhed Jao269616b2020-03-30 13:23:14 +080093import java.util.function.Supplier;
Dario Frenibe98c3f2018-12-22 15:25:27 +000094
95/**
96 * This class handles staged install sessions, i.e. install sessions that require packages to
97 * be installed only after a reboot.
98 */
99public class StagingManager {
100
101 private static final String TAG = "StagingManager";
102
Dario Freni4b572c02019-01-29 09:40:31 +0000103 private final PackageInstallerService mPi;
Dario Freni2e8dffc2019-02-06 14:55:16 +0000104 private final ApexManager mApexManager;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000105 private final PowerManager mPowerManager;
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +0000106 private final Context mContext;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100107 private final PreRebootVerificationHandler mPreRebootVerificationHandler;
Rhed Jao269616b2020-03-30 13:23:14 +0800108 private final Supplier<PackageParser2> mPackageParserSupplier;
Dario Frenibe98c3f2018-12-22 15:25:27 +0000109
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +0100110 private final File mFailureReasonFile = new File("/metadata/staged-install/failure_reason.txt");
111 private String mFailureReason;
112
Dario Frenibe98c3f2018-12-22 15:25:27 +0000113 @GuardedBy("mStagedSessions")
114 private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
115
Oli Lanc72b0bb2019-12-02 14:03:55 +0000116 @GuardedBy("mStagedSessions")
117 private final SparseIntArray mSessionRollbackIds = new SparseIntArray();
118
Gavin Corkery333136c2020-02-03 19:03:43 +0000119 @GuardedBy("mFailedPackageNames")
120 private final List<String> mFailedPackageNames = new ArrayList<>();
121 private String mNativeFailureReason;
122
Gavin Corkery24b4ed52020-04-29 16:30:10 +0100123 @GuardedBy("mSuccessfulStagedSessionIds")
124 private final List<Integer> mSuccessfulStagedSessionIds = new ArrayList<>();
125
Rhed Jao269616b2020-03-30 13:23:14 +0800126 StagingManager(PackageInstallerService pi, Context context,
127 Supplier<PackageParser2> packageParserSupplier) {
Dario Freni4b572c02019-01-29 09:40:31 +0000128 mPi = pi;
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +0000129 mContext = context;
Rhed Jao269616b2020-03-30 13:23:14 +0800130 mPackageParserSupplier = packageParserSupplier;
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000131
132 mApexManager = ApexManager.getInstance();
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000133 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100134 mPreRebootVerificationHandler = new PreRebootVerificationHandler(
135 BackgroundThread.get().getLooper());
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +0100136
137 if (mFailureReasonFile.exists()) {
138 try (BufferedReader reader = new BufferedReader(new FileReader(mFailureReasonFile))) {
139 mFailureReason = reader.readLine();
140 } catch (Exception ignore) { }
141 }
Dario Frenibe98c3f2018-12-22 15:25:27 +0000142 }
143
Gavin Corkery24b4ed52020-04-29 16:30:10 +0100144 /**
145 This class manages lifecycle events for StagingManager.
146 */
147 public static final class Lifecycle extends SystemService {
148 private static StagingManager sStagingManager;
149
150 public Lifecycle(Context context) {
151 super(context);
152 }
153
154 void startService(StagingManager stagingManager) {
155 sStagingManager = stagingManager;
156 LocalServices.getService(SystemServiceManager.class).startService(this);
157 }
158
159 @Override
160 public void onStart() {
161 // no-op
162 }
163
164 @Override
165 public void onBootPhase(int phase) {
166 if (phase == SystemService.PHASE_BOOT_COMPLETED && sStagingManager != null) {
167 sStagingManager.markStagedSessionsAsSuccessful();
168 }
169 }
170 }
171
Dario Frenibe98c3f2018-12-22 15:25:27 +0000172 private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
173 synchronized (mStagedSessions) {
174 PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId);
Narayan Kamath94c93ab2019-01-04 10:47:00 +0000175 // storedSession might be null if a call to abortSession was made before the session
176 // is updated.
177 if (storedSession != null) {
178 mStagedSessions.put(sessionInfo.sessionId, sessionInfo);
Dario Frenibe98c3f2018-12-22 15:25:27 +0000179 }
Dario Frenibe98c3f2018-12-22 15:25:27 +0000180 }
181 }
182
Patrick Baumann6bc126b2020-03-06 10:34:17 -0800183 ParceledListSlice<PackageInstaller.SessionInfo> getSessions(int callingUid) {
Dario Frenibe98c3f2018-12-22 15:25:27 +0000184 final List<PackageInstaller.SessionInfo> result = new ArrayList<>();
185 synchronized (mStagedSessions) {
186 for (int i = 0; i < mStagedSessions.size(); i++) {
Patrick Baumann6bc126b2020-03-06 10:34:17 -0800187 final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
Mohammad Samiul Islam731bd962020-04-23 16:23:21 +0100188 if (stagedSession.isDestroyed()) {
189 continue;
190 }
Patrick Baumann6bc126b2020-03-06 10:34:17 -0800191 result.add(stagedSession.generateInfoForCaller(false /*icon*/, callingUid));
Dario Frenibe98c3f2018-12-22 15:25:27 +0000192 }
193 }
194 return new ParceledListSlice<>(result);
195 }
196
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100197 /**
198 * Validates the signature used to sign the container of the new apex package
199 *
200 * @param newApexPkg The new apex package that is being installed
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100201 * @throws PackageManagerException
202 */
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000203 private void validateApexSignature(PackageInfo newApexPkg)
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100204 throws PackageManagerException {
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100205 // Get signing details of the new package
206 final String apexPath = newApexPkg.applicationInfo.sourceDir;
207 final String packageName = newApexPkg.packageName;
Michael Groover33df7c42020-01-24 19:00:01 -0800208 int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
209 newApexPkg.applicationInfo.targetSdkVersion);
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100210
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000211 final SigningDetails newSigningDetails;
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000212 try {
Michael Groover33df7c42020-01-24 19:00:01 -0800213 newSigningDetails = ApkSignatureVerifier.verify(apexPath, minSignatureScheme);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000214 } catch (PackageParserException e) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100215 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
216 "Failed to parse APEX package " + apexPath, e);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000217 }
218
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100219 // Get signing details of the existing package
220 final PackageInfo existingApexPkg = mApexManager.getPackageInfo(packageName,
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100221 ApexManager.MATCH_ACTIVE_PACKAGE);
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100222 if (existingApexPkg == null) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100223 // This should never happen, because submitSessionToApexService ensures that no new
224 // apexes were installed.
225 throw new IllegalStateException("Unknown apex package " + packageName);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000226 }
227
228 final SigningDetails existingSigningDetails;
229 try {
230 existingSigningDetails = ApkSignatureVerifier.verify(
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100231 existingApexPkg.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000232 } catch (PackageParserException e) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100233 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100234 "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir, e);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000235 }
236
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100237 // Verify signing details for upgrade
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000238 if (newSigningDetails.checkCapability(existingSigningDetails,
239 SigningDetails.CertCapabilities.INSTALLED_DATA)
240 || existingSigningDetails.checkCapability(newSigningDetails,
241 SigningDetails.CertCapabilities.ROLLBACK)) {
Mohammad Samiul Islam8e35db12019-08-22 15:43:55 +0100242 return;
243 }
244
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100245 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100246 "APK-container signature of APEX package " + packageName + " with version "
247 + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not"
248 + " compatible with the one currently installed on device");
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000249 }
250
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100251 private List<PackageInfo> submitSessionToApexService(
252 @NonNull PackageInstallerSession session) throws PackageManagerException {
Oli Lanc72b0bb2019-12-02 14:03:55 +0000253 final IntArray childSessionIds = new IntArray();
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100254 if (session.isMultiPackage()) {
255 for (int id : session.getChildSessionIds()) {
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +0100256 if (isApexSession(getStagedSession(id))) {
Oli Lanc72b0bb2019-12-02 14:03:55 +0000257 childSessionIds.add(id);
258 }
259 }
260 }
261 ApexSessionParams apexSessionParams = new ApexSessionParams();
262 apexSessionParams.sessionId = session.sessionId;
263 apexSessionParams.childSessionIds = childSessionIds.toArray();
264 if (session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
265 apexSessionParams.isRollback = true;
266 apexSessionParams.rollbackId = retrieveRollbackIdForCommitSession(session.sessionId);
267 } else {
268 synchronized (mStagedSessions) {
269 int rollbackId = mSessionRollbackIds.get(session.sessionId, -1);
270 if (rollbackId != -1) {
271 apexSessionParams.hasRollbackEnabled = true;
272 apexSessionParams.rollbackId = rollbackId;
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100273 }
Nikita Ioffe8bbb8152019-02-21 15:54:54 +0000274 }
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100275 }
Nikita Ioffe4e7d24a2019-07-05 15:49:45 +0100276 // submitStagedSession will throw a PackageManagerException if apexd verification fails,
277 // which will be propagated to populate stagedSessionErrorMessage of this session.
Oli Lanc72b0bb2019-12-02 14:03:55 +0000278 final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100279 final List<PackageInfo> result = new ArrayList<>();
Jackal Guoae037922020-03-25 15:15:18 +0800280 final List<String> apexPackageNames = new ArrayList<>();
Oli Lanc2c7a222019-07-31 15:27:22 +0100281 for (ApexInfo apexInfo : apexInfoList.apexInfos) {
282 final PackageInfo packageInfo;
Rhed Jao269616b2020-03-30 13:23:14 +0800283 final int flags = PackageManager.GET_META_DATA;
284 try (PackageParser2 packageParser = mPackageParserSupplier.get()) {
Oli Lanc2c7a222019-07-31 15:27:22 +0100285 File apexFile = new File(apexInfo.modulePath);
Rhed Jao269616b2020-03-30 13:23:14 +0800286 final ParsedPackage parsedPackage = packageParser.parsePackage(
287 apexFile, flags, false);
288 packageInfo = PackageInfoWithoutStateUtils.generate(parsedPackage, apexInfo, flags);
289 if (packageInfo == null) {
290 throw new PackageManagerException(
291 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
292 "Unable to generate package info: " + apexInfo.modulePath);
293 }
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100294 } catch (PackageParserException e) {
295 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Oli Lanc2c7a222019-07-31 15:27:22 +0100296 "Failed to parse APEX package " + apexInfo.modulePath, e);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100297 }
Oli Lanc2c7a222019-07-31 15:27:22 +0100298 final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName,
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100299 ApexManager.MATCH_ACTIVE_PACKAGE);
300 if (activePackage == null) {
Oli Lanc2c7a222019-07-31 15:27:22 +0100301 Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100302 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
303 "It is forbidden to install new APEX packages.");
304 }
305 checkRequiredVersionCode(session, activePackage);
Oli Lanc2c7a222019-07-31 15:27:22 +0100306 checkDowngrade(session, activePackage, packageInfo);
307 result.add(packageInfo);
Jackal Guoae037922020-03-25 15:15:18 +0800308 apexPackageNames.add(packageInfo.packageName);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100309 }
Jackal Guoae037922020-03-25 15:15:18 +0800310 Slog.d(TAG, "Session " + session.sessionId + " has following APEX packages: "
311 + apexPackageNames);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100312 return result;
313 }
314
Oli Lanc72b0bb2019-12-02 14:03:55 +0000315 private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException {
316 RollbackManager rm = mContext.getSystemService(RollbackManager.class);
317
Jackal Guo1b333092020-03-30 11:12:56 +0800318 final List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks();
319 for (int i = 0, size = rollbacks.size(); i < size; i++) {
320 final RollbackInfo rollback = rollbacks.get(i);
Oli Lanc72b0bb2019-12-02 14:03:55 +0000321 if (rollback.getCommittedSessionId() == sessionId) {
322 return rollback.getRollbackId();
323 }
324 }
325 throw new PackageManagerException(
326 "Could not find rollback id for commit session: " + sessionId);
327 }
328
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100329 private void checkRequiredVersionCode(final PackageInstallerSession session,
330 final PackageInfo activePackage) throws PackageManagerException {
331 if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) {
332 return;
333 }
334 final long activeVersion = activePackage.applicationInfo.longVersionCode;
335 if (activeVersion != session.params.requiredInstalledVersionCode) {
Mohammad Samiul Islam44ad95b2019-11-20 15:14:36 +0000336 if (!mApexManager.abortStagedSession(session.sessionId)) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100337 Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
338 }
339 throw new PackageManagerException(
340 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
341 "Installed version of APEX package " + activePackage.packageName
Richard Uhler2124d4b2019-04-25 13:01:39 +0100342 + " does not match required. Active version: " + activeVersion
343 + " required: " + session.params.requiredInstalledVersionCode);
Nikita Ioffe8bbb8152019-02-21 15:54:54 +0000344 }
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100345 }
346
347 private void checkDowngrade(final PackageInstallerSession session,
348 final PackageInfo activePackage, final PackageInfo newPackage)
349 throws PackageManagerException {
350 final long activeVersion = activePackage.applicationInfo.longVersionCode;
351 final long newVersionCode = newPackage.applicationInfo.longVersionCode;
Winsonf00c7552020-01-28 12:52:01 -0800352 boolean isAppDebuggable = (activePackage.applicationInfo.flags
353 & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100354 final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
Winsonf00c7552020-01-28 12:52:01 -0800355 session.params.installFlags, isAppDebuggable);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100356 if (activeVersion > newVersionCode && !allowsDowngrade) {
Mohammad Samiul Islam44ad95b2019-11-20 15:14:36 +0000357 if (!mApexManager.abortStagedSession(session.sessionId)) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100358 Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
359 }
360 throw new PackageManagerException(
361 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
362 "Downgrade of APEX package " + newPackage.packageName
363 + " is not allowed. Active version: " + activeVersion
364 + " attempted: " + newVersionCode);
365 }
Dario Freni015f9352019-01-14 21:56:17 +0000366 }
367
Dario Freni015f9352019-01-14 21:56:17 +0000368 private static boolean isApexSession(@NonNull PackageInstallerSession session) {
369 return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
370 }
371
Dario Frenif141aab2019-02-04 14:48:30 +0000372 private boolean sessionContains(@NonNull PackageInstallerSession session,
373 Predicate<PackageInstallerSession> filter) {
Dario Freni4b572c02019-01-29 09:40:31 +0000374 if (!session.isMultiPackage()) {
Dario Frenif141aab2019-02-04 14:48:30 +0000375 return filter.test(session);
Dario Freni4b572c02019-01-29 09:40:31 +0000376 }
377 synchronized (mStagedSessions) {
Jackal Guoae037922020-03-25 15:15:18 +0800378 final int[] childSessionIds = session.getChildSessionIds();
379 for (int id : childSessionIds) {
380 // Retrieve cached sessions matching ids.
381 final PackageInstallerSession s = mStagedSessions.get(id);
382 // Filter only the ones containing APEX.
383 if (filter.test(s)) {
384 return true;
385 }
386 }
387 return false;
Dario Freni4b572c02019-01-29 09:40:31 +0000388 }
389 }
390
Dario Frenif141aab2019-02-04 14:48:30 +0000391 private boolean sessionContainsApex(@NonNull PackageInstallerSession session) {
392 return sessionContains(session, (s) -> isApexSession(s));
393 }
394
395 private boolean sessionContainsApk(@NonNull PackageInstallerSession session) {
396 return sessionContains(session, (s) -> !isApexSession(s));
397 }
398
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000399 // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +0100400 private void abortCheckpoint(int sessionId, String errorMsg) {
401 String failureReason = "Failed to install sessionId: " + sessionId + " Error: " + errorMsg;
402 Slog.e(TAG, failureReason);
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000403 try {
404 if (supportsCheckpoint() && needsCheckpoint()) {
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +0100405 // Store failure reason for next reboot
406 try (BufferedWriter writer =
407 new BufferedWriter(new FileWriter(mFailureReasonFile))) {
408 writer.write(failureReason);
409 } catch (Exception e) {
410 Slog.w(TAG, "Failed to save failure reason: ", e);
411 }
412
Mohammad Samiul Islam5bf077d2020-05-04 16:36:01 +0100413 // Only revert apex sessions if device supports updating apex
414 if (mApexManager.isApexSupported()) {
415 mApexManager.revertActiveSessions();
416 }
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000417 PackageHelper.getStorageManager().abortChanges(
418 "StagingManager initiated", false /*retry*/);
419 }
420 } catch (Exception e) {
421 Slog.wtf(TAG, "Failed to abort checkpoint", e);
Mohammad Samiul Islam5bf077d2020-05-04 16:36:01 +0100422 // Only revert apex sessions if device supports updating apex
423 if (mApexManager.isApexSupported()) {
424 mApexManager.revertActiveSessions();
425 }
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000426 mPowerManager.reboot(null);
427 }
428 }
429
430 private boolean supportsCheckpoint() throws RemoteException {
431 return PackageHelper.getStorageManager().supportsCheckpoint();
432 }
433
434 private boolean needsCheckpoint() throws RemoteException {
435 return PackageHelper.getStorageManager().needsCheckpoint();
436 }
437
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000438 /**
Mohammad Samiul Islamcefe39f2020-03-22 21:32:33 +0000439 * Utility function for extracting apex sessions out of multi-package/single session.
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000440 */
Mohammad Samiul Islamcefe39f2020-03-22 21:32:33 +0000441 private List<PackageInstallerSession> extractApexSessions(PackageInstallerSession session) {
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000442 List<PackageInstallerSession> apexSessions = new ArrayList<>();
443 if (session.isMultiPackage()) {
444 List<PackageInstallerSession> childrenSessions = new ArrayList<>();
445 synchronized (mStagedSessions) {
446 for (int childSessionId : session.getChildSessionIds()) {
447 PackageInstallerSession childSession = mStagedSessions.get(childSessionId);
448 if (childSession != null) {
449 childrenSessions.add(childSession);
450 }
451 }
452 }
Jackal Guo1b333092020-03-30 11:12:56 +0800453 for (int i = 0, size = childrenSessions.size(); i < size; i++) {
454 final PackageInstallerSession childSession = childrenSessions.get(i);
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000455 if (sessionContainsApex(childSession)) {
456 apexSessions.add(childSession);
457 }
458 }
459 } else {
460 apexSessions.add(session);
461 }
Mohammad Samiul Islamcefe39f2020-03-22 21:32:33 +0000462 return apexSessions;
463 }
464
465 /**
466 * Checks if all apk-in-apex were installed without errors for all of the apex sessions. Throws
467 * error for any apk-in-apex failed to install.
468 *
469 * @throws PackageManagerException if any apk-in-apex failed to install
470 */
471 private void checkInstallationOfApkInApexSuccessful(PackageInstallerSession session)
472 throws PackageManagerException {
473 final List<PackageInstallerSession> apexSessions = extractApexSessions(session);
474 if (apexSessions.isEmpty()) {
475 return;
476 }
477
478 for (PackageInstallerSession apexSession : apexSessions) {
479 String packageName = apexSession.getPackageName();
480 if (!mApexManager.isApkInApexInstallSuccess(packageName)) {
481 throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
482 "Failed to install apk-in-apex of " + packageName);
483 }
484 }
485 }
486
487 /**
488 * Perform snapshot and restore as required both for APEXes themselves and for apks in APEX.
489 * Apks inside apex are not installed using apk-install flow. They are scanned from the system
490 * directory directly by PackageManager, as such, RollbackManager need to handle their data
491 * separately here.
492 */
493 private void snapshotAndRestoreForApexSession(PackageInstallerSession session) {
494 boolean doSnapshotOrRestore =
495 (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
496 || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
497 if (!doSnapshotOrRestore) {
498 return;
499 }
500
501 // Find all the apex sessions that needs processing
502 final List<PackageInstallerSession> apexSessions = extractApexSessions(session);
503 if (apexSessions.isEmpty()) {
504 return;
505 }
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000506
Oli Lan7bbd8b42020-01-14 10:11:42 +0000507 final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
508 final int[] allUsers = um.getUserIds();
509 IRollbackManager rm = IRollbackManager.Stub.asInterface(
510 ServiceManager.getService(Context.ROLLBACK_SERVICE));
511
Jackal Guo1b333092020-03-30 11:12:56 +0800512 for (int i = 0, sessionsSize = apexSessions.size(); i < sessionsSize; i++) {
513 final String packageName = apexSessions.get(i).getPackageName();
Oli Lan7bbd8b42020-01-14 10:11:42 +0000514 // Perform any snapshots or restores for the APEX itself
515 snapshotAndRestoreApexUserData(packageName, allUsers, rm);
516
517 // Process the apks inside the APEX
Jackal Guo1b333092020-03-30 11:12:56 +0800518 final List<String> apksInApex = mApexManager.getApksInApex(packageName);
519 for (int j = 0, apksSize = apksInApex.size(); j < apksSize; j++) {
520 snapshotAndRestoreApkInApexUserData(apksInApex.get(j), allUsers, rm);
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000521 }
522 }
523 }
524
Oli Lan7bbd8b42020-01-14 10:11:42 +0000525 private void snapshotAndRestoreApexUserData(
526 String packageName, int[] allUsers, IRollbackManager rm) {
527 try {
528 // appId, ceDataInode, and seInfo are not needed for APEXes
529 rm.snapshotAndRestoreUserData(packageName, allUsers, 0, 0,
530 null, 0 /*token*/);
531 } catch (RemoteException re) {
532 Slog.e(TAG, "Error snapshotting/restoring user data: " + re);
533 }
534 }
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000535
Oli Lan7bbd8b42020-01-14 10:11:42 +0000536 private void snapshotAndRestoreApkInApexUserData(
537 String packageName, int[] allUsers, IRollbackManager rm) {
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000538 PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class);
539 AndroidPackage pkg = mPmi.getPackage(packageName);
540 if (pkg == null) {
541 Slog.e(TAG, "Could not find package: " + packageName
542 + "for snapshotting/restoring user data.");
543 return;
544 }
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000545
546 int appId = -1;
547 long ceDataInode = -1;
Winsone0756292020-01-31 12:21:54 -0800548 final PackageSetting ps = mPmi.getPackageSetting(packageName);
Oli Lan7bbd8b42020-01-14 10:11:42 +0000549 if (ps != null) {
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000550 appId = ps.appId;
551 ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM);
552 // NOTE: We ignore the user specified in the InstallParam because we know this is
553 // an update, and hence need to restore data for all installed users.
554 final int[] installedUsers = ps.queryInstalledUsers(allUsers, true);
555
Winsone0756292020-01-31 12:21:54 -0800556 final String seInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
Mohammad Samiul Islam3fcecfc2019-12-20 17:46:01 +0000557 try {
558 rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode,
559 seInfo, 0 /*token*/);
560 } catch (RemoteException re) {
561 Slog.e(TAG, "Error snapshotting/restoring user data: " + re);
562 }
563 }
564 }
565
Gavin Corkery333136c2020-02-03 19:03:43 +0000566 /**
567 * Prepares for the logging of apexd reverts by storing the native failure reason if necessary,
568 * and adding the package name of the session which apexd reverted to the list of reverted
569 * session package names.
570 * Logging needs to wait until the ACTION_BOOT_COMPLETED broadcast is sent.
571 */
572 private void prepareForLoggingApexdRevert(@NonNull PackageInstallerSession session,
573 @NonNull String nativeFailureReason) {
574 synchronized (mFailedPackageNames) {
575 mNativeFailureReason = nativeFailureReason;
576 if (session.getPackageName() != null) {
577 mFailedPackageNames.add(session.getPackageName());
578 }
579 }
580 }
581
Dario Freni61b3e252019-01-11 23:05:39 +0000582 private void resumeSession(@NonNull PackageInstallerSession session) {
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100583 Slog.d(TAG, "Resuming session " + session.sessionId);
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000584
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100585 final boolean hasApex = sessionContainsApex(session);
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000586 ApexSessionInfo apexSessionInfo = null;
Nikita Ioffea820bd92019-02-15 14:22:44 +0000587 if (hasApex) {
Dario Freni2e8dffc2019-02-06 14:55:16 +0000588 // Check with apexservice whether the apex packages have been activated.
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000589 apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
590
Gavin Corkery333136c2020-02-03 19:03:43 +0000591 // Prepare for logging a native crash during boot, if one occurred.
592 if (apexSessionInfo != null && !TextUtils.isEmpty(
593 apexSessionInfo.crashingNativeProcess)) {
594 prepareForLoggingApexdRevert(session, apexSessionInfo.crashingNativeProcess);
595 }
596
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000597 if (apexSessionInfo != null && apexSessionInfo.isVerified) {
598 // Session has been previously submitted to apexd, but didn't complete all the
599 // pre-reboot verification, perhaps because the device rebooted in the meantime.
600 // Greedily re-trigger the pre-reboot verification. We want to avoid marking it as
601 // failed when not in checkpoint mode, hence it is being processed separately.
602 Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to "
603 + "be verified, resuming pre-reboot verification");
604 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
605 return;
606 }
607 }
608
609 // Before we resume session, we check if revert is needed or not. Typically, we enter file-
610 // system checkpoint mode when we reboot first time in order to install staged sessions. We
611 // want to install staged sessions in this mode as rebooting now will revert user data. If
612 // something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will
613 // have no effect on user data, so mark the sessions as failed instead.
614 try {
615 // If checkpoint is supported, then we only resume sessions if we are in checkpointing
616 // mode. If not, we fail all sessions.
617 if (supportsCheckpoint() && !needsCheckpoint()) {
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +0100618 String errorMsg = "Reverting back to safe state. Marking " + session.sessionId
619 + " as failed";
620 if (!TextUtils.isEmpty(mFailureReason)) {
621 errorMsg = errorMsg + ": " + mFailureReason;
Gavin Corkery7e8b5a32020-04-20 09:49:18 +0100622 }
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +0100623 Slog.d(TAG, errorMsg);
Gavin Corkery7e8b5a32020-04-20 09:49:18 +0100624 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, errorMsg);
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000625 return;
626 }
627 } catch (RemoteException e) {
628 // Cannot continue staged install without knowing if fs-checkpoint is supported
629 Slog.e(TAG, "Checkpoint support unknown. Aborting staged install for session "
630 + session.sessionId, e);
631 // TODO: Mark all staged sessions together and reboot only once
632 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
633 "Checkpoint support unknown. Aborting staged install.");
634 if (hasApex) {
635 mApexManager.revertActiveSessions();
636 }
637 mPowerManager.reboot("Checkpoint support unknown");
638 return;
639 }
640
Mohammad Samiul Islamcefe39f2020-03-22 21:32:33 +0000641 // Check if apex packages in the session failed to activate
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000642 if (hasApex) {
Dario Freni2e8dffc2019-02-06 14:55:16 +0000643 if (apexSessionInfo == null) {
Mohammad Samiul Islamcefe39f2020-03-22 21:32:33 +0000644 final String errorMsg = "apexd did not know anything about a staged session "
645 + "supposed to be activated";
Dario Freni2e8dffc2019-02-06 14:55:16 +0000646 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
Mohammad Samiul Islam166243f2020-03-06 16:19:22 +0000647 errorMsg);
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +0100648 abortCheckpoint(session.sessionId, errorMsg);
Dario Freni4b572c02019-01-29 09:40:31 +0000649 return;
650 }
Nikita Ioffe82742222019-02-26 12:14:04 +0000651 if (isApexSessionFailed(apexSessionInfo)) {
Mohammad Samiul Islamcefe39f2020-03-22 21:32:33 +0000652 String errorMsg = "APEX activation failed. Check logcat messages from apexd "
653 + "for more information.";
Gavin Corkery7e8b5a32020-04-20 09:49:18 +0100654 if (!TextUtils.isEmpty(mNativeFailureReason)) {
655 errorMsg = "Session reverted due to crashing native process: "
656 + mNativeFailureReason;
657 }
Dario Frenib6d28962019-01-31 15:52:24 +0000658 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
Mohammad Samiul Islam166243f2020-03-06 16:19:22 +0000659 errorMsg);
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +0100660 abortCheckpoint(session.sessionId, errorMsg);
Dario Freni4b572c02019-01-29 09:40:31 +0000661 return;
662 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000663 if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
Mohammad Samiul Islamcefe39f2020-03-22 21:32:33 +0000664 // Apexd did not apply the session for some unknown reason. There is no
665 // guarantee that apexd will install it next time. Safer to proactively mark
666 // it as failed.
667 final String errorMsg = "Staged session " + session.sessionId + "at boot "
668 + "didn't activate nor fail. Marking it as failed anyway.";
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000669 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
Mohammad Samiul Islam166243f2020-03-06 16:19:22 +0000670 errorMsg);
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +0100671 abortCheckpoint(session.sessionId, errorMsg);
Dario Freni4b572c02019-01-29 09:40:31 +0000672 return;
673 }
674 }
Mohammad Samiul Islamcefe39f2020-03-22 21:32:33 +0000675 // Handle apk and apk-in-apex installation
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100676 try {
Mohammad Samiul Islamcefe39f2020-03-22 21:32:33 +0000677 if (hasApex) {
678 checkInstallationOfApkInApexSuccessful(session);
679 snapshotAndRestoreForApexSession(session);
680 Slog.i(TAG, "APEX packages in session " + session.sessionId
681 + " were successfully activated. Proceeding with APK packages, if any");
682 }
683 // The APEX part of the session is activated, proceed with the installation of APKs.
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100684 Slog.d(TAG, "Installing APK packages in session " + session.sessionId);
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100685 installApksInSession(session);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100686 } catch (PackageManagerException e) {
687 session.setStagedSessionFailed(e.error, e.getMessage());
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +0100688 abortCheckpoint(session.sessionId, e.getMessage());
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000689
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +0000690 // If checkpoint is not supported, we have to handle failure for one staged session.
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000691 if (!hasApex) {
692 return;
693 }
694
Mohammad Samiul Islam1a96eb62019-11-21 10:38:06 +0000695 if (!mApexManager.revertActiveSessions()) {
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000696 Slog.e(TAG, "Failed to abort APEXd session");
697 } else {
698 Slog.e(TAG,
699 "Successfully aborted apexd session. Rebooting device in order to revert "
700 + "to the previous state of APEXd.");
701 mPowerManager.reboot(null);
702 }
Dario Freni61b3e252019-01-11 23:05:39 +0000703 return;
704 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000705
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100706 Slog.d(TAG, "Marking session " + session.sessionId + " as applied");
Dario Freni4b572c02019-01-29 09:40:31 +0000707 session.setStagedSessionApplied();
Nikita Ioffea820bd92019-02-15 14:22:44 +0000708 if (hasApex) {
Gavin Corkery24b4ed52020-04-29 16:30:10 +0100709 try {
710 if (supportsCheckpoint()) {
711 // Store the session ID, which will be marked as successful by ApexManager
712 // upon boot completion.
713 synchronized (mSuccessfulStagedSessionIds) {
714 mSuccessfulStagedSessionIds.add(session.sessionId);
715 }
716 } else {
717 // Mark sessions as successful immediately on non-checkpointing devices.
718 mApexManager.markStagedSessionSuccessful(session.sessionId);
719 }
720 } catch (RemoteException e) {
721 Slog.w(TAG, "Checkpoint support unknown, marking session as successful "
722 + "immediately.");
723 mApexManager.markStagedSessionSuccessful(session.sessionId);
724 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000725 }
Dario Freni4b572c02019-01-29 09:40:31 +0000726 }
727
Dario Freni815bd212019-02-20 14:09:36 +0000728 private List<String> findAPKsInDir(File stageDir) {
729 List<String> ret = new ArrayList<>();
Dario Freni4b572c02019-01-29 09:40:31 +0000730 if (stageDir != null && stageDir.exists()) {
731 for (File file : stageDir.listFiles()) {
732 if (file.getAbsolutePath().toLowerCase().endsWith(".apk")) {
Dario Freni815bd212019-02-20 14:09:36 +0000733 ret.add(file.getAbsolutePath());
Dario Freni4b572c02019-01-29 09:40:31 +0000734 }
735 }
Dario Freni61b3e252019-01-11 23:05:39 +0000736 }
Dario Freni815bd212019-02-20 14:09:36 +0000737 return ret;
Dario Freni4b572c02019-01-29 09:40:31 +0000738 }
739
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100740 @NonNull
Dario Freni4b572c02019-01-29 09:40:31 +0000741 private PackageInstallerSession createAndWriteApkSession(
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100742 @NonNull PackageInstallerSession originalSession, boolean preReboot)
743 throws PackageManagerException {
744 final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
745 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
Dario Freni4b572c02019-01-29 09:40:31 +0000746 if (originalSession.stageDir == null) {
747 Slog.wtf(TAG, "Attempting to install a staged APK session with no staging dir");
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100748 throw new PackageManagerException(errorCode,
749 "Attempting to install a staged APK session with no staging dir");
Dario Freni1473bcb2019-01-25 14:27:13 +0000750 }
Dario Freni815bd212019-02-20 14:09:36 +0000751 List<String> apkFilePaths = findAPKsInDir(originalSession.stageDir);
752 if (apkFilePaths.isEmpty()) {
Dario Freni4b572c02019-01-29 09:40:31 +0000753 Slog.w(TAG, "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100754 throw new PackageManagerException(errorCode,
755 "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
Dario Freni61b3e252019-01-11 23:05:39 +0000756 }
Dario Freni4b572c02019-01-29 09:40:31 +0000757
758 PackageInstaller.SessionParams params = originalSession.params.copy();
759 params.isStaged = false;
Dario Frenia2afebf2019-05-01 16:03:17 +0100760 params.installFlags |= PackageManager.INSTALL_STAGED;
Dario Frenic3e68ea2019-04-02 11:45:13 +0100761 // TODO(b/129744602): use the userid from the original session.
Dario Frenif141aab2019-02-04 14:48:30 +0000762 if (preReboot) {
763 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
764 params.installFlags |= PackageManager.INSTALL_DRY_RUN;
765 } else {
766 params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
767 }
Dario Freni4b572c02019-01-29 09:40:31 +0000768 try {
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +0000769 int apkSessionId = mPi.createSession(
770 params, originalSession.getInstallerPackageName(),
771 0 /* UserHandle.SYSTEM */);
772 PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
Dario Freni4b572c02019-01-29 09:40:31 +0000773 apkSession.open();
Jackal Guo1b333092020-03-30 11:12:56 +0800774 for (int i = 0, size = apkFilePaths.size(); i < size; i++) {
775 final String apkFilePath = apkFilePaths.get(i);
Dario Freni815bd212019-02-20 14:09:36 +0000776 File apkFile = new File(apkFilePath);
777 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
778 ParcelFileDescriptor.MODE_READ_ONLY);
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +0000779 long sizeBytes = (pfd == null) ? -1 : pfd.getStatSize();
Dario Freni815bd212019-02-20 14:09:36 +0000780 if (sizeBytes < 0) {
781 Slog.e(TAG, "Unable to get size of: " + apkFilePath);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100782 throw new PackageManagerException(errorCode,
783 "Unable to get size of: " + apkFilePath);
Dario Freni815bd212019-02-20 14:09:36 +0000784 }
785 apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
Dario Freni4b572c02019-01-29 09:40:31 +0000786 }
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +0000787 return apkSession;
788 } catch (IOException | ParcelableException e) {
Dario Freni4b572c02019-01-29 09:40:31 +0000789 Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
Mohammad Samiul Islam8ce32fe2020-01-14 18:33:39 +0000790 throw new PackageManagerException(errorCode, "Failed to create/write APK session", e);
Dario Freni4b572c02019-01-29 09:40:31 +0000791 }
Dario Freni4b572c02019-01-29 09:40:31 +0000792 }
793
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100794 /**
795 * Extract apks in the given session into a new session. Returns {@code null} if there is no
796 * apks in the given session. Only parent session is returned for multi-package session.
797 */
798 @Nullable
799 private PackageInstallerSession extractApksInSession(PackageInstallerSession session,
800 boolean preReboot) throws PackageManagerException {
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100801 final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
802 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
Dario Freni4b572c02019-01-29 09:40:31 +0000803 if (!session.isMultiPackage() && !isApexSession(session)) {
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100804 return createAndWriteApkSession(session, preReboot);
Dario Freni4b572c02019-01-29 09:40:31 +0000805 } else if (session.isMultiPackage()) {
806 // For multi-package staged sessions containing APKs, we identify which child sessions
807 // contain an APK, and with those then create a new multi-package group of sessions,
808 // carrying over all the session parameters and unmarking them as staged. On commit the
809 // sessions will be installed atomically.
Jackal Guoae037922020-03-25 15:15:18 +0800810 final List<PackageInstallerSession> childSessions = new ArrayList<>();
Dario Freni4b572c02019-01-29 09:40:31 +0000811 synchronized (mStagedSessions) {
Jackal Guoae037922020-03-25 15:15:18 +0800812 final int[] childSessionIds = session.getChildSessionIds();
813 for (int id : childSessionIds) {
814 final PackageInstallerSession s = mStagedSessions.get(id);
815 if (!isApexSession(s)) {
816 childSessions.add(s);
817 }
818 }
Dario Freni4b572c02019-01-29 09:40:31 +0000819 }
820 if (childSessions.isEmpty()) {
821 // APEX-only multi-package staged session, nothing to do.
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100822 return null;
Dario Freni4b572c02019-01-29 09:40:31 +0000823 }
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100824 final PackageInstaller.SessionParams params = session.params.copy();
Dario Freni4b572c02019-01-29 09:40:31 +0000825 params.isStaged = false;
Dario Frenif141aab2019-02-04 14:48:30 +0000826 if (preReboot) {
827 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
828 }
Dario Frenic3e68ea2019-04-02 11:45:13 +0100829 // TODO(b/129744602): use the userid from the original session.
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100830 final int apkParentSessionId = mPi.createSession(
Dario Frenic3e68ea2019-04-02 11:45:13 +0100831 params, session.getInstallerPackageName(),
832 0 /* UserHandle.SYSTEM */);
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100833 final PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
Dario Freni4b572c02019-01-29 09:40:31 +0000834 try {
835 apkParentSession.open();
836 } catch (IOException e) {
837 Slog.e(TAG, "Unable to prepare multi-package session for staged session "
838 + session.sessionId);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100839 throw new PackageManagerException(errorCode,
840 "Unable to prepare multi-package session for staged session");
Dario Freni4b572c02019-01-29 09:40:31 +0000841 }
842
Jackal Guo1b333092020-03-30 11:12:56 +0800843 for (int i = 0, size = childSessions.size(); i < size; i++) {
844 final PackageInstallerSession apkChildSession = createAndWriteApkSession(
845 childSessions.get(i), preReboot);
Dario Freni09660472019-02-18 16:44:23 +0000846 try {
847 apkParentSession.addChildSessionId(apkChildSession.sessionId);
Patrick Baumann00321b72019-04-09 15:07:25 -0700848 } catch (IllegalStateException e) {
Dario Freni09660472019-02-18 16:44:23 +0000849 Slog.e(TAG, "Failed to add a child session for installing the APK files", e);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100850 throw new PackageManagerException(errorCode,
851 "Failed to add a child session " + apkChildSession.sessionId);
Dario Freni09660472019-02-18 16:44:23 +0000852 }
Dario Freni4b572c02019-01-29 09:40:31 +0000853 }
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100854 return apkParentSession;
Dario Freni4b572c02019-01-29 09:40:31 +0000855 }
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100856 return null;
857 }
858
859 private void verifyApksInSession(PackageInstallerSession session)
860 throws PackageManagerException {
861
862 final PackageInstallerSession apksToVerify = extractApksInSession(
863 session, /* preReboot */ true);
864 if (apksToVerify == null) {
865 return;
866 }
867
868 final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync(
869 (Intent result) -> {
870 int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
871 PackageInstaller.STATUS_FAILURE);
872 if (status != PackageInstaller.STATUS_SUCCESS) {
873 final String errorMessage = result.getStringExtra(
874 PackageInstaller.EXTRA_STATUS_MESSAGE);
875 Slog.e(TAG, "Failure to verify APK staged session "
876 + session.sessionId + " [" + errorMessage + "]");
877 session.setStagedSessionFailed(
878 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage);
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +0100879 mPreRebootVerificationHandler.onPreRebootVerificationComplete(
880 session.sessionId);
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100881 return;
882 }
883 mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
884 session.sessionId);
885 });
886
887 apksToVerify.commit(receiver.getIntentSender(), false);
888 }
889
890 private void installApksInSession(@NonNull PackageInstallerSession session)
891 throws PackageManagerException {
892
893 final PackageInstallerSession apksToInstall = extractApksInSession(
894 session, /* preReboot */ false);
895 if (apksToInstall == null) {
896 return;
897 }
898
899 if ((apksToInstall.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
900 // If rollback is available for this session, notify the rollback
901 // manager of the apk session so it can properly enable rollback.
902 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
903 ServiceManager.getService(Context.ROLLBACK_SERVICE));
904 try {
905 rm.notifyStagedApkSession(session.sessionId, apksToInstall.sessionId);
906 } catch (RemoteException re) {
907 Slog.e(TAG, "Failed to notifyStagedApkSession for session: "
908 + session.sessionId, re);
909 }
910 }
911
912 final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
913 apksToInstall.commit(receiver.getIntentSender(), false);
914 final Intent result = receiver.getResult();
915 final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
916 PackageInstaller.STATUS_FAILURE);
917 if (status != PackageInstaller.STATUS_SUCCESS) {
918 final String errorMessage = result.getStringExtra(
919 PackageInstaller.EXTRA_STATUS_MESSAGE);
920 Slog.e(TAG, "Failure to install APK staged session "
921 + session.sessionId + " [" + errorMessage + "]");
922 throw new PackageManagerException(
923 SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage);
924 }
Dario Freni61b3e252019-01-11 23:05:39 +0000925 }
926
Nikita Ioffe8e5b703c2019-03-14 18:40:17 +0000927 void commitSession(@NonNull PackageInstallerSession session) {
Dario Freni276cd072019-01-09 11:13:44 +0000928 updateStoredSession(session);
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100929 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
Dario Frenibe98c3f2018-12-22 15:25:27 +0000930 }
931
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +0000932 private int parentOrOwnSessionId(PackageInstallerSession session) {
933 return session.hasParentSessionId() ? session.getParentSessionId() : session.sessionId;
934 }
935
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100936 /**
937 * <p> Check if the session provided is non-overlapping with the active staged sessions.
938 *
939 * <p> A session is non-overlapping if it meets one of the following conditions: </p>
940 * <ul>
941 * <li>It is a parent session</li>
942 * <li>It is already one of the active sessions</li>
943 * <li>Its package name is not same as any of the active sessions</li>
944 * </ul>
945 * @throws PackageManagerException if session fails the check
946 */
947 void checkNonOverlappingWithStagedSessions(@NonNull PackageInstallerSession session)
948 throws PackageManagerException {
949 if (session.isMultiPackage()) {
950 // We cannot say a parent session overlaps until we process its children
951 return;
952 }
953 if (session.getPackageName() == null) {
954 throw new PackageManagerException(PackageManager.INSTALL_FAILED_INVALID_APK,
955 "Cannot stage session " + session.sessionId + " with package name null");
956 }
957
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +0000958 boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService(
959 Context.STORAGE_SERVICE)).isCheckpointSupported();
960
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000961 synchronized (mStagedSessions) {
962 for (int i = 0; i < mStagedSessions.size(); i++) {
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100963 final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +0100964 if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()
965 || stagedSession.isDestroyed()) {
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000966 continue;
967 }
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100968 if (stagedSession.isMultiPackage()) {
969 // This active parent staged session is useless as it doesn't have a package
970 // name and the session we are checking is not a parent session either.
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000971 continue;
972 }
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100973 // Check if stagedSession has an active parent session or not
974 if (stagedSession.hasParentSessionId()) {
975 int parentId = stagedSession.getParentSessionId();
976 PackageInstallerSession parentSession = mStagedSessions.get(parentId);
Mohammad Samiul Islam806ccab2020-06-19 13:26:34 +0100977 if (parentSession == null || parentSession.isStagedAndInTerminalState()
978 || parentSession.isDestroyed()) {
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100979 // Parent session has been abandoned or terminated already
980 continue;
981 }
982 }
983
Mohammad Samiul Islam806ccab2020-06-19 13:26:34 +0100984 // From here on, stagedSession is a non-parent active staged session
985
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100986 // Check if session is one of the active sessions
987 if (session.sessionId == stagedSession.sessionId) {
988 Slog.w(TAG, "Session " + session.sessionId + " is already staged");
989 continue;
990 }
991
992 // If session is not among the active sessions, then it cannot have same package
993 // name as any of the active sessions.
994 if (session.getPackageName().equals(stagedSession.getPackageName())) {
995 throw new PackageManagerException(
996 PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
997 "Package: " + session.getPackageName() + " in session: "
998 + session.sessionId + " has been staged already by session: "
999 + stagedSession.sessionId, null);
1000 }
1001
Mohammad Samiul Islamcc4c7d82019-11-05 18:18:28 +00001002 // Staging multiple root sessions is not allowed if device doesn't support
1003 // checkpoint. If session and stagedSession do not have common ancestor, they are
1004 // from two different root sessions.
1005 if (!supportsCheckpoint
1006 && parentOrOwnSessionId(session) != parentOrOwnSessionId(stagedSession)) {
1007 throw new PackageManagerException(
1008 PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
1009 "Cannot stage multiple sessions without checkpoint support", null);
1010 }
Nikita Ioffeda998cf2019-03-04 22:54:30 +00001011 }
1012 }
Nikita Ioffeda998cf2019-03-04 22:54:30 +00001013 }
1014
Dario Frenibe98c3f2018-12-22 15:25:27 +00001015 void createSession(@NonNull PackageInstallerSession sessionInfo) {
1016 synchronized (mStagedSessions) {
1017 mStagedSessions.append(sessionInfo.sessionId, sessionInfo);
1018 }
1019 }
1020
Dario Freni015f9352019-01-14 21:56:17 +00001021 void abortSession(@NonNull PackageInstallerSession session) {
Dario Frenibe98c3f2018-12-22 15:25:27 +00001022 synchronized (mStagedSessions) {
Dario Freni015f9352019-01-14 21:56:17 +00001023 mStagedSessions.remove(session.sessionId);
Oli Lanc72b0bb2019-12-02 14:03:55 +00001024 mSessionRollbackIds.delete(session.sessionId);
Dario Frenibe98c3f2018-12-22 15:25:27 +00001025 }
1026 }
Dario Freni8e7d0ec2019-01-10 15:21:40 +00001027
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001028 /**
1029 * <p>Abort committed staged session
1030 *
1031 * <p>This method must be called while holding {@link PackageInstallerSession.mLock}.
1032 *
1033 * <p>The method returns {@code false} to indicate it is not safe to clean up the session from
1034 * system yet. When it is safe, the method returns {@code true}.
1035 *
1036 * <p> When it is safe to clean up, {@link StagingManager} will call
1037 * {@link PackageInstallerSession#abandon()} on the session again.
1038 *
1039 * @return {@code true} if it is safe to cleanup the session resources, otherwise {@code false}.
1040 */
1041 boolean abortCommittedSessionLocked(@NonNull PackageInstallerSession session) {
1042 int sessionId = session.sessionId;
Nikita Ioffe1e523b52019-03-13 16:07:48 +00001043 if (session.isStagedSessionApplied()) {
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001044 Slog.w(TAG, "Cannot abort applied session : " + sessionId);
1045 return false;
shafik07205e32019-02-07 20:12:33 +00001046 }
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001047 if (!session.isDestroyed()) {
1048 throw new IllegalStateException("Committed session must be destroyed before aborting it"
1049 + " from StagingManager");
1050 }
1051 if (getStagedSession(sessionId) == null) {
1052 Slog.w(TAG, "Session " + sessionId + " has been abandoned already");
1053 return false;
1054 }
Nikita Ioffee2d52f52019-03-11 14:29:23 +00001055
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001056 // If pre-reboot verification is running, then return false. StagingManager will call
1057 // abandon again when pre-reboot verification ends.
1058 if (mPreRebootVerificationHandler.isVerificationRunning(sessionId)) {
1059 Slog.w(TAG, "Session " + sessionId + " aborted before pre-reboot "
1060 + "verification completed.");
1061 return false;
1062 }
1063
1064 // A session could be marked ready once its pre-reboot verification ends
1065 if (session.isStagedSessionReady()) {
1066 if (sessionContainsApex(session)) {
1067 try {
1068 ApexSessionInfo apexSession =
1069 mApexManager.getStagedSessionInfo(session.sessionId);
1070 if (apexSession == null || isApexSessionFinalized(apexSession)) {
1071 Slog.w(TAG,
1072 "Cannot abort session " + session.sessionId
1073 + " because it is not active.");
1074 } else {
1075 mApexManager.abortStagedSession(session.sessionId);
1076 }
1077 } catch (Exception e) {
1078 // Failed to contact apexd service. The apex might still be staged. We can still
1079 // safely cleanup the staged session since pre-reboot verification is complete.
1080 // Also, cleaning up the stageDir prevents the apex from being activated.
1081 Slog.w(TAG, "Could not contact apexd to abort staged session " + sessionId);
1082 }
Mohammad Samiul Islam44ad95b2019-11-20 15:14:36 +00001083 }
Nikita Ioffee2d52f52019-03-11 14:29:23 +00001084 }
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001085
1086 // Session was successfully aborted from apexd (if required) and pre-reboot verification
1087 // is also complete. It is now safe to clean up the session from system.
1088 abortSession(session);
1089 return true;
shafik07205e32019-02-07 20:12:33 +00001090 }
1091
Nikita Ioffee2d52f52019-03-11 14:29:23 +00001092 private boolean isApexSessionFinalized(ApexSessionInfo session) {
shafik07205e32019-02-07 20:12:33 +00001093 /* checking if the session is in a final state, i.e., not active anymore */
Nikita Ioffe82742222019-02-26 12:14:04 +00001094 return session.isUnknown || session.isActivationFailed || session.isSuccess
Mohammad Samiul Islam2dfc86a2019-11-20 13:53:07 +00001095 || session.isReverted;
Nikita Ioffe82742222019-02-26 12:14:04 +00001096 }
1097
1098 private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) {
Mohammad Samiul Islam2dfc86a2019-11-20 13:53:07 +00001099 // isRevertInProgress is included to cover the scenario, when a device is rebooted
1100 // during the revert, and apexd fails to resume the revert after reboot.
Nikita Ioffe82742222019-02-26 12:14:04 +00001101 return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown
Mohammad Samiul Islam2dfc86a2019-11-20 13:53:07 +00001102 || apexSessionInfo.isReverted || apexSessionInfo.isRevertInProgress
1103 || apexSessionInfo.isRevertFailed;
shafik07205e32019-02-07 20:12:33 +00001104 }
1105
Dario Freni015f9352019-01-14 21:56:17 +00001106 @GuardedBy("mStagedSessions")
1107 private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) {
1108 // This method assumes that the argument is either a parent session of a multi-package
1109 // i.e. isMultiPackage() returns true, or that it is a child session, i.e.
1110 // hasParentSessionId() returns true.
1111 if (session.isMultiPackage()) {
1112 // Parent session of a multi-package group. Check that we restored all the children.
1113 for (int childSession : session.getChildSessionIds()) {
1114 if (mStagedSessions.get(childSession) == null) {
1115 return false;
1116 }
1117 }
1118 return true;
1119 }
1120 if (session.hasParentSessionId()) {
1121 PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId());
1122 if (parent == null) {
1123 return false;
1124 }
1125 return isMultiPackageSessionComplete(parent);
1126 }
1127 Slog.wtf(TAG, "Attempting to restore an invalid multi-package session.");
1128 return false;
1129 }
1130
Rhed Jao1fc8b362020-01-16 18:38:17 +08001131 void restoreSession(@NonNull PackageInstallerSession session, boolean isDeviceUpgrading) {
Dario Freni015f9352019-01-14 21:56:17 +00001132 PackageInstallerSession sessionToResume = session;
1133 synchronized (mStagedSessions) {
1134 mStagedSessions.append(session.sessionId, session);
1135 // For multi-package sessions, we don't know in which order they will be restored. We
1136 // need to wait until we have restored all the session in a group before restoring them.
1137 if (session.isMultiPackage() || session.hasParentSessionId()) {
1138 if (!isMultiPackageSessionComplete(session)) {
1139 // Still haven't recovered all sessions of the group, return.
1140 return;
1141 }
1142 // Group recovered, find the parent if necessary and resume the installation.
1143 if (session.hasParentSessionId()) {
1144 sessionToResume = mStagedSessions.get(session.getParentSessionId());
1145 }
1146 }
1147 }
Rhed Jao1fc8b362020-01-16 18:38:17 +08001148 // The preconditions used during pre-reboot verification might have changed when device
1149 // is upgrading. Updated staged sessions to activation failed before we resume the session.
1150 if (isDeviceUpgrading && !sessionToResume.isStagedAndInTerminalState()) {
1151 sessionToResume.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
1152 "Build fingerprint has changed");
1153 return;
1154 }
Dario Freni015f9352019-01-14 21:56:17 +00001155 checkStateAndResume(sessionToResume);
1156 }
1157
1158 private void checkStateAndResume(@NonNull PackageInstallerSession session) {
Mohammad Samiul Islama9225312020-06-10 18:00:24 +01001159 // Do not resume session if boot completed already
1160 if (SystemProperties.getBoolean("sys.boot_completed", false)) {
1161 return;
1162 }
1163
Dario Freni47799f42019-03-13 18:06:24 +00001164 if (!session.isCommitted()) {
1165 // Session hasn't been committed yet, ignore.
1166 return;
1167 }
Dario Freni61b3e252019-01-11 23:05:39 +00001168 // Check the state of the session and decide what to do next.
1169 if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
1170 // Final states, nothing to do.
1171 return;
1172 }
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001173 if (session.isDestroyed()) {
1174 // Device rebooted before abandoned session was cleaned up.
1175 session.abandon();
1176 return;
1177 }
Dario Freni61b3e252019-01-11 23:05:39 +00001178 if (!session.isStagedSessionReady()) {
1179 // The framework got restarted before the pre-reboot verification could complete,
1180 // restart the verification.
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001181 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
Dario Freni61b3e252019-01-11 23:05:39 +00001182 } else {
1183 // Session had already being marked ready. Start the checks to verify if there is any
1184 // follow-up work.
Dario Freni4df010e2019-01-31 19:34:46 +00001185 resumeSession(session);
Dario Freni61b3e252019-01-11 23:05:39 +00001186 }
Dario Freni8e7d0ec2019-01-10 15:21:40 +00001187 }
Dario Freni4b572c02019-01-29 09:40:31 +00001188
Gavin Corkery333136c2020-02-03 19:03:43 +00001189 private void logFailedApexSessionsIfNecessary() {
1190 synchronized (mFailedPackageNames) {
1191 if (!mFailedPackageNames.isEmpty()) {
1192 WatchdogRollbackLogger.logApexdRevert(mContext,
1193 mFailedPackageNames, mNativeFailureReason);
1194 }
1195 }
1196 }
1197
Gavin Corkery24b4ed52020-04-29 16:30:10 +01001198 void markStagedSessionsAsSuccessful() {
1199 synchronized (mSuccessfulStagedSessionIds) {
1200 for (int i = 0; i < mSuccessfulStagedSessionIds.size(); i++) {
1201 mApexManager.markStagedSessionSuccessful(mSuccessfulStagedSessionIds.get(i));
1202 }
1203 }
1204 }
1205
Rhed Jaob793c212019-10-30 20:54:37 +08001206 void systemReady() {
Gavin Corkery24b4ed52020-04-29 16:30:10 +01001207 new Lifecycle(mContext).startService(this);
Rhed Jaob793c212019-10-30 20:54:37 +08001208 // Register the receiver of boot completed intent for staging manager.
1209 mContext.registerReceiver(new BroadcastReceiver() {
1210 @Override
1211 public void onReceive(Context ctx, Intent intent) {
1212 mPreRebootVerificationHandler.readyToStart();
Gavin Corkery333136c2020-02-03 19:03:43 +00001213 BackgroundThread.getExecutor().execute(
1214 () -> logFailedApexSessionsIfNecessary());
Rhed Jaob793c212019-10-30 20:54:37 +08001215 ctx.unregisterReceiver(this);
1216 }
1217 }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
Mohammad Samiul Islam992b6372020-05-07 12:27:54 +01001218
1219 mFailureReasonFile.delete();
Rhed Jaob793c212019-10-30 20:54:37 +08001220 }
1221
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001222 private static class LocalIntentReceiverAsync {
1223 final Consumer<Intent> mConsumer;
1224
1225 LocalIntentReceiverAsync(Consumer<Intent> consumer) {
1226 mConsumer = consumer;
1227 }
1228
1229 private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
1230 @Override
1231 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
1232 IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
1233 mConsumer.accept(intent);
1234 }
1235 };
1236
1237 public IntentSender getIntentSender() {
1238 return new IntentSender((IIntentSender) mLocalSender);
1239 }
1240 }
1241
1242 private static class LocalIntentReceiverSync {
Dario Freni4b572c02019-01-29 09:40:31 +00001243 private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
1244
1245 private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
1246 @Override
1247 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001248 IIntentReceiver finishedReceiver, String requiredPermission,
1249 Bundle options) {
Dario Freni4b572c02019-01-29 09:40:31 +00001250 try {
1251 mResult.offer(intent, 5, TimeUnit.SECONDS);
1252 } catch (InterruptedException e) {
1253 throw new RuntimeException(e);
1254 }
1255 }
1256 };
1257
1258 public IntentSender getIntentSender() {
1259 return new IntentSender((IIntentSender) mLocalSender);
1260 }
1261
1262 public Intent getResult() {
1263 try {
1264 return mResult.take();
1265 } catch (InterruptedException e) {
1266 throw new RuntimeException(e);
1267 }
1268 }
1269 }
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001270
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001271 private PackageInstallerSession getStagedSession(int sessionId) {
1272 PackageInstallerSession session;
1273 synchronized (mStagedSessions) {
1274 session = mStagedSessions.get(sessionId);
1275 }
1276 return session;
1277 }
1278
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001279 private final class PreRebootVerificationHandler extends Handler {
Rhed Jaob793c212019-10-30 20:54:37 +08001280 // Hold session ids before handler gets ready to do the verification.
1281 private IntArray mPendingSessionIds;
1282 private boolean mIsReady;
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001283 @GuardedBy("mVerificationRunning")
1284 private final SparseBooleanArray mVerificationRunning = new SparseBooleanArray();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001285
1286 PreRebootVerificationHandler(Looper looper) {
1287 super(looper);
1288 }
1289
1290 /**
1291 * Handler for states of pre reboot verification. The states are arranged linearly (shown
1292 * below) with each state either calling the next state, or calling some other method that
1293 * eventually calls the next state.
1294 *
1295 * <p><ul>
1296 * <li>MSG_PRE_REBOOT_VERIFICATION_START</li>
1297 * <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li>
1298 * <li>MSG_PRE_REBOOT_VERIFICATION_APK</li>
1299 * <li>MSG_PRE_REBOOT_VERIFICATION_END</li>
1300 * </ul></p>
1301 *
1302 * Details about each of state can be found in corresponding handler of node.
1303 */
1304 private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
1305 private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
1306 private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3;
1307 private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4;
1308
1309 @Override
1310 public void handleMessage(Message msg) {
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001311 final int sessionId = msg.arg1;
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001312 final PackageInstallerSession session = getStagedSession(sessionId);
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001313 if (session == null) {
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001314 Slog.wtf(TAG, "Session disappeared in the middle of pre-reboot verification: "
1315 + sessionId);
1316 return;
1317 }
1318 if (session.isDestroyed()) {
1319 // No point in running verification on a destroyed session
1320 onPreRebootVerificationComplete(sessionId);
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001321 return;
1322 }
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001323 switch (msg.what) {
1324 case MSG_PRE_REBOOT_VERIFICATION_START:
1325 handlePreRebootVerification_Start(session);
1326 break;
1327 case MSG_PRE_REBOOT_VERIFICATION_APEX:
1328 handlePreRebootVerification_Apex(session);
1329 break;
1330 case MSG_PRE_REBOOT_VERIFICATION_APK:
1331 handlePreRebootVerification_Apk(session);
1332 break;
1333 case MSG_PRE_REBOOT_VERIFICATION_END:
1334 handlePreRebootVerification_End(session);
1335 break;
1336 }
1337 }
1338
Rhed Jaob793c212019-10-30 20:54:37 +08001339 // Notify the handler that system is ready, and reschedule the pre-reboot verifications.
1340 private synchronized void readyToStart() {
1341 mIsReady = true;
1342 if (mPendingSessionIds != null) {
1343 for (int i = 0; i < mPendingSessionIds.size(); i++) {
1344 startPreRebootVerification(mPendingSessionIds.get(i));
1345 }
1346 mPendingSessionIds = null;
1347 }
1348 }
1349
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001350 // Method for starting the pre-reboot verification
Rhed Jaob793c212019-10-30 20:54:37 +08001351 private synchronized void startPreRebootVerification(int sessionId) {
1352 if (!mIsReady) {
1353 if (mPendingSessionIds == null) {
1354 mPendingSessionIds = new IntArray();
1355 }
1356 mPendingSessionIds.add(sessionId);
1357 return;
1358 }
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001359
1360 PackageInstallerSession session = getStagedSession(sessionId);
1361 synchronized (mVerificationRunning) {
1362 // Do not start verification on a session that has been abandoned
1363 if (session == null || session.isDestroyed()) {
1364 return;
1365 }
1366 Slog.d(TAG, "Starting preRebootVerification for session " + sessionId);
1367 mVerificationRunning.put(sessionId, true);
1368 }
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001369 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001370 }
1371
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001372 // Things to do when pre-reboot verification completes for a particular sessionId
1373 private void onPreRebootVerificationComplete(int sessionId) {
1374 // Remove it from mVerificationRunning so that verification is considered complete
1375 synchronized (mVerificationRunning) {
1376 Slog.d(TAG, "Stopping preRebootVerification for session " + sessionId);
1377 mVerificationRunning.delete(sessionId);
1378 }
1379 // Check if the session was destroyed while pre-reboot verification was running. If so,
1380 // abandon it again.
1381 PackageInstallerSession session = getStagedSession(sessionId);
1382 if (session != null && session.isDestroyed()) {
1383 session.abandon();
1384 }
1385 }
1386
1387 private boolean isVerificationRunning(int sessionId) {
1388 synchronized (mVerificationRunning) {
1389 return mVerificationRunning.get(sessionId);
1390 }
1391 }
1392
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001393 private void notifyPreRebootVerification_Start_Complete(int sessionId) {
1394 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001395 }
1396
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001397 private void notifyPreRebootVerification_Apex_Complete(int sessionId) {
1398 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001399 }
1400
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001401 private void notifyPreRebootVerification_Apk_Complete(int sessionId) {
1402 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001403 }
1404
1405 /**
1406 * A dummy state for starting the pre reboot verification.
1407 *
1408 * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification
1409 */
1410 private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
Oli Lanc72b0bb2019-12-02 14:03:55 +00001411 if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
1412 // If rollback is enabled for this session, we call through to the RollbackManager
1413 // with the list of sessions it must enable rollback for. Note that
1414 // notifyStagedSession is a synchronous operation.
1415 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
1416 ServiceManager.getService(Context.ROLLBACK_SERVICE));
1417 try {
1418 // NOTE: To stay consistent with the non-staged install flow, we don't fail the
1419 // entire install if rollbacks can't be enabled.
1420 int rollbackId = rm.notifyStagedSession(session.sessionId);
1421 if (rollbackId != -1) {
1422 synchronized (mStagedSessions) {
1423 mSessionRollbackIds.put(session.sessionId, rollbackId);
1424 }
1425 }
1426 } catch (RemoteException re) {
1427 Slog.e(TAG, "Failed to notifyStagedSession for session: "
1428 + session.sessionId, re);
1429 }
1430 }
1431
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001432 notifyPreRebootVerification_Start_Complete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001433 }
1434
1435 /**
1436 * Pre-reboot verification state for apex files:
1437 *
1438 * <p><ul>
1439 * <li>submits session to apex service</li>
1440 * <li>validates signatures of apex files</li>
1441 * </ul></p>
1442 */
1443 private void handlePreRebootVerification_Apex(@NonNull PackageInstallerSession session) {
1444 final boolean hasApex = sessionContainsApex(session);
1445
1446 // APEX checks. For single-package sessions, check if they contain an APEX. For
1447 // multi-package sessions, find all the child sessions that contain an APEX.
1448 if (hasApex) {
Jackal Guo63aa98d2020-04-14 16:47:32 +08001449 final List<PackageInfo> apexPackages;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001450 try {
Jackal Guo63aa98d2020-04-14 16:47:32 +08001451 apexPackages = submitSessionToApexService(session);
Jackal Guo1b333092020-03-30 11:12:56 +08001452 for (int i = 0, size = apexPackages.size(); i < size; i++) {
1453 validateApexSignature(apexPackages.get(i));
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001454 }
1455 } catch (PackageManagerException e) {
1456 session.setStagedSessionFailed(e.error, e.getMessage());
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001457 onPreRebootVerificationComplete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001458 return;
1459 }
Jackal Guo63aa98d2020-04-14 16:47:32 +08001460
1461 final PackageManagerInternal packageManagerInternal =
1462 LocalServices.getService(PackageManagerInternal.class);
1463 packageManagerInternal.pruneCachedApksInApex(apexPackages);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001464 }
1465
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001466 notifyPreRebootVerification_Apex_Complete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001467 }
1468
1469 /**
1470 * Pre-reboot verification state for apk files:
1471 * <p><ul>
1472 * <li>performs a dry-run install of apk</li>
1473 * </ul></p>
1474 */
1475 private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) {
1476 if (!sessionContainsApk(session)) {
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +01001477 notifyPreRebootVerification_Apk_Complete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001478 return;
1479 }
1480
1481 try {
1482 Slog.d(TAG, "Running a pre-reboot verification for APKs in session "
1483 + session.sessionId + " by performing a dry-run install");
1484
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +01001485 // verifyApksInSession will notify the handler when APK verification is complete
1486 verifyApksInSession(session);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001487 // TODO(b/118865310): abort the session on apexd.
1488 } catch (PackageManagerException e) {
1489 session.setStagedSessionFailed(e.error, e.getMessage());
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001490 onPreRebootVerificationComplete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001491 }
1492 }
1493
1494 /**
1495 * Pre-reboot verification state for wrapping up:
1496 * <p><ul>
1497 * <li>enables rollback if required</li>
1498 * <li>marks session as ready</li>
1499 * </ul></p>
1500 */
1501 private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) {
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +00001502 // Before marking the session as ready, start checkpoint service if available
1503 try {
1504 IStorageManager storageManager = PackageHelper.getStorageManager();
1505 if (storageManager.supportsCheckpoint()) {
Gavin Corkeryab2544c2020-02-04 08:50:53 +00001506 storageManager.startCheckpoint(2);
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +00001507 }
1508 } catch (Exception e) {
1509 // Failed to get hold of StorageManager
1510 Slog.e(TAG, "Failed to get hold of StorageManager", e);
1511 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
1512 "Failed to get hold of StorageManager");
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001513 onPreRebootVerificationComplete(session.sessionId);
Mohammad Samiul Islam7e9fdb02019-11-28 18:14:40 +00001514 return;
1515 }
1516
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001517 // Stop pre-reboot verification before marking session ready. From this point on, if we
1518 // abandon the session then it will be cleaned up immediately. If session is abandoned
1519 // after this point, then even if for some reason system tries to install the session
1520 // or activate its apex, there won't be any files to work with as they will be cleaned
1521 // up by the system as part of abandonment. If session is abandoned before this point,
1522 // then the session is already destroyed and cannot be marked ready anymore.
1523 onPreRebootVerificationComplete(session.sessionId);
1524
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001525 // Proactively mark session as ready before calling apexd. Although this call order
1526 // looks counter-intuitive, this is the easiest way to ensure that session won't end up
1527 // in the inconsistent state:
1528 // - If device gets rebooted right before call to apexd, then apexd will never activate
1529 // apex files of this staged session. This will result in StagingManager failing
1530 // the session.
1531 // On the other hand, if the order of the calls was inverted (first call apexd, then
1532 // mark session as ready), then if a device gets rebooted right after the call to apexd,
1533 // only apex part of the train will be applied, leaving device in an inconsistent state.
1534 Slog.d(TAG, "Marking session " + session.sessionId + " as ready");
1535 session.setStagedSessionReady();
Mohammad Samiul Islama0623e22020-04-24 09:26:19 +01001536 if (session.isStagedSessionReady()) {
1537 final boolean hasApex = sessionContainsApex(session);
1538 if (hasApex) {
1539 try {
1540 mApexManager.markStagedSessionReady(session.sessionId);
1541 } catch (PackageManagerException e) {
1542 session.setStagedSessionFailed(e.error, e.getMessage());
1543 return;
1544 }
1545 }
Mohammad Samiul Islamef400272019-08-15 16:44:57 +01001546 }
1547 }
1548 }
Dario Frenibe98c3f2018-12-22 15:25:27 +00001549}