blob: 3390ca780e7d958a46c9a677732d0d781e4442b4 [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;
Narayan Kamathfcd4a042019-02-01 14:16:37 +000024import android.content.Context;
Dario Freni4b572c02019-01-29 09:40:31 +000025import android.content.IIntentReceiver;
26import android.content.IIntentSender;
27import android.content.Intent;
28import android.content.IntentSender;
Dario Freni2e8dffc2019-02-06 14:55:16 +000029import android.content.pm.PackageInfo;
Dario Frenibe98c3f2018-12-22 15:25:27 +000030import android.content.pm.PackageInstaller;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000031import android.content.pm.PackageInstaller.SessionInfo;
32import android.content.pm.PackageManager;
Nikita Ioffee0dbc982019-05-09 19:33:35 +010033import android.content.pm.PackageParser;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000034import android.content.pm.PackageParser.PackageParserException;
35import android.content.pm.PackageParser.SigningDetails;
36import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
Dario Frenibe98c3f2018-12-22 15:25:27 +000037import android.content.pm.ParceledListSlice;
Narayan Kamathfcd4a042019-02-01 14:16:37 +000038import android.content.rollback.IRollbackManager;
Dario Freni4b572c02019-01-29 09:40:31 +000039import android.os.Bundle;
Narayan Kamath94c93ab2019-01-04 10:47:00 +000040import android.os.Handler;
Dario Freni4b572c02019-01-29 09:40:31 +000041import android.os.IBinder;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +010042import android.os.Looper;
43import android.os.Message;
Dario Freni4b572c02019-01-29 09:40:31 +000044import android.os.ParcelFileDescriptor;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000045import android.os.PowerManager;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000046import android.os.RemoteException;
47import android.os.ServiceManager;
Nikita Ioffee0dbc982019-05-09 19:33:35 +010048import android.util.IntArray;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000049import android.util.Slog;
Dario Frenibe98c3f2018-12-22 15:25:27 +000050import android.util.SparseArray;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000051import android.util.apk.ApkSignatureVerifier;
Dario Frenibe98c3f2018-12-22 15:25:27 +000052
53import com.android.internal.annotations.GuardedBy;
Narayan Kamath94c93ab2019-01-04 10:47:00 +000054import com.android.internal.os.BackgroundThread;
Dario Frenibe98c3f2018-12-22 15:25:27 +000055
Dario Freni4b572c02019-01-29 09:40:31 +000056import java.io.File;
57import java.io.IOException;
Dario Frenibe98c3f2018-12-22 15:25:27 +000058import java.util.ArrayList;
Dario Freni015f9352019-01-14 21:56:17 +000059import java.util.Arrays;
Dario Frenibe98c3f2018-12-22 15:25:27 +000060import java.util.List;
Dario Freni4b572c02019-01-29 09:40:31 +000061import java.util.concurrent.LinkedBlockingQueue;
62import java.util.concurrent.TimeUnit;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +010063import java.util.function.Consumer;
Dario Frenif141aab2019-02-04 14:48:30 +000064import java.util.function.Predicate;
Dario Freni015f9352019-01-14 21:56:17 +000065import java.util.stream.Collectors;
Dario Frenibe98c3f2018-12-22 15:25:27 +000066
67/**
68 * This class handles staged install sessions, i.e. install sessions that require packages to
69 * be installed only after a reboot.
70 */
71public class StagingManager {
72
73 private static final String TAG = "StagingManager";
74
Dario Freni4b572c02019-01-29 09:40:31 +000075 private final PackageInstallerService mPi;
Dario Freni2e8dffc2019-02-06 14:55:16 +000076 private final ApexManager mApexManager;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000077 private final PowerManager mPowerManager;
Mohammad Samiul Islamef400272019-08-15 16:44:57 +010078 private final PreRebootVerificationHandler mPreRebootVerificationHandler;
Dario Frenibe98c3f2018-12-22 15:25:27 +000079
Dario Frenibe98c3f2018-12-22 15:25:27 +000080 @GuardedBy("mStagedSessions")
81 private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
82
Nikita Ioffe1a7965e2019-03-12 22:32:41 +000083 StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
Dario Freni4b572c02019-01-29 09:40:31 +000084 mPi = pi;
Dario Freni2e8dffc2019-02-06 14:55:16 +000085 mApexManager = am;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000086 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +010087 mPreRebootVerificationHandler = new PreRebootVerificationHandler(
88 BackgroundThread.get().getLooper());
Dario Frenibe98c3f2018-12-22 15:25:27 +000089 }
90
91 private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
92 synchronized (mStagedSessions) {
93 PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId);
Narayan Kamath94c93ab2019-01-04 10:47:00 +000094 // storedSession might be null if a call to abortSession was made before the session
95 // is updated.
96 if (storedSession != null) {
97 mStagedSessions.put(sessionInfo.sessionId, sessionInfo);
Dario Frenibe98c3f2018-12-22 15:25:27 +000098 }
Dario Frenibe98c3f2018-12-22 15:25:27 +000099 }
100 }
101
102 ParceledListSlice<PackageInstaller.SessionInfo> getSessions() {
103 final List<PackageInstaller.SessionInfo> result = new ArrayList<>();
104 synchronized (mStagedSessions) {
105 for (int i = 0; i < mStagedSessions.size(); i++) {
106 result.add(mStagedSessions.valueAt(i).generateInfo(false));
107 }
108 }
109 return new ParceledListSlice<>(result);
110 }
111
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100112 /**
113 * Validates the signature used to sign the container of the new apex package
114 *
115 * @param newApexPkg The new apex package that is being installed
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100116 * @throws PackageManagerException
117 */
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000118 private void validateApexSignature(PackageInfo newApexPkg)
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100119 throws PackageManagerException {
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100120 // Get signing details of the new package
121 final String apexPath = newApexPkg.applicationInfo.sourceDir;
122 final String packageName = newApexPkg.packageName;
123
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000124 final SigningDetails newSigningDetails;
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000125 try {
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000126 newSigningDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000127 } catch (PackageParserException e) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100128 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
129 "Failed to parse APEX package " + apexPath, e);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000130 }
131
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100132 // Get signing details of the existing package
133 final PackageInfo existingApexPkg = mApexManager.getPackageInfo(packageName,
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100134 ApexManager.MATCH_ACTIVE_PACKAGE);
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100135 if (existingApexPkg == null) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100136 // This should never happen, because submitSessionToApexService ensures that no new
137 // apexes were installed.
138 throw new IllegalStateException("Unknown apex package " + packageName);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000139 }
140
141 final SigningDetails existingSigningDetails;
142 try {
143 existingSigningDetails = ApkSignatureVerifier.verify(
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100144 existingApexPkg.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000145 } catch (PackageParserException e) {
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100146 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100147 "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir, e);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000148 }
149
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100150 // Verify signing details for upgrade
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000151 if (newSigningDetails.checkCapability(existingSigningDetails,
152 SigningDetails.CertCapabilities.INSTALLED_DATA)
153 || existingSigningDetails.checkCapability(newSigningDetails,
154 SigningDetails.CertCapabilities.ROLLBACK)) {
Mohammad Samiul Islam8e35db12019-08-22 15:43:55 +0100155 return;
156 }
157
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100158 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Mohammad Samiul Islamb8841242019-07-29 14:38:38 +0100159 "APK-container signature of APEX package " + packageName + " with version "
160 + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not"
161 + " compatible with the one currently installed on device");
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000162 }
163
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100164 private List<PackageInfo> submitSessionToApexService(
165 @NonNull PackageInstallerSession session) throws PackageManagerException {
166 final IntArray childSessionsIds = new IntArray();
167 if (session.isMultiPackage()) {
168 for (int id : session.getChildSessionIds()) {
169 if (isApexSession(mStagedSessions.get(id))) {
170 childSessionsIds.add(id);
171 }
Nikita Ioffe8bbb8152019-02-21 15:54:54 +0000172 }
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100173 }
Nikita Ioffe4e7d24a2019-07-05 15:49:45 +0100174 // submitStagedSession will throw a PackageManagerException if apexd verification fails,
175 // which will be propagated to populate stagedSessionErrorMessage of this session.
176 final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId,
177 childSessionsIds.toArray());
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100178 final List<PackageInfo> result = new ArrayList<>();
Oli Lanc2c7a222019-07-31 15:27:22 +0100179 for (ApexInfo apexInfo : apexInfoList.apexInfos) {
180 final PackageInfo packageInfo;
181 int flags = PackageManager.GET_META_DATA;
182 PackageParser.Package pkg;
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100183 try {
Oli Lanc2c7a222019-07-31 15:27:22 +0100184 File apexFile = new File(apexInfo.modulePath);
185 PackageParser pp = new PackageParser();
186 pkg = pp.parsePackage(apexFile, flags, false);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100187 } catch (PackageParserException e) {
188 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Oli Lanc2c7a222019-07-31 15:27:22 +0100189 "Failed to parse APEX package " + apexInfo.modulePath, e);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100190 }
Oli Lanc2c7a222019-07-31 15:27:22 +0100191 packageInfo = PackageParser.generatePackageInfo(pkg, apexInfo, flags);
192 final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName,
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100193 ApexManager.MATCH_ACTIVE_PACKAGE);
194 if (activePackage == null) {
Oli Lanc2c7a222019-07-31 15:27:22 +0100195 Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100196 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
197 "It is forbidden to install new APEX packages.");
198 }
199 checkRequiredVersionCode(session, activePackage);
Oli Lanc2c7a222019-07-31 15:27:22 +0100200 checkDowngrade(session, activePackage, packageInfo);
201 result.add(packageInfo);
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100202 }
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100203 Slog.d(TAG, "Session " + session.sessionId + " has following APEX packages: ["
204 + result.stream().map(p -> p.packageName).collect(Collectors.joining(",")) + "]");
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100205 return result;
206 }
207
208 private void checkRequiredVersionCode(final PackageInstallerSession session,
209 final PackageInfo activePackage) throws PackageManagerException {
210 if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) {
211 return;
212 }
213 final long activeVersion = activePackage.applicationInfo.longVersionCode;
214 if (activeVersion != session.params.requiredInstalledVersionCode) {
215 if (!mApexManager.abortActiveSession()) {
216 Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
217 }
218 throw new PackageManagerException(
219 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
220 "Installed version of APEX package " + activePackage.packageName
Richard Uhler2124d4b2019-04-25 13:01:39 +0100221 + " does not match required. Active version: " + activeVersion
222 + " required: " + session.params.requiredInstalledVersionCode);
Nikita Ioffe8bbb8152019-02-21 15:54:54 +0000223 }
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100224 }
225
226 private void checkDowngrade(final PackageInstallerSession session,
227 final PackageInfo activePackage, final PackageInfo newPackage)
228 throws PackageManagerException {
229 final long activeVersion = activePackage.applicationInfo.longVersionCode;
230 final long newVersionCode = newPackage.applicationInfo.longVersionCode;
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100231 final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
Nikita Ioffee0dbc982019-05-09 19:33:35 +0100232 session.params.installFlags, activePackage.applicationInfo.flags);
233 if (activeVersion > newVersionCode && !allowsDowngrade) {
234 if (!mApexManager.abortActiveSession()) {
235 Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
236 }
237 throw new PackageManagerException(
238 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
239 "Downgrade of APEX package " + newPackage.packageName
240 + " is not allowed. Active version: " + activeVersion
241 + " attempted: " + newVersionCode);
242 }
Dario Freni015f9352019-01-14 21:56:17 +0000243 }
244
Dario Freni015f9352019-01-14 21:56:17 +0000245 private static boolean isApexSession(@NonNull PackageInstallerSession session) {
246 return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
247 }
248
Dario Frenif141aab2019-02-04 14:48:30 +0000249 private boolean sessionContains(@NonNull PackageInstallerSession session,
250 Predicate<PackageInstallerSession> filter) {
Dario Freni4b572c02019-01-29 09:40:31 +0000251 if (!session.isMultiPackage()) {
Dario Frenif141aab2019-02-04 14:48:30 +0000252 return filter.test(session);
Dario Freni4b572c02019-01-29 09:40:31 +0000253 }
254 synchronized (mStagedSessions) {
255 return !(Arrays.stream(session.getChildSessionIds())
256 // Retrieve cached sessions matching ids.
257 .mapToObj(i -> mStagedSessions.get(i))
258 // Filter only the ones containing APEX.
Dario Frenif141aab2019-02-04 14:48:30 +0000259 .filter(childSession -> filter.test(childSession))
Dario Freni4b572c02019-01-29 09:40:31 +0000260 .collect(Collectors.toList())
261 .isEmpty());
262 }
263 }
264
Dario Frenif141aab2019-02-04 14:48:30 +0000265 private boolean sessionContainsApex(@NonNull PackageInstallerSession session) {
266 return sessionContains(session, (s) -> isApexSession(s));
267 }
268
269 private boolean sessionContainsApk(@NonNull PackageInstallerSession session) {
270 return sessionContains(session, (s) -> !isApexSession(s));
271 }
272
Dario Freni61b3e252019-01-11 23:05:39 +0000273 private void resumeSession(@NonNull PackageInstallerSession session) {
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100274 Slog.d(TAG, "Resuming session " + session.sessionId);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100275 final boolean hasApex = sessionContainsApex(session);
Nikita Ioffea820bd92019-02-15 14:22:44 +0000276 if (hasApex) {
Dario Freni2e8dffc2019-02-06 14:55:16 +0000277 // Check with apexservice whether the apex packages have been activated.
278 ApexSessionInfo apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
279 if (apexSessionInfo == null) {
280 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
281 "apexd did not know anything about a staged session supposed to be"
282 + "activated");
Dario Freni4b572c02019-01-29 09:40:31 +0000283 return;
284 }
Nikita Ioffe82742222019-02-26 12:14:04 +0000285 if (isApexSessionFailed(apexSessionInfo)) {
Dario Frenib6d28962019-01-31 15:52:24 +0000286 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
Dario Freni4b572c02019-01-29 09:40:31 +0000287 "APEX activation failed. Check logcat messages from apexd for "
288 + "more information.");
289 return;
290 }
291 if (apexSessionInfo.isVerified) {
292 // Session has been previously submitted to apexd, but didn't complete all the
293 // pre-reboot verification, perhaps because the device rebooted in the meantime.
294 // Greedily re-trigger the pre-reboot verification.
295 Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to be "
296 + "verified, resuming pre-reboot verification");
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100297 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
Dario Freni4b572c02019-01-29 09:40:31 +0000298 return;
299 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000300 if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
Dario Freni4b572c02019-01-29 09:40:31 +0000301 // In all the remaining cases apexd will try to apply the session again at next
302 // boot. Nothing to do here for now.
303 Slog.w(TAG, "Staged session " + session.sessionId + " scheduled to be applied "
304 + "at boot didn't activate nor fail. This usually means that apexd will "
305 + "retry at next reboot.");
306 return;
307 }
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100308 Slog.i(TAG, "APEX packages in session " + session.sessionId
309 + " were successfully activated. Proceeding with APK packages, if any");
Dario Freni4b572c02019-01-29 09:40:31 +0000310 }
311 // The APEX part of the session is activated, proceed with the installation of APKs.
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100312 try {
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100313 Slog.d(TAG, "Installing APK packages in session " + session.sessionId);
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100314 installApksInSession(session);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100315 } catch (PackageManagerException e) {
316 session.setStagedSessionFailed(e.error, e.getMessage());
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000317
318 if (!hasApex) {
319 return;
320 }
321
322 if (!mApexManager.abortActiveSession()) {
323 Slog.e(TAG, "Failed to abort APEXd session");
324 } else {
325 Slog.e(TAG,
326 "Successfully aborted apexd session. Rebooting device in order to revert "
327 + "to the previous state of APEXd.");
328 mPowerManager.reboot(null);
329 }
Dario Freni61b3e252019-01-11 23:05:39 +0000330 return;
331 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000332
Nikita Ioffef2c2e012019-07-23 17:48:38 +0100333 Slog.d(TAG, "Marking session " + session.sessionId + " as applied");
Dario Freni4b572c02019-01-29 09:40:31 +0000334 session.setStagedSessionApplied();
Nikita Ioffea820bd92019-02-15 14:22:44 +0000335 if (hasApex) {
336 mApexManager.markStagedSessionSuccessful(session.sessionId);
337 }
Dario Freni4b572c02019-01-29 09:40:31 +0000338 }
339
Dario Freni815bd212019-02-20 14:09:36 +0000340 private List<String> findAPKsInDir(File stageDir) {
341 List<String> ret = new ArrayList<>();
Dario Freni4b572c02019-01-29 09:40:31 +0000342 if (stageDir != null && stageDir.exists()) {
343 for (File file : stageDir.listFiles()) {
344 if (file.getAbsolutePath().toLowerCase().endsWith(".apk")) {
Dario Freni815bd212019-02-20 14:09:36 +0000345 ret.add(file.getAbsolutePath());
Dario Freni4b572c02019-01-29 09:40:31 +0000346 }
347 }
Dario Freni61b3e252019-01-11 23:05:39 +0000348 }
Dario Freni815bd212019-02-20 14:09:36 +0000349 return ret;
Dario Freni4b572c02019-01-29 09:40:31 +0000350 }
351
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100352 @NonNull
Dario Freni4b572c02019-01-29 09:40:31 +0000353 private PackageInstallerSession createAndWriteApkSession(
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100354 @NonNull PackageInstallerSession originalSession, boolean preReboot)
355 throws PackageManagerException {
356 final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
357 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
Dario Freni4b572c02019-01-29 09:40:31 +0000358 if (originalSession.stageDir == null) {
359 Slog.wtf(TAG, "Attempting to install a staged APK session with no staging dir");
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100360 throw new PackageManagerException(errorCode,
361 "Attempting to install a staged APK session with no staging dir");
Dario Freni1473bcb2019-01-25 14:27:13 +0000362 }
Dario Freni815bd212019-02-20 14:09:36 +0000363 List<String> apkFilePaths = findAPKsInDir(originalSession.stageDir);
364 if (apkFilePaths.isEmpty()) {
Dario Freni4b572c02019-01-29 09:40:31 +0000365 Slog.w(TAG, "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100366 throw new PackageManagerException(errorCode,
367 "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
Dario Freni61b3e252019-01-11 23:05:39 +0000368 }
Dario Freni4b572c02019-01-29 09:40:31 +0000369
370 PackageInstaller.SessionParams params = originalSession.params.copy();
371 params.isStaged = false;
Dario Frenia2afebf2019-05-01 16:03:17 +0100372 params.installFlags |= PackageManager.INSTALL_STAGED;
Dario Frenic3e68ea2019-04-02 11:45:13 +0100373 // TODO(b/129744602): use the userid from the original session.
Dario Frenif141aab2019-02-04 14:48:30 +0000374 if (preReboot) {
375 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
376 params.installFlags |= PackageManager.INSTALL_DRY_RUN;
377 } else {
378 params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
379 }
Dario Freni4b572c02019-01-29 09:40:31 +0000380 int apkSessionId = mPi.createSession(
Dario Frenic3e68ea2019-04-02 11:45:13 +0100381 params, originalSession.getInstallerPackageName(),
382 0 /* UserHandle.SYSTEM */);
Dario Freni4b572c02019-01-29 09:40:31 +0000383 PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
384
385 try {
386 apkSession.open();
Dario Freni815bd212019-02-20 14:09:36 +0000387 for (String apkFilePath : apkFilePaths) {
388 File apkFile = new File(apkFilePath);
389 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
390 ParcelFileDescriptor.MODE_READ_ONLY);
391 long sizeBytes = pfd.getStatSize();
392 if (sizeBytes < 0) {
393 Slog.e(TAG, "Unable to get size of: " + apkFilePath);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100394 throw new PackageManagerException(errorCode,
395 "Unable to get size of: " + apkFilePath);
Dario Freni815bd212019-02-20 14:09:36 +0000396 }
397 apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
Dario Freni4b572c02019-01-29 09:40:31 +0000398 }
Dario Freni4b572c02019-01-29 09:40:31 +0000399 } catch (IOException e) {
400 Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100401 throw new PackageManagerException(errorCode, "Failed to write APK session", e);
Dario Freni4b572c02019-01-29 09:40:31 +0000402 }
403 return apkSession;
404 }
405
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100406 /**
407 * Extract apks in the given session into a new session. Returns {@code null} if there is no
408 * apks in the given session. Only parent session is returned for multi-package session.
409 */
410 @Nullable
411 private PackageInstallerSession extractApksInSession(PackageInstallerSession session,
412 boolean preReboot) throws PackageManagerException {
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100413 final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
414 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
Dario Freni4b572c02019-01-29 09:40:31 +0000415 if (!session.isMultiPackage() && !isApexSession(session)) {
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100416 return createAndWriteApkSession(session, preReboot);
Dario Freni4b572c02019-01-29 09:40:31 +0000417 } else if (session.isMultiPackage()) {
418 // For multi-package staged sessions containing APKs, we identify which child sessions
419 // contain an APK, and with those then create a new multi-package group of sessions,
420 // carrying over all the session parameters and unmarking them as staged. On commit the
421 // sessions will be installed atomically.
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100422 final List<PackageInstallerSession> childSessions;
Dario Freni4b572c02019-01-29 09:40:31 +0000423 synchronized (mStagedSessions) {
424 childSessions =
425 Arrays.stream(session.getChildSessionIds())
426 // Retrieve cached sessions matching ids.
427 .mapToObj(i -> mStagedSessions.get(i))
428 // Filter only the ones containing APKs.s
429 .filter(childSession -> !isApexSession(childSession))
430 .collect(Collectors.toList());
431 }
432 if (childSessions.isEmpty()) {
433 // APEX-only multi-package staged session, nothing to do.
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100434 return null;
Dario Freni4b572c02019-01-29 09:40:31 +0000435 }
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100436 final PackageInstaller.SessionParams params = session.params.copy();
Dario Freni4b572c02019-01-29 09:40:31 +0000437 params.isStaged = false;
Dario Frenif141aab2019-02-04 14:48:30 +0000438 if (preReboot) {
439 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
440 }
Dario Frenic3e68ea2019-04-02 11:45:13 +0100441 // TODO(b/129744602): use the userid from the original session.
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100442 final int apkParentSessionId = mPi.createSession(
Dario Frenic3e68ea2019-04-02 11:45:13 +0100443 params, session.getInstallerPackageName(),
444 0 /* UserHandle.SYSTEM */);
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100445 final PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
Dario Freni4b572c02019-01-29 09:40:31 +0000446 try {
447 apkParentSession.open();
448 } catch (IOException e) {
449 Slog.e(TAG, "Unable to prepare multi-package session for staged session "
450 + session.sessionId);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100451 throw new PackageManagerException(errorCode,
452 "Unable to prepare multi-package session for staged session");
Dario Freni4b572c02019-01-29 09:40:31 +0000453 }
454
455 for (PackageInstallerSession sessionToClone : childSessions) {
Dario Frenif141aab2019-02-04 14:48:30 +0000456 PackageInstallerSession apkChildSession =
457 createAndWriteApkSession(sessionToClone, preReboot);
Dario Freni09660472019-02-18 16:44:23 +0000458 try {
459 apkParentSession.addChildSessionId(apkChildSession.sessionId);
Patrick Baumann00321b72019-04-09 15:07:25 -0700460 } catch (IllegalStateException e) {
Dario Freni09660472019-02-18 16:44:23 +0000461 Slog.e(TAG, "Failed to add a child session for installing the APK files", e);
Nikita Ioffec2b87a92019-07-15 13:28:11 +0100462 throw new PackageManagerException(errorCode,
463 "Failed to add a child session " + apkChildSession.sessionId);
Dario Freni09660472019-02-18 16:44:23 +0000464 }
Dario Freni4b572c02019-01-29 09:40:31 +0000465 }
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100466 return apkParentSession;
Dario Freni4b572c02019-01-29 09:40:31 +0000467 }
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100468 return null;
469 }
470
471 private void verifyApksInSession(PackageInstallerSession session)
472 throws PackageManagerException {
473
474 final PackageInstallerSession apksToVerify = extractApksInSession(
475 session, /* preReboot */ true);
476 if (apksToVerify == null) {
477 return;
478 }
479
480 final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync(
481 (Intent result) -> {
482 int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
483 PackageInstaller.STATUS_FAILURE);
484 if (status != PackageInstaller.STATUS_SUCCESS) {
485 final String errorMessage = result.getStringExtra(
486 PackageInstaller.EXTRA_STATUS_MESSAGE);
487 Slog.e(TAG, "Failure to verify APK staged session "
488 + session.sessionId + " [" + errorMessage + "]");
489 session.setStagedSessionFailed(
490 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage);
491 return;
492 }
493 mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
494 session.sessionId);
495 });
496
497 apksToVerify.commit(receiver.getIntentSender(), false);
498 }
499
500 private void installApksInSession(@NonNull PackageInstallerSession session)
501 throws PackageManagerException {
502
503 final PackageInstallerSession apksToInstall = extractApksInSession(
504 session, /* preReboot */ false);
505 if (apksToInstall == null) {
506 return;
507 }
508
509 if ((apksToInstall.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
510 // If rollback is available for this session, notify the rollback
511 // manager of the apk session so it can properly enable rollback.
512 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
513 ServiceManager.getService(Context.ROLLBACK_SERVICE));
514 try {
515 rm.notifyStagedApkSession(session.sessionId, apksToInstall.sessionId);
516 } catch (RemoteException re) {
517 Slog.e(TAG, "Failed to notifyStagedApkSession for session: "
518 + session.sessionId, re);
519 }
520 }
521
522 final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
523 apksToInstall.commit(receiver.getIntentSender(), false);
524 final Intent result = receiver.getResult();
525 final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
526 PackageInstaller.STATUS_FAILURE);
527 if (status != PackageInstaller.STATUS_SUCCESS) {
528 final String errorMessage = result.getStringExtra(
529 PackageInstaller.EXTRA_STATUS_MESSAGE);
530 Slog.e(TAG, "Failure to install APK staged session "
531 + session.sessionId + " [" + errorMessage + "]");
532 throw new PackageManagerException(
533 SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage);
534 }
Dario Freni61b3e252019-01-11 23:05:39 +0000535 }
536
Nikita Ioffe8e5b703c2019-03-14 18:40:17 +0000537 void commitSession(@NonNull PackageInstallerSession session) {
Dario Freni276cd072019-01-09 11:13:44 +0000538 updateStoredSession(session);
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100539 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
Dario Frenibe98c3f2018-12-22 15:25:27 +0000540 }
541
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100542 /**
543 * <p> Check if the session provided is non-overlapping with the active staged sessions.
544 *
545 * <p> A session is non-overlapping if it meets one of the following conditions: </p>
546 * <ul>
547 * <li>It is a parent session</li>
548 * <li>It is already one of the active sessions</li>
549 * <li>Its package name is not same as any of the active sessions</li>
550 * </ul>
551 * @throws PackageManagerException if session fails the check
552 */
553 void checkNonOverlappingWithStagedSessions(@NonNull PackageInstallerSession session)
554 throws PackageManagerException {
555 if (session.isMultiPackage()) {
556 // We cannot say a parent session overlaps until we process its children
557 return;
558 }
559 if (session.getPackageName() == null) {
560 throw new PackageManagerException(PackageManager.INSTALL_FAILED_INVALID_APK,
561 "Cannot stage session " + session.sessionId + " with package name null");
562 }
563
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000564 synchronized (mStagedSessions) {
565 for (int i = 0; i < mStagedSessions.size(); i++) {
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100566 final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
567 if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()) {
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000568 continue;
569 }
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100570 if (stagedSession.isMultiPackage()) {
571 // This active parent staged session is useless as it doesn't have a package
572 // name and the session we are checking is not a parent session either.
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000573 continue;
574 }
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100575
576 // From here on, stagedSession is a non-parent active staged session
577
578 // Check if stagedSession has an active parent session or not
579 if (stagedSession.hasParentSessionId()) {
580 int parentId = stagedSession.getParentSessionId();
581 PackageInstallerSession parentSession = mStagedSessions.get(parentId);
582 if (parentSession == null || parentSession.isStagedAndInTerminalState()) {
583 // Parent session has been abandoned or terminated already
584 continue;
585 }
586 }
587
588 // Check if session is one of the active sessions
589 if (session.sessionId == stagedSession.sessionId) {
590 Slog.w(TAG, "Session " + session.sessionId + " is already staged");
591 continue;
592 }
593
594 // If session is not among the active sessions, then it cannot have same package
595 // name as any of the active sessions.
596 if (session.getPackageName().equals(stagedSession.getPackageName())) {
597 throw new PackageManagerException(
598 PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
599 "Package: " + session.getPackageName() + " in session: "
600 + session.sessionId + " has been staged already by session: "
601 + stagedSession.sessionId, null);
602 }
603
604 // TODO(b/141843321): Add support for staging multiple sessions in apexd
605 // Since apexd doesn't support multiple staged sessions yet, we have to careful how
606 // we handle apex sessions. We want to allow a set of apex sessions under the same
607 // parent to be staged when there is no previously staged apex sessions.
608 if (isApexSession(session) && isApexSession(stagedSession)) {
609 // session is apex and it can co-exist with stagedSession only if they are from
610 // same parent
611 final boolean coExist;
612 if (!session.hasParentSessionId() && !stagedSession.hasParentSessionId()) {
613 // Both single package apex sessions. Cannot co-exist.
614 coExist = false;
615 } else {
616 // At least one of the session has parent. Both must be from same parent.
617 coExist =
618 session.getParentSessionId() == stagedSession.getParentSessionId();
619 }
620 if (!coExist) {
621 throw new PackageManagerException(
622 PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
623 "Package: " + session.getPackageName() + " in session: "
624 + session.sessionId + " cannot be staged as there is "
625 + "already another apex staged session: "
626 + stagedSession.sessionId, null);
627 }
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000628 }
629 }
630 }
Nikita Ioffeda998cf2019-03-04 22:54:30 +0000631 }
632
Dario Frenibe98c3f2018-12-22 15:25:27 +0000633 void createSession(@NonNull PackageInstallerSession sessionInfo) {
634 synchronized (mStagedSessions) {
635 mStagedSessions.append(sessionInfo.sessionId, sessionInfo);
636 }
637 }
638
Dario Freni015f9352019-01-14 21:56:17 +0000639 void abortSession(@NonNull PackageInstallerSession session) {
Dario Frenibe98c3f2018-12-22 15:25:27 +0000640 synchronized (mStagedSessions) {
Dario Freni015f9352019-01-14 21:56:17 +0000641 mStagedSessions.remove(session.sessionId);
Dario Frenibe98c3f2018-12-22 15:25:27 +0000642 }
643 }
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000644
shafik07205e32019-02-07 20:12:33 +0000645 void abortCommittedSession(@NonNull PackageInstallerSession session) {
Nikita Ioffe1e523b52019-03-13 16:07:48 +0000646 if (session.isStagedSessionApplied()) {
647 Slog.w(TAG, "Cannot abort applied session : " + session.sessionId);
shafik07205e32019-02-07 20:12:33 +0000648 return;
649 }
shafik07205e32019-02-07 20:12:33 +0000650 abortSession(session);
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000651
652 boolean hasApex = sessionContainsApex(session);
653 if (hasApex) {
654 ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId);
655 if (apexSession == null || isApexSessionFinalized(apexSession)) {
656 Slog.w(TAG,
Mohammad Samiul Islamda004972019-10-09 11:29:26 +0100657 "Cannot abort session " + session.sessionId
658 + " because it is not active or APEXD is not reachable");
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000659 return;
660 }
661 mApexManager.abortActiveSession();
662 }
shafik07205e32019-02-07 20:12:33 +0000663 }
664
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000665 private boolean isApexSessionFinalized(ApexSessionInfo session) {
shafik07205e32019-02-07 20:12:33 +0000666 /* checking if the session is in a final state, i.e., not active anymore */
Nikita Ioffe82742222019-02-26 12:14:04 +0000667 return session.isUnknown || session.isActivationFailed || session.isSuccess
Mohammad Samiul Islam2dfc86a2019-11-20 13:53:07 +0000668 || session.isReverted;
Nikita Ioffe82742222019-02-26 12:14:04 +0000669 }
670
671 private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) {
Mohammad Samiul Islam2dfc86a2019-11-20 13:53:07 +0000672 // isRevertInProgress is included to cover the scenario, when a device is rebooted
673 // during the revert, and apexd fails to resume the revert after reboot.
Nikita Ioffe82742222019-02-26 12:14:04 +0000674 return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown
Mohammad Samiul Islam2dfc86a2019-11-20 13:53:07 +0000675 || apexSessionInfo.isReverted || apexSessionInfo.isRevertInProgress
676 || apexSessionInfo.isRevertFailed;
shafik07205e32019-02-07 20:12:33 +0000677 }
678
Dario Freni015f9352019-01-14 21:56:17 +0000679 @GuardedBy("mStagedSessions")
680 private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) {
681 // This method assumes that the argument is either a parent session of a multi-package
682 // i.e. isMultiPackage() returns true, or that it is a child session, i.e.
683 // hasParentSessionId() returns true.
684 if (session.isMultiPackage()) {
685 // Parent session of a multi-package group. Check that we restored all the children.
686 for (int childSession : session.getChildSessionIds()) {
687 if (mStagedSessions.get(childSession) == null) {
688 return false;
689 }
690 }
691 return true;
692 }
693 if (session.hasParentSessionId()) {
694 PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId());
695 if (parent == null) {
696 return false;
697 }
698 return isMultiPackageSessionComplete(parent);
699 }
700 Slog.wtf(TAG, "Attempting to restore an invalid multi-package session.");
701 return false;
702 }
703
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000704 void restoreSession(@NonNull PackageInstallerSession session) {
Dario Freni015f9352019-01-14 21:56:17 +0000705 PackageInstallerSession sessionToResume = session;
706 synchronized (mStagedSessions) {
707 mStagedSessions.append(session.sessionId, session);
708 // For multi-package sessions, we don't know in which order they will be restored. We
709 // need to wait until we have restored all the session in a group before restoring them.
710 if (session.isMultiPackage() || session.hasParentSessionId()) {
711 if (!isMultiPackageSessionComplete(session)) {
712 // Still haven't recovered all sessions of the group, return.
713 return;
714 }
715 // Group recovered, find the parent if necessary and resume the installation.
716 if (session.hasParentSessionId()) {
717 sessionToResume = mStagedSessions.get(session.getParentSessionId());
718 }
719 }
720 }
721 checkStateAndResume(sessionToResume);
722 }
723
724 private void checkStateAndResume(@NonNull PackageInstallerSession session) {
Dario Freni47799f42019-03-13 18:06:24 +0000725 if (!session.isCommitted()) {
726 // Session hasn't been committed yet, ignore.
727 return;
728 }
Dario Freni61b3e252019-01-11 23:05:39 +0000729 // Check the state of the session and decide what to do next.
730 if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
731 // Final states, nothing to do.
732 return;
733 }
734 if (!session.isStagedSessionReady()) {
735 // The framework got restarted before the pre-reboot verification could complete,
736 // restart the verification.
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100737 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
Dario Freni61b3e252019-01-11 23:05:39 +0000738 } else {
739 // Session had already being marked ready. Start the checks to verify if there is any
740 // follow-up work.
Dario Freni4df010e2019-01-31 19:34:46 +0000741 resumeSession(session);
Dario Freni61b3e252019-01-11 23:05:39 +0000742 }
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000743 }
Dario Freni4b572c02019-01-29 09:40:31 +0000744
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100745 private static class LocalIntentReceiverAsync {
746 final Consumer<Intent> mConsumer;
747
748 LocalIntentReceiverAsync(Consumer<Intent> consumer) {
749 mConsumer = consumer;
750 }
751
752 private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
753 @Override
754 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
755 IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
756 mConsumer.accept(intent);
757 }
758 };
759
760 public IntentSender getIntentSender() {
761 return new IntentSender((IIntentSender) mLocalSender);
762 }
763 }
764
765 private static class LocalIntentReceiverSync {
Dario Freni4b572c02019-01-29 09:40:31 +0000766 private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
767
768 private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
769 @Override
770 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100771 IIntentReceiver finishedReceiver, String requiredPermission,
772 Bundle options) {
Dario Freni4b572c02019-01-29 09:40:31 +0000773 try {
774 mResult.offer(intent, 5, TimeUnit.SECONDS);
775 } catch (InterruptedException e) {
776 throw new RuntimeException(e);
777 }
778 }
779 };
780
781 public IntentSender getIntentSender() {
782 return new IntentSender((IIntentSender) mLocalSender);
783 }
784
785 public Intent getResult() {
786 try {
787 return mResult.take();
788 } catch (InterruptedException e) {
789 throw new RuntimeException(e);
790 }
791 }
792 }
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100793
794 private final class PreRebootVerificationHandler extends Handler {
795
796 PreRebootVerificationHandler(Looper looper) {
797 super(looper);
798 }
799
800 /**
801 * Handler for states of pre reboot verification. The states are arranged linearly (shown
802 * below) with each state either calling the next state, or calling some other method that
803 * eventually calls the next state.
804 *
805 * <p><ul>
806 * <li>MSG_PRE_REBOOT_VERIFICATION_START</li>
807 * <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li>
808 * <li>MSG_PRE_REBOOT_VERIFICATION_APK</li>
809 * <li>MSG_PRE_REBOOT_VERIFICATION_END</li>
810 * </ul></p>
811 *
812 * Details about each of state can be found in corresponding handler of node.
813 */
814 private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
815 private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
816 private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3;
817 private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4;
818
819 @Override
820 public void handleMessage(Message msg) {
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100821 final int sessionId = msg.arg1;
822 final PackageInstallerSession session;
823 synchronized (mStagedSessions) {
824 session = mStagedSessions.get(sessionId);
825 }
826 // Maybe session was aborted before pre-reboot verification was complete
827 if (session == null) {
828 Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId);
829 return;
830 }
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100831 switch (msg.what) {
832 case MSG_PRE_REBOOT_VERIFICATION_START:
833 handlePreRebootVerification_Start(session);
834 break;
835 case MSG_PRE_REBOOT_VERIFICATION_APEX:
836 handlePreRebootVerification_Apex(session);
837 break;
838 case MSG_PRE_REBOOT_VERIFICATION_APK:
839 handlePreRebootVerification_Apk(session);
840 break;
841 case MSG_PRE_REBOOT_VERIFICATION_END:
842 handlePreRebootVerification_End(session);
843 break;
844 }
845 }
846
847 // Method for starting the pre-reboot verification
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100848 private void startPreRebootVerification(int sessionId) {
849 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100850 }
851
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100852 private void notifyPreRebootVerification_Start_Complete(int sessionId) {
853 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100854 }
855
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100856 private void notifyPreRebootVerification_Apex_Complete(int sessionId) {
857 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100858 }
859
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100860 private void notifyPreRebootVerification_Apk_Complete(int sessionId) {
861 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, sessionId, 0).sendToTarget();
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100862 }
863
864 /**
865 * A dummy state for starting the pre reboot verification.
866 *
867 * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification
868 */
869 private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
870 Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100871 notifyPreRebootVerification_Start_Complete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100872 }
873
874 /**
875 * Pre-reboot verification state for apex files:
876 *
877 * <p><ul>
878 * <li>submits session to apex service</li>
879 * <li>validates signatures of apex files</li>
880 * </ul></p>
881 */
882 private void handlePreRebootVerification_Apex(@NonNull PackageInstallerSession session) {
883 final boolean hasApex = sessionContainsApex(session);
884
885 // APEX checks. For single-package sessions, check if they contain an APEX. For
886 // multi-package sessions, find all the child sessions that contain an APEX.
887 if (hasApex) {
888 try {
889 final List<PackageInfo> apexPackages =
890 submitSessionToApexService(session);
891 for (PackageInfo apexPackage : apexPackages) {
Mohammad Samiul Islamd7472d02019-11-11 17:53:47 +0000892 validateApexSignature(apexPackage);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100893 }
894 } catch (PackageManagerException e) {
895 session.setStagedSessionFailed(e.error, e.getMessage());
896 return;
897 }
898 }
899
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100900 notifyPreRebootVerification_Apex_Complete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100901 }
902
903 /**
904 * Pre-reboot verification state for apk files:
905 * <p><ul>
906 * <li>performs a dry-run install of apk</li>
907 * </ul></p>
908 */
909 private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) {
910 if (!sessionContainsApk(session)) {
Mohammad Samiul Islam6dc98892019-09-13 11:10:12 +0100911 notifyPreRebootVerification_Apk_Complete(session.sessionId);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100912 return;
913 }
914
915 try {
916 Slog.d(TAG, "Running a pre-reboot verification for APKs in session "
917 + session.sessionId + " by performing a dry-run install");
918
Mohammad Samiul Islam8c041df2019-09-10 16:26:26 +0100919 // verifyApksInSession will notify the handler when APK verification is complete
920 verifyApksInSession(session);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100921 // TODO(b/118865310): abort the session on apexd.
922 } catch (PackageManagerException e) {
923 session.setStagedSessionFailed(e.error, e.getMessage());
924 }
925 }
926
927 /**
928 * Pre-reboot verification state for wrapping up:
929 * <p><ul>
930 * <li>enables rollback if required</li>
931 * <li>marks session as ready</li>
932 * </ul></p>
933 */
934 private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) {
935 if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
936 // If rollback is enabled for this session, we call through to the RollbackManager
937 // with the list of sessions it must enable rollback for. Note that
938 // notifyStagedSession is a synchronous operation.
939 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
940 ServiceManager.getService(Context.ROLLBACK_SERVICE));
941 try {
942 // NOTE: To stay consistent with the non-staged install flow, we don't fail the
943 // entire install if rollbacks can't be enabled.
944 if (!rm.notifyStagedSession(session.sessionId)) {
945 Slog.e(TAG, "Unable to enable rollback for session: "
946 + session.sessionId);
947 }
948 } catch (RemoteException re) {
Richard Uhler7c81c3a2019-09-11 10:43:24 +0100949 Slog.e(TAG, "Failed to notifyStagedSession for session: "
950 + session.sessionId, re);
Mohammad Samiul Islamef400272019-08-15 16:44:57 +0100951 }
952 }
953
954 // Proactively mark session as ready before calling apexd. Although this call order
955 // looks counter-intuitive, this is the easiest way to ensure that session won't end up
956 // in the inconsistent state:
957 // - If device gets rebooted right before call to apexd, then apexd will never activate
958 // apex files of this staged session. This will result in StagingManager failing
959 // the session.
960 // On the other hand, if the order of the calls was inverted (first call apexd, then
961 // mark session as ready), then if a device gets rebooted right after the call to apexd,
962 // only apex part of the train will be applied, leaving device in an inconsistent state.
963 Slog.d(TAG, "Marking session " + session.sessionId + " as ready");
964 session.setStagedSessionReady();
965 final boolean hasApex = sessionContainsApex(session);
966 if (!hasApex) {
967 // Session doesn't contain apex, nothing to do.
968 return;
969 }
970 try {
971 mApexManager.markStagedSessionReady(session.sessionId);
972 } catch (PackageManagerException e) {
973 session.setStagedSessionFailed(e.error, e.getMessage());
974 }
975 }
976 }
Dario Frenibe98c3f2018-12-22 15:25:27 +0000977}