1 /*
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 
17 package com.android.server.rollback;
18 
19 import static com.android.server.rollback.RollbackManagerServiceImpl.sendFailure;
20 
21 import android.Manifest;
22 import android.annotation.AnyThread;
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.WorkerThread;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentSender;
30 import android.content.pm.Flags;
31 import android.content.pm.PackageInstaller;
32 import android.content.pm.PackageManager;
33 import android.content.pm.PackageManagerInternal;
34 import android.content.pm.VersionedPackage;
35 import android.content.rollback.PackageRollbackInfo;
36 import android.content.rollback.RollbackInfo;
37 import android.content.rollback.RollbackManager;
38 import android.os.Binder;
39 import android.os.Handler;
40 import android.os.Looper;
41 import android.os.ParcelFileDescriptor;
42 import android.os.UserHandle;
43 import android.os.UserManager;
44 import android.os.ext.SdkExtensions;
45 import android.text.TextUtils;
46 import android.util.ArraySet;
47 import android.util.Slog;
48 import android.util.SparseIntArray;
49 
50 import com.android.internal.annotations.VisibleForTesting;
51 import com.android.internal.util.ArrayUtils;
52 import com.android.internal.util.IndentingPrintWriter;
53 import com.android.internal.util.Preconditions;
54 import com.android.server.LocalServices;
55 import com.android.server.RescueParty;
56 import com.android.server.pm.pkg.AndroidPackage;
57 
58 import java.io.File;
59 import java.io.IOException;
60 import java.lang.annotation.Retention;
61 import java.lang.annotation.RetentionPolicy;
62 import java.text.ParseException;
63 import java.time.Instant;
64 import java.util.ArrayList;
65 import java.util.List;
66 import java.util.Objects;
67 import java.util.Set;
68 import java.util.function.Consumer;
69 
70 /**
71  * Information about a rollback available for a set of atomically installed packages.
72  *
73  * Threading model:
74  *
75  * Each method falls into one of the 2 categories:
76  * - @AnyThread annotates thread-safe methods.
77  * - @WorkerThread annotates methods that should be called from the worker thread only.
78  *
79  * In production code, the constructor is called on the worker thread of
80  * {@link RollbackManagerServiceImpl}. All method invocations should happen on this thread.
81  * Violation of thread invariants will trigger exceptions. In the case of unit tests, it is up to
82  * the tests to serialize all method calls to avoid race condition. No thread invariants are
83  * enforced in this case.
84  */
85 class Rollback {
86 
87     private static final String TAG = "RollbackManager";
88 
89     @IntDef(prefix = { "ROLLBACK_STATE_" }, value = {
90             ROLLBACK_STATE_ENABLING,
91             ROLLBACK_STATE_AVAILABLE,
92             ROLLBACK_STATE_COMMITTED,
93             ROLLBACK_STATE_DELETED
94     })
95     @Retention(RetentionPolicy.SOURCE)
96     @interface RollbackState {}
97 
98     /**
99      * The rollback is in the process of being enabled. It is not yet
100      * available for use.
101      */
102     static final int ROLLBACK_STATE_ENABLING = 0;
103 
104     /**
105      * The rollback is currently available.
106      */
107     static final int ROLLBACK_STATE_AVAILABLE = 1;
108 
109     /**
110      * The rollback has been committed.
111      */
112     static final int ROLLBACK_STATE_COMMITTED = 3;
113 
114     /**
115      * The rollback has been deleted.
116      */
117     static final int ROLLBACK_STATE_DELETED = 4;
118 
119     /**
120      * The session ID associate with this rollback. This is the parent session ID in the case of
121      * a multi-package session.
122      */
123     private final int mOriginalSessionId;
124 
125     /**
126      * The rollback info for this rollback.
127      */
128     public final RollbackInfo info;
129 
130     /**
131      * The directory where the rollback data is stored.
132      */
133     private final File mBackupDir;
134 
135     /**
136      * The time when the upgrade occurred, for purposes of expiring
137      * rollback data.
138      *
139      * The timestamp is not applicable for all rollback states, but we make
140      * sure to keep it non-null to avoid potential errors there.
141      */
142     private @NonNull Instant mTimestamp;
143 
144     /**
145      * The current state of the rollback.
146      * ENABLING, AVAILABLE, DELETED, or COMMITTED.
147      */
148     private @RollbackState int mState;
149 
150     /**
151      * The detailed description of the current state. For a DELETED state, it describes
152      * the reason why the rollback is deleted.
153      */
154     private @NonNull String mStateDescription = "";
155 
156     /**
157      * True if we are expecting the package manager to call restoreUserData
158      * for this rollback because it has just been committed but the rollback
159      * has not yet been fully applied.
160      */
161     private boolean mRestoreUserDataInProgress = false;
162 
163     /**
164      * The user that performed the install with rollback enabled.
165      */
166     private final int mUserId;
167 
168     /**
169      * The installer package name from the install session that enabled the rollback. May be null if
170      * that session did not set this value.
171      *
172      * If this is an empty string then the installer package name will be resolved by
173      * PackageManager.
174      */
175     @Nullable private final String mInstallerPackageName;
176 
177     /**
178      * Time after which rollback expires.
179      */
180     private long mRollbackLifetimeMillis = 0;
181 
182     /**
183      * Session ids for all packages in the install. For multi-package sessions, this is the list
184      * of child session ids. For normal sessions, this list is a single element with the normal
185      * session id.
186      */
187     private final int[] mPackageSessionIds;
188 
189     /**
190      * The extension versions supported at the time of rollback creation.
191      */
192     @NonNull private final SparseIntArray mExtensionVersions;
193 
194     /**
195      * The worker thread on which all method invocations should happen. It might be null in the
196      * case of unit tests where no thread invariants are enforced.
197      */
198     @Nullable private final Handler mHandler;
199 
200     /**
201      * Constructs a new, empty Rollback instance.
202      *
203      * @param rollbackId the id of the rollback.
204      * @param backupDir the directory where the rollback data is stored.
205      * @param originalSessionId the session id associated with this rollback.
206      * @param isStaged true if this is a staged rollback.
207      * @param userId the user that performed the install with rollback enabled.
208      * @param installerPackageName the installer package name from the original install session.
209      * @param packageSessionIds the session ids for all packages in the install.
210      * @param extensionVersions the extension versions supported at the time of rollback creation
211      */
Rollback(int rollbackId, File backupDir, int originalSessionId, boolean isStaged, int userId, String installerPackageName, int[] packageSessionIds, SparseIntArray extensionVersions)212     Rollback(int rollbackId, File backupDir, int originalSessionId, boolean isStaged, int userId,
213             String installerPackageName, int[] packageSessionIds,
214             SparseIntArray extensionVersions) {
215         this.info = new RollbackInfo(rollbackId,
216                 /* packages */ new ArrayList<>(),
217                 /* isStaged */ isStaged,
218                 /* causePackages */ new ArrayList<>(),
219                 /* committedSessionId */ -1,
220                 /* rollbackImpactLevel */ PackageManager.ROLLBACK_USER_IMPACT_LOW);
221         mUserId = userId;
222         mInstallerPackageName = installerPackageName;
223         mBackupDir = backupDir;
224         mOriginalSessionId = originalSessionId;
225         mState = ROLLBACK_STATE_ENABLING;
226         mTimestamp = Instant.now();
227         mPackageSessionIds = packageSessionIds != null ? packageSessionIds : new int[0];
228         mExtensionVersions = Objects.requireNonNull(extensionVersions);
229         mHandler = Looper.myLooper() != null ? new Handler(Looper.myLooper()) : null;
230     }
231 
232     /**
233      * Constructs a pre-populated Rollback instance.
234      */
Rollback(RollbackInfo info, File backupDir, Instant timestamp, int originalSessionId, @RollbackState int state, String stateDescription, boolean restoreUserDataInProgress, int userId, String installerPackageName, SparseIntArray extensionVersions)235     Rollback(RollbackInfo info, File backupDir, Instant timestamp, int originalSessionId,
236             @RollbackState int state, String stateDescription, boolean restoreUserDataInProgress,
237             int userId, String installerPackageName, SparseIntArray extensionVersions) {
238         this.info = info;
239         mUserId = userId;
240         mInstallerPackageName = installerPackageName;
241         mBackupDir = backupDir;
242         mTimestamp = timestamp;
243         mOriginalSessionId = originalSessionId;
244         mState = state;
245         mStateDescription = stateDescription;
246         mRestoreUserDataInProgress = restoreUserDataInProgress;
247         mExtensionVersions = Objects.requireNonNull(extensionVersions);
248         // TODO(b/120200473): Include this field during persistence. This field will be used to
249         // decide which rollback to expire when ACTION_PACKAGE_REPLACED is received. Note persisting
250         // this field is not backward compatible. We won't fix b/120200473 until S to minimize the
251         // impact.
252         mPackageSessionIds = new int[0];
253         mHandler = Looper.myLooper() != null ? new Handler(Looper.myLooper()) : null;
254     }
255 
assertInWorkerThread()256     private void assertInWorkerThread() {
257         Preconditions.checkState(mHandler == null || mHandler.getLooper().isCurrentThread());
258     }
259 
260     /**
261      * Whether the rollback is for rollback of a staged install.
262      */
263     @AnyThread
isStaged()264     boolean isStaged() {
265         return info.isStaged();
266     }
267 
268     /**
269      * Returns the directory in which rollback data should be stored.
270      */
271     @AnyThread
getBackupDir()272     File getBackupDir() {
273         return mBackupDir;
274     }
275 
276     /**
277      * Returns the time when the upgrade occurred, for purposes of expiring rollback data.
278      */
279     @WorkerThread
getTimestamp()280     Instant getTimestamp() {
281         assertInWorkerThread();
282         return mTimestamp;
283     }
284 
285     /**
286      * Sets the time at which upgrade occurred.
287      */
288     @WorkerThread
setTimestamp(Instant timestamp)289     void setTimestamp(Instant timestamp) {
290         assertInWorkerThread();
291         mTimestamp = timestamp;
292         RollbackStore.saveRollback(this);
293     }
294 
295     /**
296      * Sets rollback lifetime in milliseconds, for purposes of expiring rollback data.
297      */
298     @WorkerThread
setRollbackLifetimeMillis(long lifetimeMillis)299     void setRollbackLifetimeMillis(long lifetimeMillis) {
300         assertInWorkerThread();
301         mRollbackLifetimeMillis = lifetimeMillis;
302     }
303 
304     /**
305      * Returns rollback lifetime in milliseconds, for purposes of expiring rollback data.
306      */
307     @WorkerThread
getRollbackLifetimeMillis()308     long getRollbackLifetimeMillis() {
309         assertInWorkerThread();
310         return mRollbackLifetimeMillis;
311     }
312 
313     /**
314      * Returns the session ID associated with this rollback, or {@code -1} if unknown.
315      */
316     @AnyThread
getOriginalSessionId()317     int getOriginalSessionId() {
318         return mOriginalSessionId;
319     }
320 
321     /**
322      * Returns the ID of the user that performed the install with rollback enabled.
323      */
324     @AnyThread
getUserId()325     int getUserId() {
326         return mUserId;
327     }
328 
329     /**
330      * Returns the installer package name from the install session that enabled the rollback. In the
331      * case that this is called on a rollback from an older version, returns the empty string.
332      */
333     @AnyThread
getInstallerPackageName()334     @Nullable String getInstallerPackageName() {
335         return mInstallerPackageName;
336     }
337 
338     /**
339      * Returns the extension versions that were supported at the time that the rollback was created,
340      * as a mapping from SdkVersion to ExtensionVersion.
341      */
342     @AnyThread
getExtensionVersions()343     SparseIntArray getExtensionVersions() {
344         return mExtensionVersions;
345     }
346 
347     /**
348      * Returns true if the rollback is in the ENABLING state.
349      */
350     @WorkerThread
isEnabling()351     boolean isEnabling() {
352         assertInWorkerThread();
353         return mState == ROLLBACK_STATE_ENABLING;
354     }
355 
356     /**
357      * Returns true if the rollback is in the AVAILABLE state.
358      */
359     @WorkerThread
isAvailable()360     boolean isAvailable() {
361         assertInWorkerThread();
362         return mState == ROLLBACK_STATE_AVAILABLE;
363     }
364 
365     /**
366      * Returns true if the rollback is in the COMMITTED state.
367      */
368     @WorkerThread
isCommitted()369     boolean isCommitted() {
370         assertInWorkerThread();
371         return mState == ROLLBACK_STATE_COMMITTED;
372     }
373 
374     /**
375      * Returns true if the rollback is in the DELETED state.
376      */
377     @WorkerThread
isDeleted()378     boolean isDeleted() {
379         assertInWorkerThread();
380         return mState == ROLLBACK_STATE_DELETED;
381     }
382 
383     /**
384      * Saves this rollback to persistent storage.
385      */
386     @WorkerThread
saveRollback()387     void saveRollback() {
388         assertInWorkerThread();
389         RollbackStore.saveRollback(this);
390     }
391 
392     /**
393      * Enables this rollback for the provided package.
394      *
395      * @return boolean True if the rollback was enabled successfully for the specified package.
396      */
397     @WorkerThread
enableForPackage(String packageName, long newVersion, long installedVersion, boolean isApex, String sourceDir, String[] splitSourceDirs, int rollbackDataPolicy, @PackageManager.RollbackImpactLevel int rollbackImpactLevel)398     boolean enableForPackage(String packageName, long newVersion, long installedVersion,
399             boolean isApex, String sourceDir, String[] splitSourceDirs, int rollbackDataPolicy,
400             @PackageManager.RollbackImpactLevel int rollbackImpactLevel) {
401         assertInWorkerThread();
402         try {
403             RollbackStore.backupPackageCodePath(this, packageName, sourceDir);
404             if (!ArrayUtils.isEmpty(splitSourceDirs)) {
405                 for (String dir : splitSourceDirs) {
406                     RollbackStore.backupPackageCodePath(this, packageName, dir);
407                 }
408             }
409         } catch (IOException e) {
410             Slog.e(TAG, "Unable to copy package for rollback for " + packageName, e);
411             return false;
412         }
413 
414         PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
415                 new VersionedPackage(packageName, newVersion),
416                 new VersionedPackage(packageName, installedVersion),
417                 new ArrayList<>() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
418                 isApex, false /* isApkInApex */, new ArrayList<>(), rollbackDataPolicy);
419 
420         info.getPackages().add(packageRollbackInfo);
421 
422         if (info.getRollbackImpactLevel() < rollbackImpactLevel) {
423             info.setRollbackImpactLevel(rollbackImpactLevel);
424         }
425         return true;
426     }
427 
428     /**
429      * Enables this rollback for the provided apk-in-apex.
430      *
431      * @return boolean True if the rollback was enabled successfully for the specified package.
432      */
433     @WorkerThread
enableForPackageInApex(String packageName, long installedVersion, int rollbackDataPolicy)434     boolean enableForPackageInApex(String packageName, long installedVersion,
435             int rollbackDataPolicy) {
436         assertInWorkerThread();
437         // TODO(b/147666157): Extract the new version number of apk-in-apex
438         // The new version for the apk-in-apex is set to 0 for now. If the package is then further
439         // updated via non-staged install flow, then RollbackManagerServiceImpl#onPackageReplaced()
440         // will be called and this rollback will be deleted. Other ways of package update have not
441         // been handled yet.
442         PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
443                 new VersionedPackage(packageName, 0 /* newVersion */),
444                 new VersionedPackage(packageName, installedVersion),
445                 new ArrayList<>() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
446                 false /* isApex */, true /* isApkInApex */, new ArrayList<>(), rollbackDataPolicy);
447         info.getPackages().add(packageRollbackInfo);
448         return true;
449     }
450 
addAll(List<Integer> list, int[] arr)451     private static void addAll(List<Integer> list, int[] arr) {
452         for (int i = 0; i < arr.length; ++i) {
453             list.add(arr[i]);
454         }
455     }
456 
457     /**
458      * Snapshots user data for the provided package and user ids. Does nothing if this rollback is
459      * not in the ENABLING state.
460      */
461     @WorkerThread
snapshotUserData(String packageName, int[] userIds, AppDataRollbackHelper dataHelper)462     void snapshotUserData(String packageName, int[] userIds, AppDataRollbackHelper dataHelper) {
463         assertInWorkerThread();
464         if (!isEnabling()) {
465             return;
466         }
467 
468         for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
469             if (pkgRollbackInfo.getPackageName().equals(packageName)) {
470                 if (pkgRollbackInfo.getRollbackDataPolicy()
471                         == PackageManager.ROLLBACK_DATA_POLICY_RESTORE) {
472                     dataHelper.snapshotAppData(info.getRollbackId(), pkgRollbackInfo, userIds);
473                     addAll(pkgRollbackInfo.getSnapshottedUsers(), userIds);
474                     RollbackStore.saveRollback(this);
475                 }
476                 break;
477             }
478         }
479     }
480 
481     /**
482      * Commits the pending backups and restores for a given {@code userId}. If this rollback has a
483      * pending backup, it is updated with a mapping from {@code userId} to inode of the CE user data
484      * snapshot.
485      */
486     @WorkerThread
commitPendingBackupAndRestoreForUser(int userId, AppDataRollbackHelper dataHelper)487     void commitPendingBackupAndRestoreForUser(int userId, AppDataRollbackHelper dataHelper) {
488         assertInWorkerThread();
489         if (dataHelper.commitPendingBackupAndRestoreForUser(userId, this)) {
490             RollbackStore.saveRollback(this);
491         }
492     }
493 
494     /**
495      * Changes the state of the rollback to AVAILABLE. This also changes the timestamp to the
496      * current time and saves the rollback. Does nothing if this rollback is already in the
497      * DELETED state.
498      */
499     @WorkerThread
makeAvailable()500     void makeAvailable() {
501         assertInWorkerThread();
502         if (isDeleted()) {
503             Slog.w(TAG, "Cannot make deleted rollback available.");
504             return;
505         }
506         setState(ROLLBACK_STATE_AVAILABLE, "");
507         mTimestamp = Instant.now();
508         RollbackStore.saveRollback(this);
509     }
510 
511     /**
512      * Commits the rollback.
513      */
514     @WorkerThread
commit(final Context context, List<VersionedPackage> causePackages, String callerPackageName, IntentSender statusReceiver)515     void commit(final Context context, List<VersionedPackage> causePackages,
516             String callerPackageName, IntentSender statusReceiver) {
517         assertInWorkerThread();
518         if (!isAvailable()) {
519             sendFailure(context, statusReceiver,
520                     RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
521                     "Rollback unavailable");
522             return;
523         }
524 
525         if (containsApex() && wasCreatedAtLowerExtensionVersion()) {
526             PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
527             if (extensionVersionReductionWouldViolateConstraint(mExtensionVersions, pmi)) {
528                 sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
529                         "Rollback may violate a minExtensionVersion constraint");
530                 return;
531             }
532         }
533 
534         // Get a context to use to install the downgraded version of the package.
535         Context pkgContext;
536         try {
537             pkgContext = context.createPackageContextAsUser(callerPackageName, 0,
538                     UserHandle.of(mUserId));
539         } catch (PackageManager.NameNotFoundException e) {
540             sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
541                     "Invalid callerPackageName");
542             return;
543         }
544 
545         PackageManager pm = pkgContext.getPackageManager();
546         try {
547             PackageInstaller packageInstaller = pm.getPackageInstaller();
548             PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
549                     PackageInstaller.SessionParams.MODE_FULL_INSTALL);
550             parentParams.setRequestDowngrade(true);
551             parentParams.setMultiPackage();
552             if (isStaged()) {
553                 parentParams.setStaged();
554             }
555             parentParams.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);
556 
557             int parentSessionId = packageInstaller.createSession(parentParams);
558             PackageInstaller.Session parentSession = packageInstaller.openSession(
559                     parentSessionId);
560 
561             List<String> packageNames = new ArrayList<>(info.getPackages().size());
562             for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
563                 packageNames.add(pkgRollbackInfo.getPackageName());
564 
565                 if (pkgRollbackInfo.isApkInApex()) {
566                     // No need to issue a downgrade install request for apk-in-apex. It will
567                     // be rolled back when its parent apex is downgraded.
568                     continue;
569                 }
570                 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
571                         PackageInstaller.SessionParams.MODE_FULL_INSTALL);
572                 String installerPackageName = mInstallerPackageName;
573                 if (TextUtils.isEmpty(mInstallerPackageName)) {
574                     installerPackageName = pm.getInstallerPackageName(
575                             pkgRollbackInfo.getPackageName());
576                 }
577                 if (installerPackageName != null) {
578                     params.setInstallerPackageName(installerPackageName);
579                 }
580                 params.setRequestDowngrade(true);
581                 params.setRequiredInstalledVersionCode(
582                         pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode());
583                 params.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);
584                 if (isStaged()) {
585                     params.setStaged();
586                 }
587                 if (pkgRollbackInfo.isApex()) {
588                     params.setInstallAsApex();
589                 }
590                 int sessionId = packageInstaller.createSession(params);
591                 PackageInstaller.Session session = packageInstaller.openSession(sessionId);
592                 File[] packageCodePaths = RollbackStore.getPackageCodePaths(
593                         this, pkgRollbackInfo.getPackageName());
594                 if (packageCodePaths == null) {
595                     sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
596                             "Backup copy of package: "
597                                     + pkgRollbackInfo.getPackageName() + " is inaccessible");
598                     return;
599                 }
600 
601                 for (File packageCodePath : packageCodePaths) {
602                     try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
603                             ParcelFileDescriptor.MODE_READ_ONLY)) {
604                         final long token = Binder.clearCallingIdentity();
605                         try {
606                             boolean fallbackToCopy = false;
607                             try {
608                                 // Populate apk/apex files using hard links to avoid copy
609                                 session.stageViaHardLink(packageCodePath.getAbsolutePath());
610                             } catch (Exception ignore) {
611                                 fallbackToCopy = true;
612                             }
613                             if (fallbackToCopy) {
614                                 session.write(packageCodePath.getName(), 0,
615                                         packageCodePath.length(),
616                                         fd);
617                             }
618                         } finally {
619                             Binder.restoreCallingIdentity(token);
620                         }
621                     }
622                 }
623                 parentSession.addChildSessionId(sessionId);
624             }
625 
626             // Clear flags.
627             RescueParty.resetDeviceConfigForPackages(packageNames);
628 
629             Consumer<Intent> onResult = result -> {
630                 mHandler.post(() -> {
631                     assertInWorkerThread();
632                     int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
633                             PackageInstaller.STATUS_FAILURE);
634                     if (status != PackageInstaller.STATUS_SUCCESS) {
635                         // Committing the rollback failed, but we still have all the info we
636                         // need to try rolling back again, so restore the rollback state to
637                         // how it was before we tried committing.
638                         // TODO: Should we just kill this rollback if commit failed?
639                         // Why would we expect commit not to fail again?
640                         // TODO: Could this cause a rollback to be resurrected
641                         // if it should otherwise have expired by now?
642                         setState(ROLLBACK_STATE_AVAILABLE, "Commit failed");
643                         mRestoreUserDataInProgress = false;
644                         info.setCommittedSessionId(-1);
645                         sendFailure(context, statusReceiver,
646                                 RollbackManager.STATUS_FAILURE_INSTALL,
647                                 "Rollback downgrade install failed: "
648                                         + result.getStringExtra(
649                                         PackageInstaller.EXTRA_STATUS_MESSAGE));
650                         return;
651                     }
652 
653                     if (!isStaged()) {
654                         // All calls to restoreUserData should have
655                         // completed by now for a non-staged install.
656                         mRestoreUserDataInProgress = false;
657                     }
658 
659                     info.getCausePackages().addAll(causePackages);
660                     RollbackStore.deletePackageCodePaths(this);
661                     RollbackStore.saveRollback(this);
662 
663                     // Send success.
664                     try {
665                         final Intent fillIn = new Intent();
666                         fillIn.putExtra(
667                                 RollbackManager.EXTRA_STATUS,
668                                 RollbackManager.STATUS_SUCCESS);
669                         statusReceiver.sendIntent(context, 0, fillIn, null, null);
670                     } catch (IntentSender.SendIntentException e) {
671                         // Nowhere to send the result back to, so don't bother.
672                     }
673 
674                     Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
675 
676                     UserManager userManager = context.getSystemService(UserManager.class);
677                     for (UserHandle user : userManager.getUserHandles(true)) {
678                         context.sendBroadcastAsUser(broadcast,
679                                 user,
680                                 Manifest.permission.MANAGE_ROLLBACKS);
681                     }
682                 });
683             };
684 
685             final LocalIntentReceiver receiver = new LocalIntentReceiver(onResult);
686             setState(ROLLBACK_STATE_COMMITTED, "");
687             info.setCommittedSessionId(parentSessionId);
688             mRestoreUserDataInProgress = true;
689             parentSession.commit(receiver.getIntentSender());
690         } catch (IOException e) {
691             Slog.e(TAG, "Rollback failed", e);
692             sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
693                     "IOException: " + e.toString());
694         }
695     }
696 
697     /**
698      * Restores user data for the specified package if this rollback is currently marked as
699      * having a restore in progress.
700      *
701      * @return boolean True if this rollback has a restore in progress and contains the specified
702      * package.
703      */
704     @WorkerThread
restoreUserDataForPackageIfInProgress(String packageName, int[] userIds, int appId, String seInfo, AppDataRollbackHelper dataHelper)705     boolean restoreUserDataForPackageIfInProgress(String packageName, int[] userIds, int appId,
706             String seInfo, AppDataRollbackHelper dataHelper) {
707         assertInWorkerThread();
708         if (!isRestoreUserDataInProgress()) {
709             return false;
710         }
711 
712         boolean foundPackage = false;
713         for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
714             if (pkgRollbackInfo.getPackageName().equals(packageName)) {
715                 foundPackage = true;
716                 boolean changedRollback = false;
717                 for (int userId : userIds) {
718                     changedRollback |= dataHelper.restoreAppData(
719                             info.getRollbackId(), pkgRollbackInfo, userId, appId, seInfo);
720                 }
721                 // We've updated metadata about this rollback, so save it to flash.
722                 if (changedRollback) {
723                     RollbackStore.saveRollback(this);
724                 }
725                 break;
726             }
727         }
728         return foundPackage;
729     }
730 
731     /**
732      * Deletes app data snapshots associated with this rollback, and moves to the DELETED state.
733      */
734     @WorkerThread
delete(AppDataRollbackHelper dataHelper, @NonNull String reason)735     void delete(AppDataRollbackHelper dataHelper, @NonNull String reason) {
736         assertInWorkerThread();
737         boolean containsApex = false;
738         Set<Integer> apexUsers = new ArraySet<>();
739         for (PackageRollbackInfo pkgInfo : info.getPackages()) {
740             List<Integer> snapshottedUsers = pkgInfo.getSnapshottedUsers();
741             if (pkgInfo.isApex()) {
742                 containsApex = true;
743                 apexUsers.addAll(snapshottedUsers);
744             } else {
745                 for (int i = 0; i < snapshottedUsers.size(); i++) {
746                     // Destroy app data snapshot.
747                     int userId = snapshottedUsers.get(i);
748 
749                     dataHelper.destroyAppDataSnapshot(info.getRollbackId(), pkgInfo, userId);
750                 }
751             }
752         }
753         if (containsApex) {
754             dataHelper.destroyApexDeSnapshots(info.getRollbackId());
755             for (int user : apexUsers) {
756                 dataHelper.destroyApexCeSnapshots(user, info.getRollbackId());
757             }
758         }
759 
760         RollbackStore.deleteRollback(this);
761         setState(ROLLBACK_STATE_DELETED, reason);
762     }
763 
764     /**
765      * Returns true if we are expecting the package manager to call restoreUserData for this
766      * rollback because it has just been committed but the rollback has not yet been fully applied.
767      */
768     @WorkerThread
isRestoreUserDataInProgress()769     boolean isRestoreUserDataInProgress() {
770         assertInWorkerThread();
771         return mRestoreUserDataInProgress;
772     }
773 
774     /**
775      * Sets whether we are expecting the package manager to call restoreUserData for this
776      * rollback because it has just been committed but the rollback has not yet been fully applied.
777      */
778     @WorkerThread
setRestoreUserDataInProgress(boolean restoreUserDataInProgress)779     void setRestoreUserDataInProgress(boolean restoreUserDataInProgress) {
780         assertInWorkerThread();
781         mRestoreUserDataInProgress = restoreUserDataInProgress;
782         RollbackStore.saveRollback(this);
783     }
784 
785     /**
786      * Returns true if this rollback includes the package with the provided {@code packageName}.
787      */
788     @WorkerThread
includesPackage(String packageName)789     boolean includesPackage(String packageName) {
790         assertInWorkerThread();
791         for (PackageRollbackInfo packageRollbackInfo : info.getPackages()) {
792             if (packageRollbackInfo.getPackageName().equals(packageName)) {
793                 return true;
794             }
795         }
796         return false;
797     }
798 
799     /**
800      * Returns true if this rollback includes the package with the provided {@code packageName}
801      * with a <i>version rolled back from</i> that is not {@code versionCode}.
802      */
803     @WorkerThread
includesPackageWithDifferentVersion(String packageName, long versionCode)804     boolean includesPackageWithDifferentVersion(String packageName, long versionCode) {
805         assertInWorkerThread();
806         for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
807             if (pkgRollbackInfo.getPackageName().equals(packageName)
808                     && pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode()
809                     != versionCode) {
810                 return true;
811             }
812         }
813         return false;
814     }
815 
816     /**
817      * Returns a list containing the names of all the packages included in this rollback.
818      */
819     @WorkerThread
getPackageNames()820     List<String> getPackageNames() {
821         assertInWorkerThread();
822         List<String> result = new ArrayList<>();
823         for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
824             result.add(pkgRollbackInfo.getPackageName());
825         }
826         return result;
827     }
828 
829     /**
830      * Returns a list containing the names of all the apex packages included in this rollback.
831      */
832     @WorkerThread
getApexPackageNames()833     List<String> getApexPackageNames() {
834         assertInWorkerThread();
835         List<String> result = new ArrayList<>();
836         for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
837             if (pkgRollbackInfo.isApex()) {
838                 result.add(pkgRollbackInfo.getPackageName());
839             }
840         }
841         return result;
842     }
843 
844     /**
845      * Returns true if this rollback contains the provided {@code packageSessionId}.
846      */
847     @AnyThread
containsSessionId(int packageSessionId)848     boolean containsSessionId(int packageSessionId) {
849         for (int id : mPackageSessionIds) {
850             if (id == packageSessionId) {
851                 return true;
852             }
853         }
854         return false;
855     }
856 
857     /**
858      * Returns true if all packages in this rollback are enabled. We won't enable this rollback
859      * until all packages are enabled. Note we don't count apk-in-apex here since they are enabled
860      * automatically when the embedding apex is enabled.
861      */
862     @WorkerThread
allPackagesEnabled()863     boolean allPackagesEnabled() {
864         assertInWorkerThread();
865         int packagesWithoutApkInApex = 0;
866         for (PackageRollbackInfo rollbackInfo : info.getPackages()) {
867             if (!rollbackInfo.isApkInApex()) {
868                 packagesWithoutApkInApex++;
869             }
870         }
871         return packagesWithoutApkInApex == mPackageSessionIds.length;
872     }
873 
874     @AnyThread
rollbackStateToString(@ollbackState int state)875     static String rollbackStateToString(@RollbackState int state) {
876         switch (state) {
877             case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
878             case Rollback.ROLLBACK_STATE_AVAILABLE: return "available";
879             case Rollback.ROLLBACK_STATE_COMMITTED: return "committed";
880             case Rollback.ROLLBACK_STATE_DELETED: return "deleted";
881         }
882         throw new AssertionError("Invalid rollback state: " + state);
883     }
884 
885     @AnyThread
rollbackStateFromString(String state)886     static @RollbackState int rollbackStateFromString(String state)
887             throws ParseException {
888         switch (state) {
889             case "enabling": return Rollback.ROLLBACK_STATE_ENABLING;
890             case "available": return Rollback.ROLLBACK_STATE_AVAILABLE;
891             case "committed": return Rollback.ROLLBACK_STATE_COMMITTED;
892             case "deleted": return Rollback.ROLLBACK_STATE_DELETED;
893         }
894         throw new ParseException("Invalid rollback state: " + state, 0);
895     }
896 
897     @WorkerThread
getStateAsString()898     String getStateAsString() {
899         assertInWorkerThread();
900         return rollbackStateToString(mState);
901     }
902 
903     /**
904      * Returns true if there is an app installed that specifies a minExtensionVersion greater
905      * than what was present at the time this Rollback was created.
906      */
907     @VisibleForTesting
extensionVersionReductionWouldViolateConstraint( SparseIntArray rollbackExtVers, PackageManagerInternal pmi)908     static boolean extensionVersionReductionWouldViolateConstraint(
909             SparseIntArray rollbackExtVers, PackageManagerInternal pmi) {
910         if (rollbackExtVers.size() == 0) {
911             return false;
912         }
913         List<String> packages = pmi.getPackageList().getPackageNames();
914         for (int i = 0; i < packages.size(); i++) {
915             AndroidPackage pkg = pmi.getPackage(packages.get(i));
916             SparseIntArray minExtVers = pkg.getMinExtensionVersions();
917             if (minExtVers == null) {
918                 continue;
919             }
920             for (int j = 0; j < rollbackExtVers.size(); j++) {
921                 int minExt = minExtVers.get(rollbackExtVers.keyAt(j), -1);
922                 if (rollbackExtVers.valueAt(j) < minExt) {
923                     return true;
924                 }
925             }
926         }
927         return false;
928     }
929 
930     /**
931      * Returns true if for any SDK version, the extension version recorded at the time of rollback
932      * creation is lower than the current extension version.
933      */
934     @AnyThread
wasCreatedAtLowerExtensionVersion()935     private boolean wasCreatedAtLowerExtensionVersion() {
936         for (int i = 0; i < mExtensionVersions.size(); i++) {
937             if (SdkExtensions.getExtensionVersion(mExtensionVersions.keyAt(i))
938                     > mExtensionVersions.valueAt(i)) {
939                 return true;
940             }
941         }
942         return false;
943     }
944 
945     @AnyThread
containsApex()946     private boolean containsApex() {
947         for (PackageRollbackInfo pkgInfo : info.getPackages()) {
948             if (pkgInfo.isApex()) {
949                 return true;
950             }
951         }
952         return false;
953     }
954 
955     @WorkerThread
dump(IndentingPrintWriter ipw)956     void dump(IndentingPrintWriter ipw) {
957         assertInWorkerThread();
958         ipw.println(info.getRollbackId() + ":");
959         ipw.increaseIndent();
960         ipw.println("-state: " + getStateAsString());
961         ipw.println("-stateDescription: " + mStateDescription);
962         ipw.println("-timestamp: " + getTimestamp());
963         ipw.println("-rollbackLifetimeMillis: " + getRollbackLifetimeMillis());
964         if (Flags.recoverabilityDetection()) {
965             ipw.println("-rollbackImpactLevel: " + info.getRollbackImpactLevel());
966         }
967         ipw.println("-isStaged: " + isStaged());
968         ipw.println("-originalSessionId: " + getOriginalSessionId());
969         ipw.println("-packages:");
970         ipw.increaseIndent();
971         for (PackageRollbackInfo pkg : info.getPackages()) {
972             ipw.println(pkg.getPackageName()
973                     + " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
974                     + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode()
975                     + " [" + pkg.getRollbackDataPolicy() + "]");
976         }
977         ipw.decreaseIndent();
978         if (isCommitted()) {
979             ipw.println("-causePackages:");
980             ipw.increaseIndent();
981             for (VersionedPackage cPkg : info.getCausePackages()) {
982                 ipw.println(cPkg.getPackageName() + " " + cPkg.getLongVersionCode());
983             }
984             ipw.decreaseIndent();
985             ipw.println("-committedSessionId: " + info.getCommittedSessionId());
986         }
987         if (mExtensionVersions.size() > 0) {
988             ipw.println("-extensionVersions:");
989             ipw.increaseIndent();
990             ipw.println(mExtensionVersions.toString());
991             ipw.decreaseIndent();
992         }
993         ipw.decreaseIndent();
994     }
995 
996     @WorkerThread
getStateDescription()997     String getStateDescription() {
998         assertInWorkerThread();
999         return mStateDescription;
1000     }
1001 
1002     @VisibleForTesting
setState(@ollbackState int state, String description)1003     void setState(@RollbackState int state, String description) {
1004         assertInWorkerThread();
1005         mState = state;
1006         mStateDescription = description;
1007     }
1008 }
1009