blob: fac383945afd22969f2d6772221fda62331510c2 [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;
Dario Freni4b572c02019-01-29 09:40:31 +000038import android.os.Bundle;
Narayan Kamath94c93ab2019-01-04 10:47:00 +000039import android.os.Handler;
Dario Freni4b572c02019-01-29 09:40:31 +000040import android.os.IBinder;
41import android.os.ParcelFileDescriptor;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000042import android.os.PowerManager;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000043import android.os.RemoteException;
44import android.os.ServiceManager;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000045import android.util.Slog;
Dario Frenibe98c3f2018-12-22 15:25:27 +000046import android.util.SparseArray;
Narayan Kamath9dfa6742019-01-04 14:22:50 +000047import android.util.apk.ApkSignatureVerifier;
Dario Frenibe98c3f2018-12-22 15:25:27 +000048
49import com.android.internal.annotations.GuardedBy;
Narayan Kamath94c93ab2019-01-04 10:47:00 +000050import com.android.internal.os.BackgroundThread;
Dario Frenibe98c3f2018-12-22 15:25:27 +000051
Dario Freni4b572c02019-01-29 09:40:31 +000052import java.io.File;
53import java.io.IOException;
Dario Frenibe98c3f2018-12-22 15:25:27 +000054import java.util.ArrayList;
Dario Freni015f9352019-01-14 21:56:17 +000055import java.util.Arrays;
Dario Frenibe98c3f2018-12-22 15:25:27 +000056import java.util.List;
Dario Freni4b572c02019-01-29 09:40:31 +000057import java.util.concurrent.LinkedBlockingQueue;
58import java.util.concurrent.TimeUnit;
Dario Freni015f9352019-01-14 21:56:17 +000059import java.util.stream.Collectors;
Dario Frenibe98c3f2018-12-22 15:25:27 +000060
61/**
62 * This class handles staged install sessions, i.e. install sessions that require packages to
63 * be installed only after a reboot.
64 */
65public class StagingManager {
66
67 private static final String TAG = "StagingManager";
68
Dario Freni4b572c02019-01-29 09:40:31 +000069 private final PackageInstallerService mPi;
Dario Frenibe98c3f2018-12-22 15:25:27 +000070 private final PackageManagerService mPm;
Dario Freni2e8dffc2019-02-06 14:55:16 +000071 private final ApexManager mApexManager;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000072 private final PowerManager mPowerManager;
Narayan Kamath94c93ab2019-01-04 10:47:00 +000073 private final Handler mBgHandler;
Dario Frenibe98c3f2018-12-22 15:25:27 +000074
Dario Frenibe98c3f2018-12-22 15:25:27 +000075 @GuardedBy("mStagedSessions")
76 private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
77
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000078 StagingManager(PackageManagerService pm, PackageInstallerService pi, ApexManager am,
79 Context context) {
Dario Frenibe98c3f2018-12-22 15:25:27 +000080 mPm = pm;
Dario Freni4b572c02019-01-29 09:40:31 +000081 mPi = pi;
Dario Freni2e8dffc2019-02-06 14:55:16 +000082 mApexManager = am;
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +000083 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Narayan Kamath94c93ab2019-01-04 10:47:00 +000084 mBgHandler = BackgroundThread.getHandler();
Dario Frenibe98c3f2018-12-22 15:25:27 +000085 }
86
87 private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
88 synchronized (mStagedSessions) {
89 PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId);
Narayan Kamath94c93ab2019-01-04 10:47:00 +000090 // storedSession might be null if a call to abortSession was made before the session
91 // is updated.
92 if (storedSession != null) {
93 mStagedSessions.put(sessionInfo.sessionId, sessionInfo);
Dario Frenibe98c3f2018-12-22 15:25:27 +000094 }
Dario Frenibe98c3f2018-12-22 15:25:27 +000095 }
96 }
97
98 ParceledListSlice<PackageInstaller.SessionInfo> getSessions() {
99 final List<PackageInstaller.SessionInfo> result = new ArrayList<>();
100 synchronized (mStagedSessions) {
101 for (int i = 0; i < mStagedSessions.size(); i++) {
102 result.add(mStagedSessions.valueAt(i).generateInfo(false));
103 }
104 }
105 return new ParceledListSlice<>(result);
106 }
107
Dario Freni2e8dffc2019-02-06 14:55:16 +0000108 private boolean validateApexSignature(String apexPath, String packageName) {
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000109 final SigningDetails signingDetails;
110 try {
111 signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
112 } catch (PackageParserException e) {
113 Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e);
114 return false;
115 }
116
Dario Freni2e8dffc2019-02-06 14:55:16 +0000117 final PackageInfo packageInfo = mApexManager.getActivePackage(packageName);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000118
Dario Freni2e8dffc2019-02-06 14:55:16 +0000119 if (packageInfo == null) {
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000120 // TODO: What is the right thing to do here ? This implies there's no active package
121 // with the given name. This should never be the case in production (where we only
122 // accept updates to existing APEXes) but may be required for testing.
123 return true;
124 }
125
126 final SigningDetails existingSigningDetails;
127 try {
128 existingSigningDetails = ApkSignatureVerifier.verify(
Dario Freni2e8dffc2019-02-06 14:55:16 +0000129 packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000130 } catch (PackageParserException e) {
Dario Freni2e8dffc2019-02-06 14:55:16 +0000131 Slog.e(TAG, "Unable to parse APEX package: "
132 + packageInfo.applicationInfo.sourceDir, e);
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000133 return false;
134 }
135
136 // Now that we have both sets of signatures, demand that they're an exact match.
137 if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) {
138 return true;
139 }
140
141 return false;
142 }
143
Dario Freni2e8dffc2019-02-06 14:55:16 +0000144 private boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
145 List<PackageInstallerSession> childSessions,
146 ApexInfoList apexInfoList) {
147 return mApexManager.submitStagedSession(
Dario Freni015f9352019-01-14 21:56:17 +0000148 session.sessionId,
149 childSessions != null
150 ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
151 new int[]{},
152 apexInfoList);
153 }
154
Dario Freni015f9352019-01-14 21:56:17 +0000155 private static boolean isApexSession(@NonNull PackageInstallerSession session) {
156 return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
157 }
158
Dario Freni61b3e252019-01-11 23:05:39 +0000159 private void preRebootVerification(@NonNull PackageInstallerSession session) {
Dario Freni276cd072019-01-09 11:13:44 +0000160 boolean success = true;
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000161
Dario Freni49c3fa72019-02-04 12:07:25 +0000162 // STOPSHIP: TODO(b/123753157): Verify APKs through Package Verifier.
Richard Uhler34ec3f12019-02-12 09:20:42 +0000163 // TODO: Decide whether we want to fail fast by detecting signature mismatches for APKs,
164 // right away.
Dario Freni4b572c02019-01-29 09:40:31 +0000165
Dario Freni015f9352019-01-14 21:56:17 +0000166 final ApexInfoList apexInfoList = new ApexInfoList();
167 // APEX checks. For single-package sessions, check if they contain an APEX. For
168 // multi-package sessions, find all the child sessions that contain an APEX.
169 if (!session.isMultiPackage()
170 && isApexSession(session)) {
171 success = submitSessionToApexService(session, null, apexInfoList);
Dario Freni275b4ab2019-01-25 09:55:16 +0000172
Dario Freni015f9352019-01-14 21:56:17 +0000173 } else if (session.isMultiPackage()) {
174 List<PackageInstallerSession> childSessions =
175 Arrays.stream(session.getChildSessionIds())
176 // Retrieve cached sessions matching ids.
177 .mapToObj(i -> mStagedSessions.get(i))
178 // Filter only the ones containing APEX.
179 .filter(childSession -> isApexSession(childSession))
180 .collect(Collectors.toList());
181 if (!childSessions.isEmpty()) {
182 success = submitSessionToApexService(session, childSessions, apexInfoList);
183 } // else this is a staged multi-package session with no APEX files.
184 }
Dario Freni276cd072019-01-09 11:13:44 +0000185
Dario Freni275b4ab2019-01-25 09:55:16 +0000186 if (!success) {
187 session.setStagedSessionFailed(
Dario Frenib6d28962019-01-31 15:52:24 +0000188 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Dario Freni275b4ab2019-01-25 09:55:16 +0000189 "APEX staging failed, check logcat messages from apexd for more details.");
Dario Frenife32b3f2019-02-07 17:52:11 +0000190 return;
Dario Freni275b4ab2019-01-25 09:55:16 +0000191 }
192
Dario Freni4b572c02019-01-29 09:40:31 +0000193 if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) {
Dario Freni015f9352019-01-14 21:56:17 +0000194 // For APEXes, we validate the signature here before we mark the session as ready,
195 // so we fail the session early if there is a signature mismatch. For APKs, the
196 // signature verification will be done by the package manager at the point at which
197 // it applies the staged install.
Dario Freni015f9352019-01-14 21:56:17 +0000198 for (ApexInfo apexPackage : apexInfoList.apexInfos) {
Dario Freni4b572c02019-01-29 09:40:31 +0000199 if (!validateApexSignature(apexPackage.packagePath,
Dario Freni015f9352019-01-14 21:56:17 +0000200 apexPackage.packageName)) {
Dario Frenib6d28962019-01-31 15:52:24 +0000201 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Dario Freni275b4ab2019-01-25 09:55:16 +0000202 "APK-container signature verification failed for package "
203 + apexPackage.packageName + ". Signature of file "
204 + apexPackage.packagePath + " does not match the signature of "
205 + " the package already installed.");
Dario Freni1473bcb2019-01-25 14:27:13 +0000206 // TODO(b/118865310): abort the session on apexd.
Dario Freni275b4ab2019-01-25 09:55:16 +0000207 return;
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000208 }
209 }
Dario Freni276cd072019-01-09 11:13:44 +0000210 }
Dario Freni1473bcb2019-01-25 14:27:13 +0000211
Narayan Kamathfcd4a042019-02-01 14:16:37 +0000212 if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
213 // If rollback is enabled for this session, we call through to the RollbackManager
214 // with the list of sessions it must enable rollback for. Note that notifyStagedSession
215 // is a synchronous operation.
216 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
217 ServiceManager.getService(Context.ROLLBACK_SERVICE));
218 try {
219 // NOTE: To stay consistent with the non-staged install flow, we don't fail the
220 // entire install if rollbacks can't be enabled.
221 if (!rm.notifyStagedSession(session.sessionId)) {
222 Slog.e(TAG, "Unable to enable rollback for session: " + session.sessionId);
223 }
224 } catch (RemoteException re) {
225 // Cannot happen, the rollback manager is in the same process.
226 }
227 }
228
Dario Freni275b4ab2019-01-25 09:55:16 +0000229 session.setStagedSessionReady();
Richard Uhler34ec3f12019-02-12 09:20:42 +0000230 if (sessionContainsApex(session)
231 && !mApexManager.markStagedSessionReady(session.sessionId)) {
Dario Frenib6d28962019-01-31 15:52:24 +0000232 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
Dario Freni1473bcb2019-01-25 14:27:13 +0000233 "APEX staging failed, check logcat messages from apexd for more "
234 + "details.");
235 }
Dario Freni276cd072019-01-09 11:13:44 +0000236 }
Narayan Kamath9dfa6742019-01-04 14:22:50 +0000237
Dario Freni4b572c02019-01-29 09:40:31 +0000238 private boolean sessionContainsApex(@NonNull PackageInstallerSession session) {
239 if (!session.isMultiPackage()) {
240 return isApexSession(session);
241 }
242 synchronized (mStagedSessions) {
243 return !(Arrays.stream(session.getChildSessionIds())
244 // Retrieve cached sessions matching ids.
245 .mapToObj(i -> mStagedSessions.get(i))
246 // Filter only the ones containing APEX.
247 .filter(childSession -> isApexSession(childSession))
248 .collect(Collectors.toList())
249 .isEmpty());
250 }
251 }
252
Dario Freni61b3e252019-01-11 23:05:39 +0000253 private void resumeSession(@NonNull PackageInstallerSession session) {
Nikita Ioffea820bd92019-02-15 14:22:44 +0000254 boolean hasApex = sessionContainsApex(session);
255 if (hasApex) {
Dario Freni2e8dffc2019-02-06 14:55:16 +0000256 // Check with apexservice whether the apex packages have been activated.
257 ApexSessionInfo apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
258 if (apexSessionInfo == null) {
259 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
260 "apexd did not know anything about a staged session supposed to be"
261 + "activated");
Dario Freni4b572c02019-01-29 09:40:31 +0000262 return;
263 }
Nikita Ioffe82742222019-02-26 12:14:04 +0000264 if (isApexSessionFailed(apexSessionInfo)) {
Dario Frenib6d28962019-01-31 15:52:24 +0000265 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
Dario Freni4b572c02019-01-29 09:40:31 +0000266 "APEX activation failed. Check logcat messages from apexd for "
267 + "more information.");
268 return;
269 }
270 if (apexSessionInfo.isVerified) {
271 // Session has been previously submitted to apexd, but didn't complete all the
272 // pre-reboot verification, perhaps because the device rebooted in the meantime.
273 // Greedily re-trigger the pre-reboot verification.
274 Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to be "
275 + "verified, resuming pre-reboot verification");
276 mBgHandler.post(() -> preRebootVerification(session));
277 return;
278 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000279 if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
Dario Freni4b572c02019-01-29 09:40:31 +0000280 // In all the remaining cases apexd will try to apply the session again at next
281 // boot. Nothing to do here for now.
282 Slog.w(TAG, "Staged session " + session.sessionId + " scheduled to be applied "
283 + "at boot didn't activate nor fail. This usually means that apexd will "
284 + "retry at next reboot.");
285 return;
286 }
287 }
288 // The APEX part of the session is activated, proceed with the installation of APKs.
289 if (!installApksInSession(session)) {
Dario Frenib6d28962019-01-31 15:52:24 +0000290 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
Dario Freni2e8dffc2019-02-06 14:55:16 +0000291 "Staged installation of APKs failed. Check logcat messages for"
292 + "more information.");
Nikita Ioffe39a6a5b2019-02-26 15:36:39 +0000293
294 if (!hasApex) {
295 return;
296 }
297
298 if (!mApexManager.abortActiveSession()) {
299 Slog.e(TAG, "Failed to abort APEXd session");
300 } else {
301 Slog.e(TAG,
302 "Successfully aborted apexd session. Rebooting device in order to revert "
303 + "to the previous state of APEXd.");
304 mPowerManager.reboot(null);
305 }
306
Dario Freni61b3e252019-01-11 23:05:39 +0000307 return;
308 }
Nikita Ioffea820bd92019-02-15 14:22:44 +0000309
Dario Freni4b572c02019-01-29 09:40:31 +0000310 session.setStagedSessionApplied();
Nikita Ioffea820bd92019-02-15 14:22:44 +0000311 if (hasApex) {
312 mApexManager.markStagedSessionSuccessful(session.sessionId);
313 }
Dario Freni4b572c02019-01-29 09:40:31 +0000314 }
315
Dario Freni815bd212019-02-20 14:09:36 +0000316 private List<String> findAPKsInDir(File stageDir) {
317 List<String> ret = new ArrayList<>();
Dario Freni4b572c02019-01-29 09:40:31 +0000318 if (stageDir != null && stageDir.exists()) {
319 for (File file : stageDir.listFiles()) {
320 if (file.getAbsolutePath().toLowerCase().endsWith(".apk")) {
Dario Freni815bd212019-02-20 14:09:36 +0000321 ret.add(file.getAbsolutePath());
Dario Freni4b572c02019-01-29 09:40:31 +0000322 }
323 }
Dario Freni61b3e252019-01-11 23:05:39 +0000324 }
Dario Freni815bd212019-02-20 14:09:36 +0000325 return ret;
Dario Freni4b572c02019-01-29 09:40:31 +0000326 }
327
328 private PackageInstallerSession createAndWriteApkSession(
329 @NonNull PackageInstallerSession originalSession) {
Dario Freni4b572c02019-01-29 09:40:31 +0000330 if (originalSession.stageDir == null) {
331 Slog.wtf(TAG, "Attempting to install a staged APK session with no staging dir");
332 return null;
Dario Freni1473bcb2019-01-25 14:27:13 +0000333 }
Dario Freni815bd212019-02-20 14:09:36 +0000334 List<String> apkFilePaths = findAPKsInDir(originalSession.stageDir);
335 if (apkFilePaths.isEmpty()) {
Dario Freni4b572c02019-01-29 09:40:31 +0000336 Slog.w(TAG, "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
337 return null;
Dario Freni61b3e252019-01-11 23:05:39 +0000338 }
Dario Freni4b572c02019-01-29 09:40:31 +0000339
340 PackageInstaller.SessionParams params = originalSession.params.copy();
341 params.isStaged = false;
Dario Freni49c3fa72019-02-04 12:07:25 +0000342 params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
Dario Freni4b572c02019-01-29 09:40:31 +0000343 int apkSessionId = mPi.createSession(
344 params, originalSession.getInstallerPackageName(), originalSession.userId);
345 PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
346
347 try {
348 apkSession.open();
Dario Freni815bd212019-02-20 14:09:36 +0000349 for (String apkFilePath : apkFilePaths) {
350 File apkFile = new File(apkFilePath);
351 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
352 ParcelFileDescriptor.MODE_READ_ONLY);
353 long sizeBytes = pfd.getStatSize();
354 if (sizeBytes < 0) {
355 Slog.e(TAG, "Unable to get size of: " + apkFilePath);
356 return null;
357 }
358 apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
Dario Freni4b572c02019-01-29 09:40:31 +0000359 }
Dario Freni4b572c02019-01-29 09:40:31 +0000360 } catch (IOException e) {
361 Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
362 return null;
363 }
364 return apkSession;
365 }
366
367 private boolean commitApkSession(@NonNull PackageInstallerSession apkSession,
368 int originalSessionId) {
Richard Uhler6fa7d132019-02-05 13:55:11 +0000369
370 if ((apkSession.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
371 // If rollback is available for this session, notify the rollback
372 // manager of the apk session so it can properly enable rollback.
373 final IRollbackManager rm = IRollbackManager.Stub.asInterface(
374 ServiceManager.getService(Context.ROLLBACK_SERVICE));
375 try {
376 rm.notifyStagedApkSession(originalSessionId, apkSession.sessionId);
377 } catch (RemoteException re) {
378 // Cannot happen, the rollback manager is in the same process.
379 }
380 }
381
Dario Freni4b572c02019-01-29 09:40:31 +0000382 final LocalIntentReceiver receiver = new LocalIntentReceiver();
383 apkSession.commit(receiver.getIntentSender(), false);
384 final Intent result = receiver.getResult();
385 final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
386 PackageInstaller.STATUS_FAILURE);
387 if (status == PackageInstaller.STATUS_SUCCESS) {
388 return true;
389 }
390 Slog.e(TAG, "Failure to install APK staged session " + originalSessionId + " ["
391 + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
392 return false;
393 }
394
395 private boolean installApksInSession(@NonNull PackageInstallerSession session) {
396 if (!session.isMultiPackage() && !isApexSession(session)) {
397 // APK single-packaged staged session. Do a regular install.
398 PackageInstallerSession apkSession = createAndWriteApkSession(session);
399 if (apkSession == null) {
400 return false;
401 }
402 return commitApkSession(apkSession, session.sessionId);
403 } else if (session.isMultiPackage()) {
404 // For multi-package staged sessions containing APKs, we identify which child sessions
405 // contain an APK, and with those then create a new multi-package group of sessions,
406 // carrying over all the session parameters and unmarking them as staged. On commit the
407 // sessions will be installed atomically.
408 List<PackageInstallerSession> childSessions;
409 synchronized (mStagedSessions) {
410 childSessions =
411 Arrays.stream(session.getChildSessionIds())
412 // Retrieve cached sessions matching ids.
413 .mapToObj(i -> mStagedSessions.get(i))
414 // Filter only the ones containing APKs.s
415 .filter(childSession -> !isApexSession(childSession))
416 .collect(Collectors.toList());
417 }
418 if (childSessions.isEmpty()) {
419 // APEX-only multi-package staged session, nothing to do.
420 return true;
421 }
422 PackageInstaller.SessionParams params = session.params.copy();
423 params.isStaged = false;
424 int apkParentSessionId = mPi.createSession(
425 params, session.getInstallerPackageName(), session.userId);
426 PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
427 try {
428 apkParentSession.open();
429 } catch (IOException e) {
430 Slog.e(TAG, "Unable to prepare multi-package session for staged session "
431 + session.sessionId);
432 return false;
433 }
434
435 for (PackageInstallerSession sessionToClone : childSessions) {
436 PackageInstallerSession apkChildSession = createAndWriteApkSession(sessionToClone);
437 if (apkChildSession == null) {
438 return false;
439 }
Dario Freni09660472019-02-18 16:44:23 +0000440 try {
441 apkParentSession.addChildSessionId(apkChildSession.sessionId);
442 } catch (RemoteException e) {
443 Slog.e(TAG, "Failed to add a child session for installing the APK files", e);
444 return false;
445 }
Dario Freni4b572c02019-01-29 09:40:31 +0000446 }
447 return commitApkSession(apkParentSession, session.sessionId);
448 }
449 // APEX single-package staged session, nothing to do.
450 return true;
Dario Freni61b3e252019-01-11 23:05:39 +0000451 }
452
Dario Freni276cd072019-01-09 11:13:44 +0000453 void commitSession(@NonNull PackageInstallerSession session) {
454 updateStoredSession(session);
455 mBgHandler.post(() -> preRebootVerification(session));
Dario Frenibe98c3f2018-12-22 15:25:27 +0000456 }
457
458 void createSession(@NonNull PackageInstallerSession sessionInfo) {
459 synchronized (mStagedSessions) {
460 mStagedSessions.append(sessionInfo.sessionId, sessionInfo);
461 }
462 }
463
Dario Freni015f9352019-01-14 21:56:17 +0000464 void abortSession(@NonNull PackageInstallerSession session) {
Dario Frenibe98c3f2018-12-22 15:25:27 +0000465 synchronized (mStagedSessions) {
Dario Freni015f9352019-01-14 21:56:17 +0000466 mStagedSessions.remove(session.sessionId);
Dario Frenibe98c3f2018-12-22 15:25:27 +0000467 }
468 }
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000469
shafik07205e32019-02-07 20:12:33 +0000470 void abortCommittedSession(@NonNull PackageInstallerSession session) {
471 if (session.isStagedSessionApplied()) {
472 Slog.w(TAG, "Cannot abort applied session!");
473 return;
474 }
475 if (isStagedSessionFinalized(session.sessionId)) {
476 Slog.w(TAG, "Cannot abort session because it is not active or APEXD is not reachable");
477 return;
478 }
479
480 mApexManager.abortActiveSession();
481
482 abortSession(session);
483 }
484
485 private boolean isStagedSessionFinalized(int sessionId) {
486 ApexSessionInfo session = mApexManager.getStagedSessionInfo(sessionId);
487
488 /* checking if the session is in a final state, i.e., not active anymore */
Nikita Ioffe82742222019-02-26 12:14:04 +0000489 return session.isUnknown || session.isActivationFailed || session.isSuccess
490 || session.isRolledBack;
491 }
492
493 private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) {
494 return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown
495 || apexSessionInfo.isRolledBack;
shafik07205e32019-02-07 20:12:33 +0000496 }
497
Dario Freni015f9352019-01-14 21:56:17 +0000498 @GuardedBy("mStagedSessions")
499 private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) {
500 // This method assumes that the argument is either a parent session of a multi-package
501 // i.e. isMultiPackage() returns true, or that it is a child session, i.e.
502 // hasParentSessionId() returns true.
503 if (session.isMultiPackage()) {
504 // Parent session of a multi-package group. Check that we restored all the children.
505 for (int childSession : session.getChildSessionIds()) {
506 if (mStagedSessions.get(childSession) == null) {
507 return false;
508 }
509 }
510 return true;
511 }
512 if (session.hasParentSessionId()) {
513 PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId());
514 if (parent == null) {
515 return false;
516 }
517 return isMultiPackageSessionComplete(parent);
518 }
519 Slog.wtf(TAG, "Attempting to restore an invalid multi-package session.");
520 return false;
521 }
522
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000523 void restoreSession(@NonNull PackageInstallerSession session) {
Dario Freni015f9352019-01-14 21:56:17 +0000524 PackageInstallerSession sessionToResume = session;
525 synchronized (mStagedSessions) {
526 mStagedSessions.append(session.sessionId, session);
527 // For multi-package sessions, we don't know in which order they will be restored. We
528 // need to wait until we have restored all the session in a group before restoring them.
529 if (session.isMultiPackage() || session.hasParentSessionId()) {
530 if (!isMultiPackageSessionComplete(session)) {
531 // Still haven't recovered all sessions of the group, return.
532 return;
533 }
534 // Group recovered, find the parent if necessary and resume the installation.
535 if (session.hasParentSessionId()) {
536 sessionToResume = mStagedSessions.get(session.getParentSessionId());
537 }
538 }
539 }
540 checkStateAndResume(sessionToResume);
541 }
542
543 private void checkStateAndResume(@NonNull PackageInstallerSession session) {
Dario Freni61b3e252019-01-11 23:05:39 +0000544 // Check the state of the session and decide what to do next.
545 if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
546 // Final states, nothing to do.
547 return;
548 }
549 if (!session.isStagedSessionReady()) {
550 // The framework got restarted before the pre-reboot verification could complete,
551 // restart the verification.
552 mBgHandler.post(() -> preRebootVerification(session));
553 } else {
554 // Session had already being marked ready. Start the checks to verify if there is any
555 // follow-up work.
Dario Freni4df010e2019-01-31 19:34:46 +0000556 resumeSession(session);
Dario Freni61b3e252019-01-11 23:05:39 +0000557 }
Dario Freni8e7d0ec2019-01-10 15:21:40 +0000558 }
Dario Freni4b572c02019-01-29 09:40:31 +0000559
560 private static class LocalIntentReceiver {
561 private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
562
563 private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
564 @Override
565 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
566 IIntentReceiver finishedReceiver, String requiredPermission,
567 Bundle options) {
568 try {
569 mResult.offer(intent, 5, TimeUnit.SECONDS);
570 } catch (InterruptedException e) {
571 throw new RuntimeException(e);
572 }
573 }
574 };
575
576 public IntentSender getIntentSender() {
577 return new IntentSender((IIntentSender) mLocalSender);
578 }
579
580 public Intent getResult() {
581 try {
582 return mResult.take();
583 } catch (InterruptedException e) {
584 throw new RuntimeException(e);
585 }
586 }
587 }
Dario Frenibe98c3f2018-12-22 15:25:27 +0000588}