blob: 4ee6eafd0d44ac5c6ccc4f603a27367405c7fb86 [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;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000020import android.apex.ApexInfo;
Dario Freni276cd072019-01-09 11:13:44 +000021import android.apex.ApexInfoList;
Dario Freni61b3e252019-01-11 23:05:39 +000022import android.apex.ApexSessionInfo;
Narayan Kamathfcd4a042019-02-01 14:16:37 +000023import android.content.Context;
Dario Freni4b572c02019-01-29 09:40:31 +000024import android.content.IIntentReceiver;
25import android.content.IIntentSender;
26import android.content.Intent;
27import android.content.IntentSender;
Dario Freni2e8dffc2019-02-06 14:55:16 +000028import android.content.pm.PackageInfo;
Dario Frenibe98c3f2018-12-22 15:25:27 +000029import android.content.pm.PackageInstaller;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000030import android.content.pm.PackageInstaller.SessionInfo;
31import android.content.pm.PackageManager;
32import android.content.pm.PackageParser.PackageParserException;
33import android.content.pm.PackageParser.SigningDetails;
34import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
Dario Frenibe98c3f2018-12-22 15:25:27 +000035import android.content.pm.ParceledListSlice;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000036import android.content.pm.Signature;
Narayan Kamathfcd4a042019-02-01 14:16:37 +000037import android.content.rollback.IRollbackManager;
Nikita Ioffe24e6e122019-03-11 16:45:27 +000038import android.os.Build;
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;
42import android.os.ParcelFileDescriptor;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000043import android.os.PowerManager;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000044import android.os.RemoteException;
45import android.os.ServiceManager;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000046import android.util.Slog;
Dario Frenibe98c3f2018-12-22 15:25:27 +000047import android.util.SparseArray;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000048import android.util.apk.ApkSignatureVerifier;
Dario Frenibe98c3f2018-12-22 15:25:27 +000049
50import com.android.internal.annotations.GuardedBy;
Narayan Kamath94c93ab2019-01-04 10:47:00 +000051import com.android.internal.os.BackgroundThread;
Dario Frenibe98c3f2018-12-22 15:25:27 +000052
Dario Freni4b572c02019-01-29 09:40:31 +000053import java.io.File;
54import java.io.IOException;
Dario Frenibe98c3f2018-12-22 15:25:27 +000055import java.util.ArrayList;
Dario Freni015f9352019-01-14 21:56:17 +000056import java.util.Arrays;
Dario Frenibe98c3f2018-12-22 15:25:27 +000057import java.util.List;
Dario Freni4b572c02019-01-29 09:40:31 +000058import java.util.concurrent.LinkedBlockingQueue;
59import java.util.concurrent.TimeUnit;
Dario Freni015f9352019-01-14 21:56:17 +000060import java.util.stream.Collectors;
Dario Frenibe98c3f2018-12-22 15:25:27 +000061
62/**
63 * This class handles staged install sessions, i.e. install sessions that require packages to
64 * be installed only after a reboot.
65 */
66public class StagingManager {
67
68 private static final String TAG = "StagingManager";
69
Dario Freni4b572c02019-01-29 09:40:31 +000070 private final PackageInstallerService mPi;
Dario Frenibe98c3f2018-12-22 15:25:27 +000071 private final PackageManagerService mPm;
Dario Freni2e8dffc2019-02-06 14:55:16 +000072 private final ApexManager mApexManager;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000073 private final PowerManager mPowerManager;
Narayan Kamath94c93ab2019-01-04 10:47:00 +000074 private final Handler mBgHandler;
Dario Frenibe98c3f2018-12-22 15:25:27 +000075
Dario Frenibe98c3f2018-12-22 15:25:27 +000076 @GuardedBy("mStagedSessions")
77 private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
78
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000079 StagingManager(PackageManagerService pm, PackageInstallerService pi, ApexManager am,
80 Context context) {
Dario Frenibe98c3f2018-12-22 15:25:27 +000081 mPm = pm;
Dario Freni4b572c02019-01-29 09:40:31 +000082 mPi = pi;
Dario Freni2e8dffc2019-02-06 14:55:16 +000083 mApexManager = am;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000084 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Narayan Kamath94c93ab2019-01-04 10:47:00 +000085 mBgHandler = BackgroundThread.getHandler();
Dario Frenibe98c3f2018-12-22 15:25:27 +000086 }
87
88 private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
89 synchronized (mStagedSessions) {
90 PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId);
Narayan Kamath94c93ab2019-01-04 10:47:00 +000091 // storedSession might be null if a call to abortSession was made before the session
92 // is updated.
93 if (storedSession != null) {
94 mStagedSessions.put(sessionInfo.sessionId, sessionInfo);
Dario Frenibe98c3f2018-12-22 15:25:27 +000095 }
Dario Frenibe98c3f2018-12-22 15:25:27 +000096 }
97 }
98
99 ParceledListSlice<PackageInstaller.SessionInfo> getSessions() {
100 final List<PackageInstaller.SessionInfo> result = new ArrayList<>();
101 synchronized (mStagedSessions) {
102 for (int i = 0; i < mStagedSessions.size(); i++) {
103 result.add(mStagedSessions.valueAt(i).generateInfo(false));
104 }
105 }
106 return new ParceledListSlice<>(result);
107 }
108
Dario Freni2e8dffc2019-02-06 14:55:16 +0000109 private boolean validateApexSignature(String apexPath, String packageName) {
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000110 final SigningDetails signingDetails;
111 try {
112 signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
113 } catch (PackageParserException e) {
114 Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e);
115 return false;
116 }
117
Dario Freni2e8dffc2019-02-06 14:55:16 +0000118 final PackageInfo packageInfo = mApexManager.getActivePackage(packageName);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000119
Dario Freni2e8dffc2019-02-06 14:55:16 +0000120 if (packageInfo == null) {
Nikita Ioffe24e6e122019-03-11 16:45:27 +0000121 // Only allow installing new apexes if on a debuggable build.
122 if (!Build.IS_DEBUGGABLE) {
123 Slog.w(TAG, "Attempted to install new apex " + packageName + " on user build");
124 return false;
125 }
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000126 return true;
127 }
128
129 final SigningDetails existingSigningDetails;
130 try {
131 existingSigningDetails = ApkSignatureVerifier.verify(
Dario Freni2e8dffc2019-02-06 14:55:16 +0000132 packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000133 } catch (PackageParserException e) {
Dario Freni2e8dffc2019-02-06 14:55:16 +0000134 Slog.e(TAG, "Unable to parse APEX package: "
135 + packageInfo.applicationInfo.sourceDir, e);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000136 return false;
137 }
138
139 // Now that we have both sets of signatures, demand that they're an exact match.
140 if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) {
141 return true;
142 }
143
144 return false;
145 }
146
Dario Freni2e8dffc2019-02-06 14:55:16 +0000147 private boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
148 List<PackageInstallerSession> childSessions,
149 ApexInfoList apexInfoList) {
Nikita Ioffe8bbb8152019-02-21 15:54:54 +0000150 boolean submittedToApexd = mApexManager.submitStagedSession(
Dario Freni015f9352019-01-14 21:56:17 +0000151 session.sessionId,
152 childSessions != null
153 ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
154 new int[]{},
155 apexInfoList);
Nikita Ioffe8bbb8152019-02-21 15:54:54 +0000156 if (!submittedToApexd) {
157 session.setStagedSessionFailed(
158 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
159 "APEX staging failed, check logcat messages from apexd for more details.");
160 return false;
161 }
162 for (ApexInfo newPackage : apexInfoList.apexInfos) {
163 PackageInfo activePackage = mApexManager.getActivePackage(newPackage.packageName);
164 if (activePackage == null) {
165 continue;
166 }
167 long activeVersion = activePackage.applicationInfo.longVersionCode;
168 boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
169 session.params.installFlags, activePackage.applicationInfo.flags);
170 if (activeVersion > newPackage.versionCode && !allowsDowngrade) {
171 session.setStagedSessionFailed(
172 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
173 "Downgrade of APEX package " + newPackage.packageName
174 + " is not allowed. Active version: " + activeVersion
175 + " attempted: " + newPackage.versionCode);
176
177 if (!mApexManager.abortActiveSession()) {
178 Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
179 }
180 return false;
181 }
182 }
183 return true;
Dario Freni015f9352019-01-14 21:56:17 +0000184 }
185
Dario Freni015f9352019-01-14 21:56:17 +0000186 private static boolean isApexSession(@NonNull PackageInstallerSession session) {
187 return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
188 }
189
Dario Freni61b3e252019-01-11 23:05:39 +0000190 private void preRebootVerification(@NonNull PackageInstallerSession session) {
Dario Freni276cd072019-01-09 11:13:44 +0000191 boolean success = true;
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000192
Dario Freni49c3fa72019-02-04 12:07:25 +0000193 // STOPSHIP: TODO(b/123753157): Verify APKs through Package Verifier.
Richard Uhler34ec3f12019-02-12 09:20:42 +0000194 // TODO: Decide whether we want to fail fast by detecting signature mismatches for APKs,
195 // right away.
Dario Freni4b572c02019-01-29 09:40:31 +0000196
Dario Freni015f9352019-01-14 21:56:17 +0000197 final ApexInfoList apexInfoList = new ApexInfoList();
198 // APEX checks. For single-package sessions, check if they contain an APEX. For
199 // multi-package sessions, find all the child sessions that contain an APEX.
200 if (!session.isMultiPackage()
201 && isApexSession(session)) {
202 success = submitSessionToApexService(session, null, apexInfoList);
Dario Freni275b4ab2019-01-25 09:55:16 +0000203
Dario Freni015f9352019-01-14 21:56:17 +0000204 } else if (session.isMultiPackage()) {
205 List<PackageInstallerSession> childSessions =
206 Arrays.stream(session.getChildSessionIds())
207 // Retrieve cached sessions matching ids.
208 .mapToObj(i -> mStagedSessions.get(i))
209 // Filter only the ones containing APEX.
210 .filter(childSession -> isApexSession(childSession))
211 .collect(Collectors.toList());
212 if (!childSessions.isEmpty()) {
213 success = submitSessionToApexService(session, childSessions, apexInfoList);
214 } // else this is a staged multi-package session with no APEX files.
215 }
Dario Freni276cd072019-01-09 11:13:44 +0000216
Dario Freni275b4ab2019-01-25 09:55:16 +0000217 if (!success) {
Nikita Ioffe8bbb8152019-02-21 15:54:54 +0000218 // submitSessionToApexService will populate error.
Dario Frenife32b3f2019-02-07 17:52:11 +0000219 return;
Dario Freni275b4ab2019-01-25 09:55:16 +0000220 }
221
Dario Freni4b572c02019-01-29 09:40:31 +0000222 if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) {
Dario Freni015f9352019-01-14 21:56:17 +0000223 // For APEXes, we validate the signature here before we mark the session as ready,
224 // so we fail the session early if there is a signature mismatch. For APKs, the
225 // signature verification will be done by the package manager at the point at which
226 // it applies the staged install.
Dario Freni015f9352019-01-14 21:56:17 +0000227 for (ApexInfo apexPackage : apexInfoList.apexInfos) {
Dario Freni4b572c02019-01-29 09:40:31 +0000228 if (!validateApexSignature(apexPackage.packagePath,
Dario Freni015f9352019-01-14 21:56:17 +0000229 apexPackage.packageName)) {
Dario Frenib6d28962019-01-31 15:52:24 +0000230 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Dario Freni275b4ab2019-01-25 09:55:16 +0000231 "APK-container signature verification failed for package "
232 + apexPackage.packageName + ". Signature of file "
233 + apexPackage.packagePath + " does not match the signature of "
234 + " the package already installed.");
Dario Freni1473bcb2019-01-25 14:27:13 +0000235 // TODO(b/118865310): abort the session on apexd.
Dario Freni275b4ab2019-01-25 09:55:16 +0000236 return;
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000237 }
238 }
Dario Freni276cd072019-01-09 11:13:44 +0000239 }
Dario Freni1473bcb2019-01-25 14:27:13 +0000240
Narayan Kamathfcd4a042019-02-01 14:16:37 +0000241 if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
242 // If rollback is enabled for this session, we call through to the RollbackManager
243 // with the list of sessions it must enable rollback for. Note that notifyStagedSession
244 // is a synchronous operation.
245 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
246 ServiceManager.getService(Context.ROLLBACK_SERVICE));
247 try {
248 // NOTE: To stay consistent with the non-staged install flow, we don't fail the
249 // entire install if rollbacks can't be enabled.
250 if (!rm.notifyStagedSession(session.sessionId)) {
251 Slog.e(TAG, "Unable to enable rollback for session: " + session.sessionId);
252 }
253 } catch (RemoteException re) {
254 // Cannot happen, the rollback manager is in the same process.
255 }
256 }
257
Dario Freni275b4ab2019-01-25 09:55:16 +0000258 session.setStagedSessionReady();
Richard Uhler34ec3f12019-02-12 09:20:42 +0000259 if (sessionContainsApex(session)
260 && !mApexManager.markStagedSessionReady(session.sessionId)) {
Dario Frenib6d28962019-01-31 15:52:24 +0000261 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Dario Freni1473bcb2019-01-25 14:27:13 +0000262 "APEX staging failed, check logcat messages from apexd for more "
263 + "details.");
264 }
Dario Freni276cd072019-01-09 11:13:44 +0000265 }
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000266
Dario Freni4b572c02019-01-29 09:40:31 +0000267 private boolean sessionContainsApex(@NonNull PackageInstallerSession session) {
268 if (!session.isMultiPackage()) {
269 return isApexSession(session);
270 }
271 synchronized (mStagedSessions) {
272 return !(Arrays.stream(session.getChildSessionIds())
273 // Retrieve cached sessions matching ids.
274 .mapToObj(i -> mStagedSessions.get(i))
275 // Filter only the ones containing APEX.
276 .filter(childSession -> isApexSession(childSession))
277 .collect(Collectors.toList())
278 .isEmpty());
279 }
280 }
281
Dario Freni61b3e252019-01-11 23:05:39 +0000282 private void resumeSession(@NonNull PackageInstallerSession session) {
Nikita Ioffea820bd92019-02-15 14:22:44 +0000283 boolean hasApex = sessionContainsApex(session);
284 if (hasApex) {
Dario Freni2e8dffc2019-02-06 14:55:16 +0000285 // Check with apexservice whether the apex packages have been activated.
286 ApexSessionInfo apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
287 if (apexSessionInfo == null) {
288 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
289 "apexd did not know anything about a staged session supposed to be"
290 + "activated");
Dario Freni4b572c02019-01-29 09:40:31 +0000291 return;
292 }
Nikita Ioffe82742222019-02-26 12:14:04 +0000293 if (isApexSessionFailed(apexSessionInfo)) {
Dario Frenib6d28962019-01-31 15:52:24 +0000294 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
Dario Freni4b572c02019-01-29 09:40:31 +0000295 "APEX activation failed. Check logcat messages from apexd for "
296 + "more information.");
297 return;
298 }
299 if (apexSessionInfo.isVerified) {
300 // Session has been previously submitted to apexd, but didn't complete all the
301 // pre-reboot verification, perhaps because the device rebooted in the meantime.
302 // Greedily re-trigger the pre-reboot verification.
303 Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to be "
304 + "verified, resuming pre-reboot verification");
305 mBgHandler.post(() -> preRebootVerification(session));
306 return;
307 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000308 if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
Dario Freni4b572c02019-01-29 09:40:31 +0000309 // In all the remaining cases apexd will try to apply the session again at next
310 // boot. Nothing to do here for now.
311 Slog.w(TAG, "Staged session " + session.sessionId + " scheduled to be applied "
312 + "at boot didn't activate nor fail. This usually means that apexd will "
313 + "retry at next reboot.");
314 return;
315 }
316 }
317 // The APEX part of the session is activated, proceed with the installation of APKs.
318 if (!installApksInSession(session)) {
Dario Frenib6d28962019-01-31 15:52:24 +0000319 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
Dario Freni2e8dffc2019-02-06 14:55:16 +0000320 "Staged installation of APKs failed. Check logcat messages for"
321 + "more information.");
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000322
323 if (!hasApex) {
324 return;
325 }
326
327 if (!mApexManager.abortActiveSession()) {
328 Slog.e(TAG, "Failed to abort APEXd session");
329 } else {
330 Slog.e(TAG,
331 "Successfully aborted apexd session. Rebooting device in order to revert "
332 + "to the previous state of APEXd.");
333 mPowerManager.reboot(null);
334 }
335
Dario Freni61b3e252019-01-11 23:05:39 +0000336 return;
337 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000338
Dario Freni4b572c02019-01-29 09:40:31 +0000339 session.setStagedSessionApplied();
Nikita Ioffea820bd92019-02-15 14:22:44 +0000340 if (hasApex) {
341 mApexManager.markStagedSessionSuccessful(session.sessionId);
342 }
Dario Freni4b572c02019-01-29 09:40:31 +0000343 }
344
Dario Freni815bd212019-02-20 14:09:36 +0000345 private List<String> findAPKsInDir(File stageDir) {
346 List<String> ret = new ArrayList<>();
Dario Freni4b572c02019-01-29 09:40:31 +0000347 if (stageDir != null && stageDir.exists()) {
348 for (File file : stageDir.listFiles()) {
349 if (file.getAbsolutePath().toLowerCase().endsWith(".apk")) {
Dario Freni815bd212019-02-20 14:09:36 +0000350 ret.add(file.getAbsolutePath());
Dario Freni4b572c02019-01-29 09:40:31 +0000351 }
352 }
Dario Freni61b3e252019-01-11 23:05:39 +0000353 }
Dario Freni815bd212019-02-20 14:09:36 +0000354 return ret;
Dario Freni4b572c02019-01-29 09:40:31 +0000355 }
356
357 private PackageInstallerSession createAndWriteApkSession(
358 @NonNull PackageInstallerSession originalSession) {
Dario Freni4b572c02019-01-29 09:40:31 +0000359 if (originalSession.stageDir == null) {
360 Slog.wtf(TAG, "Attempting to install a staged APK session with no staging dir");
361 return null;
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());
366 return null;
Dario Freni61b3e252019-01-11 23:05:39 +0000367 }
Dario Freni4b572c02019-01-29 09:40:31 +0000368
369 PackageInstaller.SessionParams params = originalSession.params.copy();
370 params.isStaged = false;
Dario Freni49c3fa72019-02-04 12:07:25 +0000371 params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
Dario Freni4b572c02019-01-29 09:40:31 +0000372 int apkSessionId = mPi.createSession(
373 params, originalSession.getInstallerPackageName(), originalSession.userId);
374 PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
375
376 try {
377 apkSession.open();
Dario Freni815bd212019-02-20 14:09:36 +0000378 for (String apkFilePath : apkFilePaths) {
379 File apkFile = new File(apkFilePath);
380 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
381 ParcelFileDescriptor.MODE_READ_ONLY);
382 long sizeBytes = pfd.getStatSize();
383 if (sizeBytes < 0) {
384 Slog.e(TAG, "Unable to get size of: " + apkFilePath);
385 return null;
386 }
387 apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
Dario Freni4b572c02019-01-29 09:40:31 +0000388 }
Dario Freni4b572c02019-01-29 09:40:31 +0000389 } catch (IOException e) {
390 Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
391 return null;
392 }
393 return apkSession;
394 }
395
396 private boolean commitApkSession(@NonNull PackageInstallerSession apkSession,
397 int originalSessionId) {
Richard Uhler6fa7d132019-02-05 13:55:11 +0000398
399 if ((apkSession.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
400 // If rollback is available for this session, notify the rollback
401 // manager of the apk session so it can properly enable rollback.
402 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
403 ServiceManager.getService(Context.ROLLBACK_SERVICE));
404 try {
405 rm.notifyStagedApkSession(originalSessionId, apkSession.sessionId);
406 } catch (RemoteException re) {
407 // Cannot happen, the rollback manager is in the same process.
408 }
409 }
410
Dario Freni4b572c02019-01-29 09:40:31 +0000411 final LocalIntentReceiver receiver = new LocalIntentReceiver();
412 apkSession.commit(receiver.getIntentSender(), false);
413 final Intent result = receiver.getResult();
414 final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
415 PackageInstaller.STATUS_FAILURE);
416 if (status == PackageInstaller.STATUS_SUCCESS) {
417 return true;
418 }
419 Slog.e(TAG, "Failure to install APK staged session " + originalSessionId + " ["
420 + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
421 return false;
422 }
423
424 private boolean installApksInSession(@NonNull PackageInstallerSession session) {
425 if (!session.isMultiPackage() && !isApexSession(session)) {
426 // APK single-packaged staged session. Do a regular install.
427 PackageInstallerSession apkSession = createAndWriteApkSession(session);
428 if (apkSession == null) {
429 return false;
430 }
431 return commitApkSession(apkSession, session.sessionId);
432 } else if (session.isMultiPackage()) {
433 // For multi-package staged sessions containing APKs, we identify which child sessions
434 // contain an APK, and with those then create a new multi-package group of sessions,
435 // carrying over all the session parameters and unmarking them as staged. On commit the
436 // sessions will be installed atomically.
437 List<PackageInstallerSession> childSessions;
438 synchronized (mStagedSessions) {
439 childSessions =
440 Arrays.stream(session.getChildSessionIds())
441 // Retrieve cached sessions matching ids.
442 .mapToObj(i -> mStagedSessions.get(i))
443 // Filter only the ones containing APKs.s
444 .filter(childSession -> !isApexSession(childSession))
445 .collect(Collectors.toList());
446 }
447 if (childSessions.isEmpty()) {
448 // APEX-only multi-package staged session, nothing to do.
449 return true;
450 }
451 PackageInstaller.SessionParams params = session.params.copy();
452 params.isStaged = false;
453 int apkParentSessionId = mPi.createSession(
454 params, session.getInstallerPackageName(), session.userId);
455 PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
456 try {
457 apkParentSession.open();
458 } catch (IOException e) {
459 Slog.e(TAG, "Unable to prepare multi-package session for staged session "
460 + session.sessionId);
461 return false;
462 }
463
464 for (PackageInstallerSession sessionToClone : childSessions) {
465 PackageInstallerSession apkChildSession = createAndWriteApkSession(sessionToClone);
466 if (apkChildSession == null) {
467 return false;
468 }
Dario Freni09660472019-02-18 16:44:23 +0000469 try {
470 apkParentSession.addChildSessionId(apkChildSession.sessionId);
471 } catch (RemoteException e) {
472 Slog.e(TAG, "Failed to add a child session for installing the APK files", e);
473 return false;
474 }
Dario Freni4b572c02019-01-29 09:40:31 +0000475 }
476 return commitApkSession(apkParentSession, session.sessionId);
477 }
478 // APEX single-package staged session, nothing to do.
479 return true;
Dario Freni61b3e252019-01-11 23:05:39 +0000480 }
481
Dario Freni276cd072019-01-09 11:13:44 +0000482 void commitSession(@NonNull PackageInstallerSession session) {
483 updateStoredSession(session);
484 mBgHandler.post(() -> preRebootVerification(session));
Dario Frenibe98c3f2018-12-22 15:25:27 +0000485 }
486
487 void createSession(@NonNull PackageInstallerSession sessionInfo) {
488 synchronized (mStagedSessions) {
489 mStagedSessions.append(sessionInfo.sessionId, sessionInfo);
490 }
491 }
492
Dario Freni015f9352019-01-14 21:56:17 +0000493 void abortSession(@NonNull PackageInstallerSession session) {
Dario Frenibe98c3f2018-12-22 15:25:27 +0000494 synchronized (mStagedSessions) {
Dario Freni015f9352019-01-14 21:56:17 +0000495 mStagedSessions.remove(session.sessionId);
Dario Frenibe98c3f2018-12-22 15:25:27 +0000496 }
497 }
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000498
shafik07205e32019-02-07 20:12:33 +0000499 void abortCommittedSession(@NonNull PackageInstallerSession session) {
500 if (session.isStagedSessionApplied()) {
501 Slog.w(TAG, "Cannot abort applied session!");
502 return;
503 }
shafik07205e32019-02-07 20:12:33 +0000504 abortSession(session);
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000505
506 boolean hasApex = sessionContainsApex(session);
507 if (hasApex) {
508 ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId);
509 if (apexSession == null || isApexSessionFinalized(apexSession)) {
510 Slog.w(TAG,
511 "Cannot abort session because it is not active or APEXD is not reachable");
512 return;
513 }
514 mApexManager.abortActiveSession();
515 }
shafik07205e32019-02-07 20:12:33 +0000516 }
517
Nikita Ioffee2d52f52019-03-11 14:29:23 +0000518 private boolean isApexSessionFinalized(ApexSessionInfo session) {
shafik07205e32019-02-07 20:12:33 +0000519 /* checking if the session is in a final state, i.e., not active anymore */
Nikita Ioffe82742222019-02-26 12:14:04 +0000520 return session.isUnknown || session.isActivationFailed || session.isSuccess
521 || session.isRolledBack;
522 }
523
524 private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) {
Nikita Ioffefb6688e2019-02-27 15:34:08 +0000525 // isRollbackInProgress is included to cover the scenario, when a device is rebooted in
526 // during the rollback, and apexd fails to resume the rollback after reboot.
Nikita Ioffe82742222019-02-26 12:14:04 +0000527 return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown
Nikita Ioffefb6688e2019-02-27 15:34:08 +0000528 || apexSessionInfo.isRolledBack || apexSessionInfo.isRollbackInProgress;
shafik07205e32019-02-07 20:12:33 +0000529 }
530
Dario Freni015f9352019-01-14 21:56:17 +0000531 @GuardedBy("mStagedSessions")
532 private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) {
533 // This method assumes that the argument is either a parent session of a multi-package
534 // i.e. isMultiPackage() returns true, or that it is a child session, i.e.
535 // hasParentSessionId() returns true.
536 if (session.isMultiPackage()) {
537 // Parent session of a multi-package group. Check that we restored all the children.
538 for (int childSession : session.getChildSessionIds()) {
539 if (mStagedSessions.get(childSession) == null) {
540 return false;
541 }
542 }
543 return true;
544 }
545 if (session.hasParentSessionId()) {
546 PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId());
547 if (parent == null) {
548 return false;
549 }
550 return isMultiPackageSessionComplete(parent);
551 }
552 Slog.wtf(TAG, "Attempting to restore an invalid multi-package session.");
553 return false;
554 }
555
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000556 void restoreSession(@NonNull PackageInstallerSession session) {
Dario Freni015f9352019-01-14 21:56:17 +0000557 PackageInstallerSession sessionToResume = session;
558 synchronized (mStagedSessions) {
559 mStagedSessions.append(session.sessionId, session);
560 // For multi-package sessions, we don't know in which order they will be restored. We
561 // need to wait until we have restored all the session in a group before restoring them.
562 if (session.isMultiPackage() || session.hasParentSessionId()) {
563 if (!isMultiPackageSessionComplete(session)) {
564 // Still haven't recovered all sessions of the group, return.
565 return;
566 }
567 // Group recovered, find the parent if necessary and resume the installation.
568 if (session.hasParentSessionId()) {
569 sessionToResume = mStagedSessions.get(session.getParentSessionId());
570 }
571 }
572 }
573 checkStateAndResume(sessionToResume);
574 }
575
576 private void checkStateAndResume(@NonNull PackageInstallerSession session) {
Dario Freni61b3e252019-01-11 23:05:39 +0000577 // Check the state of the session and decide what to do next.
578 if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
579 // Final states, nothing to do.
580 return;
581 }
582 if (!session.isStagedSessionReady()) {
583 // The framework got restarted before the pre-reboot verification could complete,
584 // restart the verification.
585 mBgHandler.post(() -> preRebootVerification(session));
586 } else {
587 // Session had already being marked ready. Start the checks to verify if there is any
588 // follow-up work.
Dario Freni4df010e2019-01-31 19:34:46 +0000589 resumeSession(session);
Dario Freni61b3e252019-01-11 23:05:39 +0000590 }
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000591 }
Dario Freni4b572c02019-01-29 09:40:31 +0000592
593 private static class LocalIntentReceiver {
594 private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
595
596 private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
597 @Override
598 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
599 IIntentReceiver finishedReceiver, String requiredPermission,
600 Bundle options) {
601 try {
602 mResult.offer(intent, 5, TimeUnit.SECONDS);
603 } catch (InterruptedException e) {
604 throw new RuntimeException(e);
605 }
606 }
607 };
608
609 public IntentSender getIntentSender() {
610 return new IntentSender((IIntentSender) mLocalSender);
611 }
612
613 public Intent getResult() {
614 try {
615 return mResult.take();
616 } catch (InterruptedException e) {
617 throw new RuntimeException(e);
618 }
619 }
620 }
Dario Frenibe98c3f2018-12-22 15:25:27 +0000621}