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