OSDN Git Service

f075790a2fa04081390b7475a4b08969aad1159a
[android-x86/frameworks-base.git] / services / core / java / com / android / server / rollback / RollbackManagerServiceImpl.java
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 android.Manifest;
20 import android.annotation.AnyThread;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.WorkerThread;
24 import android.app.AppOpsManager;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.IntentSender;
30 import android.content.pm.ApplicationInfo;
31 import android.content.pm.ModuleInfo;
32 import android.content.pm.PackageInfo;
33 import android.content.pm.PackageInstaller;
34 import android.content.pm.PackageManager;
35 import android.content.pm.PackageManagerInternal;
36 import android.content.pm.PackageParser;
37 import android.content.pm.ParceledListSlice;
38 import android.content.pm.UserInfo;
39 import android.content.pm.VersionedPackage;
40 import android.content.pm.parsing.ApkLiteParseUtils;
41 import android.content.pm.parsing.result.ParseResult;
42 import android.content.pm.parsing.result.ParseTypeImpl;
43 import android.content.rollback.IRollbackManager;
44 import android.content.rollback.RollbackInfo;
45 import android.content.rollback.RollbackManager;
46 import android.os.Binder;
47 import android.os.Build;
48 import android.os.Environment;
49 import android.os.Handler;
50 import android.os.HandlerExecutor;
51 import android.os.HandlerThread;
52 import android.os.Process;
53 import android.os.SystemClock;
54 import android.os.UserHandle;
55 import android.os.UserManager;
56 import android.os.ext.SdkExtensions;
57 import android.provider.DeviceConfig;
58 import android.util.IntArray;
59 import android.util.Log;
60 import android.util.LongArrayQueue;
61 import android.util.Slog;
62 import android.util.SparseBooleanArray;
63 import android.util.SparseIntArray;
64
65 import com.android.internal.annotations.GuardedBy;
66 import com.android.internal.util.DumpUtils;
67 import com.android.internal.util.IndentingPrintWriter;
68 import com.android.server.LocalServices;
69 import com.android.server.PackageWatchdog;
70 import com.android.server.SystemConfig;
71 import com.android.server.Watchdog;
72 import com.android.server.pm.ApexManager;
73 import com.android.server.pm.Installer;
74
75 import java.io.File;
76 import java.io.FileDescriptor;
77 import java.io.PrintWriter;
78 import java.security.SecureRandom;
79 import java.time.Instant;
80 import java.time.temporal.ChronoUnit;
81 import java.util.ArrayList;
82 import java.util.Arrays;
83 import java.util.HashSet;
84 import java.util.Iterator;
85 import java.util.List;
86 import java.util.Random;
87 import java.util.Set;
88 import java.util.concurrent.CountDownLatch;
89 import java.util.concurrent.Executor;
90 import java.util.concurrent.LinkedBlockingQueue;
91 import java.util.concurrent.TimeUnit;
92
93 /**
94  * Implementation of service that manages APK level rollbacks.
95  *
96  * Threading model:
97  *
98  * - @AnyThread annotates thread-safe methods.
99  * - @WorkerThread annotates methods that should be called from the handler thread only.
100  */
101 class RollbackManagerServiceImpl extends IRollbackManager.Stub {
102
103     private static final String TAG = "RollbackManager";
104     private static final boolean LOCAL_LOGV = Log.isLoggable(TAG, Log.VERBOSE);
105
106     // Rollbacks expire after 14 days.
107     private static final long DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS =
108             TimeUnit.DAYS.toMillis(14);
109
110     // Lock used to synchronize accesses to in-memory rollback data
111     // structures. By convention, methods with the suffix "Locked" require
112     // mLock is held when they are called.
113     private final Object mLock = new Object();
114
115     // No need for guarding with lock because value is only accessed in handler thread
116     // and the value will be written on boot complete. Initialization here happens before
117     // handler threads are running so that's fine.
118     private long mRollbackLifetimeDurationInMillis = DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS;
119
120     private static final long HANDLER_THREAD_TIMEOUT_DURATION_MILLIS =
121             TimeUnit.MINUTES.toMillis(10);
122
123     // Used for generating rollback IDs.
124     private final Random mRandom = new SecureRandom();
125
126     // Set of allocated rollback ids
127     @GuardedBy("mLock")
128     private final SparseBooleanArray mAllocatedRollbackIds = new SparseBooleanArray();
129
130     // The list of all rollbacks, including available and committed rollbacks.
131     @GuardedBy("mLock")
132     private final List<Rollback> mRollbacks;
133
134     // Apk sessions from a staged session with no matching rollback.
135     @GuardedBy("mLock")
136     private final IntArray mOrphanedApkSessionIds = new IntArray();
137
138     private final RollbackStore mRollbackStore;
139
140     private final Context mContext;
141     private final HandlerThread mHandlerThread;
142     private final Executor mExecutor;
143     private final Installer mInstaller;
144     private final RollbackPackageHealthObserver mPackageHealthObserver;
145     private final AppDataRollbackHelper mAppDataRollbackHelper;
146     private final Runnable mRunExpiration = this::runExpiration;
147
148     // The # of milli-seconds to sleep for each received ACTION_PACKAGE_ENABLE_ROLLBACK.
149     // Used by #blockRollbackManager to test timeout in enabling rollbacks.
150     // Accessed on the handler thread only.
151     private final LongArrayQueue mSleepDuration = new LongArrayQueue();
152
153     // This field stores the difference in Millis between the uptime (millis since device
154     // has booted) and current time (device wall clock) - it's used to update rollback
155     // timestamps when the time is changed, by the user or by change of timezone.
156     // No need for guarding with lock because value is only accessed in handler thread.
157     private long  mRelativeBootTime = calculateRelativeBootTime();
158
159     RollbackManagerServiceImpl(Context context) {
160         mContext = context;
161         // Note that we're calling onStart here because this object is only constructed on
162         // SystemService#onStart.
163         mInstaller = new Installer(mContext);
164         mInstaller.onStart();
165
166         mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback"));
167
168         mPackageHealthObserver = new RollbackPackageHealthObserver(mContext);
169         mAppDataRollbackHelper = new AppDataRollbackHelper(mInstaller);
170
171         // Load rollback data from device storage.
172         synchronized (mLock) {
173             mRollbacks = mRollbackStore.loadRollbacks();
174             if (!context.getPackageManager().isDeviceUpgrading()) {
175                 for (Rollback rollback : mRollbacks) {
176                     mAllocatedRollbackIds.put(rollback.info.getRollbackId(), true);
177                 }
178             } else {
179                 // Delete rollbacks when build fingerprint has changed.
180                 for (Rollback rollback : mRollbacks) {
181                     rollback.delete(mAppDataRollbackHelper);
182                 }
183                 mRollbacks.clear();
184             }
185         }
186
187         // Kick off and start monitoring the handler thread.
188         mHandlerThread = new HandlerThread("RollbackManagerServiceHandler");
189         mHandlerThread.start();
190         Watchdog.getInstance().addThread(getHandler(), HANDLER_THREAD_TIMEOUT_DURATION_MILLIS);
191         mExecutor = new HandlerExecutor(getHandler());
192
193         for (UserInfo userInfo : UserManager.get(mContext).getUsers(true)) {
194             registerUserCallbacks(userInfo.getUserHandle());
195         }
196
197         IntentFilter enableRollbackFilter = new IntentFilter();
198         enableRollbackFilter.addAction(Intent.ACTION_PACKAGE_ENABLE_ROLLBACK);
199         try {
200             enableRollbackFilter.addDataType("application/vnd.android.package-archive");
201         } catch (IntentFilter.MalformedMimeTypeException e) {
202             Slog.e(TAG, "addDataType", e);
203         }
204
205         mContext.registerReceiver(new BroadcastReceiver() {
206             @Override
207             public void onReceive(Context context, Intent intent) {
208                 if (Intent.ACTION_PACKAGE_ENABLE_ROLLBACK.equals(intent.getAction())) {
209                     int token = intent.getIntExtra(
210                             PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN, -1);
211                     int sessionId = intent.getIntExtra(
212                             PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_SESSION_ID, -1);
213
214                     queueSleepIfNeeded();
215
216                     getHandler().post(() -> {
217                         boolean success = enableRollback(sessionId);
218                         int ret = PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED;
219                         if (!success) {
220                             ret = PackageManagerInternal.ENABLE_ROLLBACK_FAILED;
221                         }
222
223                         PackageManagerInternal pm = LocalServices.getService(
224                                 PackageManagerInternal.class);
225                         pm.setEnableRollbackCode(token, ret);
226                     });
227
228                     // We're handling the ordered broadcast. Abort the
229                     // broadcast because there is no need for it to go to
230                     // anyone else.
231                     abortBroadcast();
232                 }
233             }
234         }, enableRollbackFilter, null, getHandler());
235
236         IntentFilter enableRollbackTimedOutFilter = new IntentFilter();
237         enableRollbackTimedOutFilter.addAction(Intent.ACTION_CANCEL_ENABLE_ROLLBACK);
238
239         mContext.registerReceiver(new BroadcastReceiver() {
240             @Override
241             public void onReceive(Context context, Intent intent) {
242                 if (Intent.ACTION_CANCEL_ENABLE_ROLLBACK.equals(intent.getAction())) {
243                     int sessionId = intent.getIntExtra(
244                             PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_SESSION_ID, -1);
245                     if (LOCAL_LOGV) {
246                         Slog.v(TAG, "broadcast=ACTION_CANCEL_ENABLE_ROLLBACK id=" + sessionId);
247                     }
248                     synchronized (mLock) {
249                         Rollback rollback = getRollbackForSessionLocked(sessionId);
250                         if (rollback != null && rollback.isEnabling()) {
251                             mRollbacks.remove(rollback);
252                             rollback.delete(mAppDataRollbackHelper);
253                         }
254                     }
255                 }
256             }
257         }, enableRollbackTimedOutFilter, null, getHandler());
258
259         IntentFilter userAddedIntentFilter = new IntentFilter(Intent.ACTION_USER_ADDED);
260         mContext.registerReceiver(new BroadcastReceiver() {
261             @Override
262             public void onReceive(Context context, Intent intent) {
263                 if (Intent.ACTION_USER_ADDED.equals(intent.getAction())) {
264                     final int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
265                     if (newUserId == -1) {
266                         return;
267                     }
268                     registerUserCallbacks(UserHandle.of(newUserId));
269                 }
270             }
271         }, userAddedIntentFilter, null, getHandler());
272
273         registerTimeChangeReceiver();
274     }
275
276     @AnyThread
277     private void registerUserCallbacks(UserHandle user) {
278         Context context = getContextAsUser(user);
279         if (context == null) {
280             Slog.e(TAG, "Unable to register user callbacks for user " + user);
281             return;
282         }
283
284         context.getPackageManager().getPackageInstaller()
285                 .registerSessionCallback(new SessionCallback(), getHandler());
286
287         IntentFilter filter = new IntentFilter();
288         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
289         filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
290         filter.addDataScheme("package");
291         context.registerReceiver(new BroadcastReceiver() {
292             @Override
293             public void onReceive(Context context, Intent intent) {
294                 String action = intent.getAction();
295                 if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
296                     String packageName = intent.getData().getSchemeSpecificPart();
297                     if (LOCAL_LOGV) {
298                         Slog.v(TAG, "broadcast=ACTION_PACKAGE_REPLACED" + " pkg=" + packageName);
299                     }
300                     onPackageReplaced(packageName);
301                 }
302                 if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
303                     String packageName = intent.getData().getSchemeSpecificPart();
304                     Slog.i(TAG, "broadcast=ACTION_PACKAGE_FULLY_REMOVED pkg=" + packageName);
305                     onPackageFullyRemoved(packageName);
306                 }
307             }
308         }, filter, null, getHandler());
309     }
310
311     @Override
312     public ParceledListSlice getAvailableRollbacks() {
313         enforceManageRollbacks("getAvailableRollbacks");
314         synchronized (mLock) {
315             List<RollbackInfo> rollbacks = new ArrayList<>();
316             for (int i = 0; i < mRollbacks.size(); ++i) {
317                 Rollback rollback = mRollbacks.get(i);
318                 if (rollback.isAvailable()) {
319                     rollbacks.add(rollback.info);
320                 }
321             }
322             return new ParceledListSlice<>(rollbacks);
323         }
324     }
325
326     @Override
327     public ParceledListSlice<RollbackInfo> getRecentlyCommittedRollbacks() {
328         enforceManageRollbacks("getRecentlyCommittedRollbacks");
329
330         synchronized (mLock) {
331             List<RollbackInfo> rollbacks = new ArrayList<>();
332             for (int i = 0; i < mRollbacks.size(); ++i) {
333                 Rollback rollback = mRollbacks.get(i);
334                 if (rollback.isCommitted()) {
335                     rollbacks.add(rollback.info);
336                 }
337             }
338             return new ParceledListSlice<>(rollbacks);
339         }
340     }
341
342     @Override
343     public void commitRollback(int rollbackId, ParceledListSlice causePackages,
344             String callerPackageName, IntentSender statusReceiver) {
345         enforceManageRollbacks("commitRollback");
346
347         final int callingUid = Binder.getCallingUid();
348         AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
349         appOps.checkPackage(callingUid, callerPackageName);
350
351         getHandler().post(() ->
352                 commitRollbackInternal(rollbackId, causePackages.getList(),
353                     callerPackageName, statusReceiver));
354     }
355
356     @AnyThread
357     private void registerTimeChangeReceiver() {
358         final BroadcastReceiver timeChangeIntentReceiver = new BroadcastReceiver() {
359             @Override
360             public void onReceive(Context context, Intent intent) {
361                 final long oldRelativeBootTime = mRelativeBootTime;
362                 mRelativeBootTime = calculateRelativeBootTime();
363                 final long timeDifference = mRelativeBootTime - oldRelativeBootTime;
364
365                 synchronized (mLock) {
366                     Iterator<Rollback> iter = mRollbacks.iterator();
367                     while (iter.hasNext()) {
368                         Rollback rollback = iter.next();
369                         rollback.setTimestamp(
370                                 rollback.getTimestamp().plusMillis(timeDifference));
371                     }
372                 }
373             }
374         };
375         final IntentFilter filter = new IntentFilter();
376         filter.addAction(Intent.ACTION_TIME_CHANGED);
377         mContext.registerReceiver(timeChangeIntentReceiver, filter,
378                 null /* broadcastPermission */, getHandler());
379     }
380
381     @AnyThread
382     private static long calculateRelativeBootTime() {
383         return System.currentTimeMillis() - SystemClock.elapsedRealtime();
384     }
385
386     /**
387      * Performs the actual work to commit a rollback.
388      * The work is done on the current thread. This may be a long running
389      * operation.
390      */
391     @WorkerThread
392     private void commitRollbackInternal(int rollbackId, List<VersionedPackage> causePackages,
393             String callerPackageName, IntentSender statusReceiver) {
394         Slog.i(TAG, "commitRollback id=" + rollbackId + " caller=" + callerPackageName);
395
396         Rollback rollback = getRollbackForId(rollbackId);
397         if (rollback == null) {
398             sendFailure(
399                     mContext, statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
400                     "Rollback unavailable");
401             return;
402         }
403         rollback.commit(mContext, causePackages, callerPackageName, statusReceiver);
404     }
405
406     @Override
407     public void reloadPersistedData() {
408         mContext.enforceCallingOrSelfPermission(
409                 Manifest.permission.TEST_MANAGE_ROLLBACKS,
410                 "reloadPersistedData");
411
412         CountDownLatch latch = new CountDownLatch(1);
413         getHandler().post(() -> {
414             synchronized (mLock) {
415                 mRollbacks.clear();
416                 mRollbacks.addAll(mRollbackStore.loadRollbacks());
417             }
418             latch.countDown();
419         });
420
421         try {
422             latch.await();
423         } catch (InterruptedException ie) {
424             throw new IllegalStateException("RollbackManagerHandlerThread interrupted");
425         }
426     }
427
428     @Override
429     public void expireRollbackForPackage(String packageName) {
430         mContext.enforceCallingOrSelfPermission(
431                 Manifest.permission.TEST_MANAGE_ROLLBACKS,
432                 "expireRollbackForPackage");
433         synchronized (mLock) {
434             Iterator<Rollback> iter = mRollbacks.iterator();
435             while (iter.hasNext()) {
436                 Rollback rollback = iter.next();
437                 if (rollback.includesPackage(packageName)) {
438                     iter.remove();
439                     rollback.delete(mAppDataRollbackHelper);
440                 }
441             }
442         }
443     }
444
445     @Override
446     public void blockRollbackManager(long millis) {
447         mContext.enforceCallingOrSelfPermission(
448                 Manifest.permission.TEST_MANAGE_ROLLBACKS,
449                 "blockRollbackManager");
450         getHandler().post(() -> {
451             mSleepDuration.addLast(millis);
452         });
453     }
454
455     @WorkerThread
456     private void queueSleepIfNeeded() {
457         if (mSleepDuration.size() == 0) {
458             return;
459         }
460         long millis = mSleepDuration.removeFirst();
461         if (millis <= 0) {
462             return;
463         }
464         getHandler().post(() -> {
465             try {
466                 Thread.sleep(millis);
467             } catch (InterruptedException e) {
468                 throw new IllegalStateException("RollbackManagerHandlerThread interrupted");
469             }
470         });
471     }
472
473     void onUnlockUser(int userId) {
474         if (LOCAL_LOGV) {
475             Slog.v(TAG, "onUnlockUser id=" + userId);
476         }
477         // In order to ensure that no package begins running while a backup or restore is taking
478         // place, onUnlockUser must remain blocked until all pending backups and restores have
479         // completed.
480         CountDownLatch latch = new CountDownLatch(1);
481         getHandler().post(() -> {
482             final List<Rollback> rollbacks;
483             synchronized (mLock) {
484                 rollbacks = new ArrayList<>(mRollbacks);
485             }
486
487             for (int i = 0; i < rollbacks.size(); i++) {
488                 Rollback rollback = rollbacks.get(i);
489                 rollback.commitPendingBackupAndRestoreForUser(userId, mAppDataRollbackHelper);
490             }
491
492             latch.countDown();
493
494             destroyCeSnapshotsForExpiredRollbacks(userId);
495         });
496
497         try {
498             latch.await();
499         } catch (InterruptedException ie) {
500             throw new IllegalStateException("RollbackManagerHandlerThread interrupted");
501         }
502     }
503
504     @WorkerThread
505     private void destroyCeSnapshotsForExpiredRollbacks(int userId) {
506         int[] rollbackIds = new int[mRollbacks.size()];
507         for (int i = 0; i < rollbackIds.length; i++) {
508             rollbackIds[i] = mRollbacks.get(i).info.getRollbackId();
509         }
510         ApexManager.getInstance().destroyCeSnapshotsNotSpecified(userId, rollbackIds);
511         try {
512             mInstaller.destroyCeSnapshotsNotSpecified(userId, rollbackIds);
513         } catch (Installer.InstallerException ie) {
514             Slog.e(TAG, "Failed to delete snapshots for user: " + userId, ie);
515         }
516     }
517
518     @WorkerThread
519     private void updateRollbackLifetimeDurationInMillis() {
520         mRollbackLifetimeDurationInMillis = DeviceConfig.getLong(
521                 DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
522                 RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
523                 DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS);
524         if (mRollbackLifetimeDurationInMillis < 0) {
525             mRollbackLifetimeDurationInMillis = DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS;
526         }
527         Slog.d(TAG, "mRollbackLifetimeDurationInMillis=" + mRollbackLifetimeDurationInMillis);
528         runExpiration();
529     }
530
531     @AnyThread
532     void onBootCompleted() {
533         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
534                 mExecutor, properties -> updateRollbackLifetimeDurationInMillis());
535
536         getHandler().post(() -> {
537             updateRollbackLifetimeDurationInMillis();
538             runExpiration();
539
540             // Check to see if any rollback-enabled staged sessions or staged
541             // rollback sessions been applied.
542             List<Rollback> enabling = new ArrayList<>();
543             List<Rollback> restoreInProgress = new ArrayList<>();
544             Set<String> apexPackageNames = new HashSet<>();
545             synchronized (mLock) {
546                 Iterator<Rollback> iter = mRollbacks.iterator();
547                 while (iter.hasNext()) {
548                     Rollback rollback = iter.next();
549                     if (!rollback.isStaged()) {
550                         // We only care about staged rollbacks here
551                         continue;
552                     }
553
554                     PackageInstaller.SessionInfo session = mContext.getPackageManager()
555                             .getPackageInstaller().getSessionInfo(rollback.getStagedSessionId());
556                     if (session == null || session.isStagedSessionFailed()) {
557                         iter.remove();
558                         rollback.delete(mAppDataRollbackHelper);
559                         continue;
560                     }
561
562                     if (session.isStagedSessionApplied()) {
563                         if (rollback.isEnabling()) {
564                             enabling.add(rollback);
565                         } else if (rollback.isRestoreUserDataInProgress()) {
566                             restoreInProgress.add(rollback);
567                         }
568                     }
569                     apexPackageNames.addAll(rollback.getApexPackageNames());
570                 }
571             }
572
573             for (Rollback rollback : enabling) {
574                 makeRollbackAvailable(rollback);
575             }
576
577             for (Rollback rollback : restoreInProgress) {
578                 rollback.setRestoreUserDataInProgress(false);
579             }
580
581             for (String apexPackageName : apexPackageNames) {
582                 // We will not recieve notifications when an apex is updated,
583                 // so check now in case any rollbacks ought to be expired. The
584                 // onPackagedReplace function is safe to call if the package
585                 // hasn't actually been updated.
586                 onPackageReplaced(apexPackageName);
587             }
588
589             synchronized (mLock) {
590                 mOrphanedApkSessionIds.clear();
591             }
592
593             mPackageHealthObserver.onBootCompletedAsync();
594         });
595     }
596
597     /**
598      * Called when a package has been replaced with a different version.
599      * Removes all backups for the package not matching the currently
600      * installed package version.
601      */
602     @WorkerThread
603     private void onPackageReplaced(String packageName) {
604         // TODO: Could this end up incorrectly deleting a rollback for a
605         // package that is about to be installed?
606         long installedVersion = getInstalledPackageVersion(packageName);
607
608         synchronized (mLock) {
609             Iterator<Rollback> iter = mRollbacks.iterator();
610             while (iter.hasNext()) {
611                 Rollback rollback = iter.next();
612                 // TODO: Should we remove rollbacks in the ENABLING state here?
613                 if ((rollback.isEnabling() || rollback.isAvailable())
614                         && rollback.includesPackageWithDifferentVersion(packageName,
615                         installedVersion)) {
616                     iter.remove();
617                     rollback.delete(mAppDataRollbackHelper);
618                 }
619             }
620         }
621     }
622
623     /**
624      * Called when a package has been completely removed from the device.
625      * Removes all backups and rollback history for the given package.
626      */
627     @WorkerThread
628     private void onPackageFullyRemoved(String packageName) {
629         expireRollbackForPackage(packageName);
630     }
631
632     /**
633      * Notifies an IntentSender of failure.
634      *
635      * @param statusReceiver where to send the failure
636      * @param status the RollbackManager.STATUS_* code with the failure.
637      * @param message the failure message.
638      */
639     @AnyThread
640     static void sendFailure(Context context, IntentSender statusReceiver,
641             @RollbackManager.Status int status, String message) {
642         Slog.e(TAG, message);
643         try {
644             final Intent fillIn = new Intent();
645             fillIn.putExtra(RollbackManager.EXTRA_STATUS, status);
646             fillIn.putExtra(RollbackManager.EXTRA_STATUS_MESSAGE, message);
647             statusReceiver.sendIntent(context, 0, fillIn, null, null);
648         } catch (IntentSender.SendIntentException e) {
649             // Nowhere to send the result back to, so don't bother.
650         }
651     }
652
653     // Check to see if anything needs expiration, and if so, expire it.
654     // Schedules future expiration as appropriate.
655     @WorkerThread
656     private void runExpiration() {
657         getHandler().removeCallbacks(mRunExpiration);
658
659         Instant now = Instant.now();
660         Instant oldest = null;
661         synchronized (mLock) {
662             Iterator<Rollback> iter = mRollbacks.iterator();
663             while (iter.hasNext()) {
664                 Rollback rollback = iter.next();
665                 if (!rollback.isAvailable()) {
666                     continue;
667                 }
668                 Instant rollbackTimestamp = rollback.getTimestamp();
669                 if (!now.isBefore(
670                         rollbackTimestamp
671                                 .plusMillis(mRollbackLifetimeDurationInMillis))) {
672                     Slog.i(TAG, "runExpiration id=" + rollback.info.getRollbackId());
673                     iter.remove();
674                     rollback.delete(mAppDataRollbackHelper);
675                 } else if (oldest == null || oldest.isAfter(rollbackTimestamp)) {
676                     oldest = rollbackTimestamp;
677                 }
678             }
679         }
680
681         if (oldest != null) {
682             long delay = now.until(
683                     oldest.plusMillis(mRollbackLifetimeDurationInMillis), ChronoUnit.MILLIS);
684             getHandler().postDelayed(mRunExpiration, delay);
685         }
686     }
687
688     @AnyThread
689     private Handler getHandler() {
690         return mHandlerThread.getThreadHandler();
691     }
692
693     @AnyThread
694     private Context getContextAsUser(UserHandle user) {
695         try {
696             return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
697         } catch (PackageManager.NameNotFoundException e) {
698             return null;
699         }
700     }
701
702     /**
703      * Called via broadcast by the package manager when a package is being
704      * staged for install with rollback enabled. Called before the package has
705      * been installed.
706      *
707      * @param sessionId the id of the install session
708      * @return true if enabling the rollback succeeds, false otherwise.
709      */
710     @WorkerThread
711     private boolean enableRollback(int sessionId) {
712         if (LOCAL_LOGV) {
713             Slog.v(TAG, "enableRollback sessionId=" + sessionId);
714         }
715
716         PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
717         PackageInstaller.SessionInfo packageSession = installer.getSessionInfo(sessionId);
718         if (packageSession == null) {
719             Slog.e(TAG, "Unable to find session for enabled rollback.");
720             return false;
721         }
722
723         PackageInstaller.SessionInfo parentSession = packageSession.hasParentSessionId()
724                 ? installer.getSessionInfo(packageSession.getParentSessionId()) : packageSession;
725         if (parentSession == null) {
726             Slog.e(TAG, "Unable to find parent session for enabled rollback.");
727             return false;
728         }
729
730         // Check to see if this is the apk session for a staged session with
731         // rollback enabled.
732         synchronized (mLock) {
733             for (int i = 0; i < mRollbacks.size(); ++i) {
734                 Rollback rollback = mRollbacks.get(i);
735                 if (rollback.getApkSessionId() == parentSession.getSessionId()) {
736                     // This is the apk session for a staged session with rollback enabled. We do
737                     // not need to create a new rollback for this session.
738                     return true;
739                 }
740             }
741         }
742
743         // Check to see if this is the apk session for a staged session for which rollback was
744         // cancelled.
745         synchronized (mLock) {
746             if (mOrphanedApkSessionIds.indexOf(parentSession.getSessionId()) != -1) {
747                 Slog.w(TAG, "Not enabling rollback for apk as no matching staged session "
748                         + "rollback exists");
749                 return false;
750             }
751         }
752
753         Rollback newRollback;
754         synchronized (mLock) {
755             // See if we already have a Rollback that contains this package
756             // session. If not, create a new Rollback for the parent session
757             // that we will use for all the packages in the session.
758             newRollback = getRollbackForSessionLocked(packageSession.getSessionId());
759             if (newRollback == null) {
760                 newRollback = createNewRollbackLocked(parentSession);
761             }
762         }
763
764         return enableRollbackForPackageSession(newRollback, packageSession);
765     }
766
767     /**
768      * Do code and userdata backups to enable rollback of the given session.
769      * In case of multiPackage sessions, <code>session</code> should be one of
770      * the child sessions, not the parent session.
771      *
772      * @return true on success, false on failure.
773      */
774     @WorkerThread
775     private boolean enableRollbackForPackageSession(Rollback rollback,
776             PackageInstaller.SessionInfo session) {
777         // TODO: Don't attempt to enable rollback for split installs.
778         final int installFlags = session.installFlags;
779         if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
780             Slog.e(TAG, "Rollback is not enabled.");
781             return false;
782         }
783         if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
784             Slog.e(TAG, "Rollbacks not supported for instant app install");
785             return false;
786         }
787
788         if (session.resolvedBaseCodePath == null) {
789             Slog.e(TAG, "Session code path has not been resolved.");
790             return false;
791         }
792
793         // Get information about the package to be installed.
794         ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
795         ParseResult<PackageParser.ApkLite> parseResult = ApkLiteParseUtils.parseApkLite(
796                 input.reset(), new File(session.resolvedBaseCodePath), 0);
797         if (parseResult.isError()) {
798             Slog.e(TAG, "Unable to parse new package: " + parseResult.getErrorMessage(),
799                     parseResult.getException());
800             return false;
801         }
802         PackageParser.ApkLite newPackage = parseResult.getResult();
803
804         String packageName = newPackage.packageName;
805         Slog.i(TAG, "Enabling rollback for install of " + packageName
806                 + ", session:" + session.sessionId);
807
808         String installerPackageName = session.getInstallerPackageName();
809         if (!enableRollbackAllowed(installerPackageName, packageName)) {
810             Slog.e(TAG, "Installer " + installerPackageName
811                     + " is not allowed to enable rollback on " + packageName);
812             return false;
813         }
814
815         final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0);
816
817         // Get information about the currently installed package.
818         final PackageInfo pkgInfo;
819         try {
820             pkgInfo = getPackageInfo(packageName);
821         } catch (PackageManager.NameNotFoundException e) {
822             // TODO: Support rolling back fresh package installs rather than
823             // fail here. Test this case.
824             Slog.e(TAG, packageName + " is not installed");
825             return false;
826         }
827
828         if (isApex) {
829             // Check if this apex contains apks inside it. If true, then they should be added as
830             // a RollbackPackageInfo into this rollback
831             final PackageManagerInternal pmi = LocalServices.getService(
832                     PackageManagerInternal.class);
833             List<String> apksInApex = pmi.getApksInApex(packageName);
834             for (String apkInApex : apksInApex) {
835                 // Get information about the currently installed package.
836                 final PackageInfo apkPkgInfo;
837                 try {
838                     apkPkgInfo = getPackageInfo(apkInApex);
839                 } catch (PackageManager.NameNotFoundException e) {
840                     // TODO: Support rolling back fresh package installs rather than
841                     // fail here. Test this case.
842                     Slog.e(TAG, apkInApex + " is not installed");
843                     return false;
844                 }
845                 if (!rollback.enableForPackageInApex(
846                         apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy)) {
847                     return false;
848                 }
849             }
850         }
851
852         /**
853          * The order is important here! Always enable the embedded apk-in-apex (if any) before
854          * enabling the embedding apex. Otherwise the rollback object might be in an inconsistent
855          * state where an embedding apex is successfully enabled while one of its embedded
856          * apk-in-apex failed. Note {@link Rollback#allPackagesEnabled()} won't behave correctly if
857          * a rollback object is inconsistent because it doesn't count apk-in-apex.
858          */
859         ApplicationInfo appInfo = pkgInfo.applicationInfo;
860         return rollback.enableForPackage(packageName, newPackage.versionCode,
861                 pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
862                 appInfo.splitSourceDirs, session.rollbackDataPolicy);
863     }
864
865     @Override
866     public void snapshotAndRestoreUserData(String packageName, int[] userIds, int appId,
867             long ceDataInode, String seInfo, int token) {
868         if (Binder.getCallingUid() != Process.SYSTEM_UID) {
869             throw new SecurityException(
870                     "snapshotAndRestoreUserData may only be called by the system.");
871         }
872
873         getHandler().post(() -> {
874             snapshotUserDataInternal(packageName, userIds);
875             restoreUserDataInternal(packageName, userIds, appId, seInfo);
876             // When this method is called as part of the install flow, a positive token number is
877             // passed to it. Need to notify the PackageManager when we are done.
878             if (token > 0) {
879                 final PackageManagerInternal pmi = LocalServices.getService(
880                         PackageManagerInternal.class);
881                 pmi.finishPackageInstall(token, false);
882             }
883         });
884     }
885
886     @WorkerThread
887     private void snapshotUserDataInternal(String packageName, int[] userIds) {
888         if (LOCAL_LOGV) {
889             Slog.v(TAG, "snapshotUserData pkg=" + packageName
890                     + " users=" + Arrays.toString(userIds));
891         }
892         synchronized (mLock) {
893             for (int i = 0; i < mRollbacks.size(); i++) {
894                 Rollback rollback = mRollbacks.get(i);
895                 rollback.snapshotUserData(packageName, userIds, mAppDataRollbackHelper);
896             }
897         }
898     }
899
900     @WorkerThread
901     private void restoreUserDataInternal(
902             String packageName, int[] userIds, int appId, String seInfo) {
903         if (LOCAL_LOGV) {
904             Slog.v(TAG, "restoreUserData pkg=" + packageName
905                     + " users=" + Arrays.toString(userIds));
906         }
907         synchronized (mLock) {
908             for (int i = 0; i < mRollbacks.size(); ++i) {
909                 Rollback rollback = mRollbacks.get(i);
910                 if (rollback.restoreUserDataForPackageIfInProgress(
911                         packageName, userIds, appId, seInfo, mAppDataRollbackHelper)) {
912                     return;
913                 }
914             }
915         }
916     }
917
918     @Override
919     public int notifyStagedSession(int sessionId) {
920         if (Binder.getCallingUid() != Process.SYSTEM_UID) {
921             throw new SecurityException("notifyStagedSession may only be called by the system.");
922         }
923         final LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>();
924
925         // NOTE: We post this runnable on the RollbackManager's binder thread because we'd prefer
926         // to preserve the invariant that all operations that modify state happen there.
927         getHandler().post(() -> {
928             PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
929
930             final PackageInstaller.SessionInfo session = installer.getSessionInfo(sessionId);
931             if (session == null) {
932                 Slog.e(TAG, "No matching install session for: " + sessionId);
933                 result.offer(-1);
934                 return;
935             }
936
937             Rollback newRollback;
938             synchronized (mLock) {
939                 newRollback = createNewRollbackLocked(session);
940             }
941
942             if (!session.isMultiPackage()) {
943                 if (!enableRollbackForPackageSession(newRollback, session)) {
944                     Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
945                 }
946             } else {
947                 for (int childSessionId : session.getChildSessionIds()) {
948                     final PackageInstaller.SessionInfo childSession =
949                             installer.getSessionInfo(childSessionId);
950                     if (childSession == null) {
951                         Slog.e(TAG, "No matching child install session for: " + childSessionId);
952                         break;
953                     }
954                     if (!enableRollbackForPackageSession(newRollback, childSession)) {
955                         Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
956                         break;
957                     }
958                 }
959             }
960
961             if (!completeEnableRollback(newRollback)) {
962                 result.offer(-1);
963             } else {
964                 result.offer(newRollback.info.getRollbackId());
965             }
966         });
967
968         try {
969             return result.take();
970         } catch (InterruptedException ie) {
971             Slog.e(TAG, "Interrupted while waiting for notifyStagedSession response");
972             return -1;
973         }
974     }
975
976     @Override
977     public void notifyStagedApkSession(int originalSessionId, int apkSessionId) {
978         if (Binder.getCallingUid() != Process.SYSTEM_UID) {
979             throw new SecurityException("notifyStagedApkSession may only be called by the system.");
980         }
981         getHandler().post(() -> {
982             Rollback rollback = null;
983             synchronized (mLock) {
984                 for (int i = 0; i < mRollbacks.size(); ++i) {
985                     Rollback candidate = mRollbacks.get(i);
986                     if (candidate.getStagedSessionId() == originalSessionId) {
987                         rollback = candidate;
988                         break;
989                     }
990                 }
991                 if (rollback == null) {
992                     // Did not find rollback matching originalSessionId.
993                     Slog.e(TAG, "notifyStagedApkSession did not find rollback for session "
994                             + originalSessionId
995                             + ". Adding orphaned apk session " + apkSessionId);
996                     mOrphanedApkSessionIds.add(apkSessionId);
997                 }
998             }
999
1000             if (rollback != null) {
1001                 rollback.setApkSessionId(apkSessionId);
1002             }
1003         });
1004     }
1005
1006     /**
1007      * Returns true if the installer is allowed to enable rollback for the
1008      * given named package, false otherwise.
1009      */
1010     @AnyThread
1011     private boolean enableRollbackAllowed(String installerPackageName, String packageName) {
1012         if (installerPackageName == null) {
1013             return false;
1014         }
1015
1016         PackageManager pm = mContext.getPackageManager();
1017         boolean manageRollbacksGranted = pm.checkPermission(
1018                 Manifest.permission.MANAGE_ROLLBACKS,
1019                 installerPackageName) == PackageManager.PERMISSION_GRANTED;
1020
1021         boolean testManageRollbacksGranted = pm.checkPermission(
1022                 Manifest.permission.TEST_MANAGE_ROLLBACKS,
1023                 installerPackageName) == PackageManager.PERMISSION_GRANTED;
1024
1025         // For now only allow rollbacks for modules or for testing.
1026         return (isRollbackWhitelisted(packageName) && manageRollbacksGranted)
1027             || testManageRollbacksGranted;
1028     }
1029
1030     /**
1031      * Returns true is this package is eligible for enabling rollback.
1032      */
1033     @AnyThread
1034     private boolean isRollbackWhitelisted(String packageName) {
1035         // TODO: Remove #isModule when the white list is ready.
1036         return SystemConfig.getInstance().getRollbackWhitelistedPackages().contains(packageName)
1037                 || isModule(packageName);
1038     }
1039     /**
1040      * Returns true if the package name is the name of a module.
1041      */
1042     @AnyThread
1043     private boolean isModule(String packageName) {
1044         PackageManager pm = mContext.getPackageManager();
1045         final ModuleInfo moduleInfo;
1046         try {
1047             moduleInfo = pm.getModuleInfo(packageName, 0);
1048         } catch (PackageManager.NameNotFoundException e) {
1049             return false;
1050         }
1051
1052         return moduleInfo != null;
1053     }
1054
1055     /**
1056      * Gets the version of the package currently installed.
1057      * Returns -1 if the package is not currently installed.
1058      */
1059     @AnyThread
1060     private long getInstalledPackageVersion(String packageName) {
1061         PackageInfo pkgInfo;
1062         try {
1063             pkgInfo = getPackageInfo(packageName);
1064         } catch (PackageManager.NameNotFoundException e) {
1065             return -1;
1066         }
1067
1068         return pkgInfo.getLongVersionCode();
1069     }
1070
1071     /**
1072      * Gets PackageInfo for the given package. Matches any user and apex.
1073      *
1074      * @throws PackageManager.NameNotFoundException if no such package is installed.
1075      */
1076     @AnyThread
1077     private PackageInfo getPackageInfo(String packageName)
1078             throws PackageManager.NameNotFoundException {
1079         PackageManager pm = mContext.getPackageManager();
1080         try {
1081             // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX
1082             // flag, so make two separate attempts to get the package info.
1083             // We don't need both flags at the same time because we assume
1084             // apex files are always installed for all users.
1085             return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER);
1086         } catch (PackageManager.NameNotFoundException e) {
1087             return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
1088         }
1089     }
1090
1091     @WorkerThread
1092     private class SessionCallback extends PackageInstaller.SessionCallback {
1093
1094         @Override
1095         public void onCreated(int sessionId) { }
1096
1097         @Override
1098         public void onBadgingChanged(int sessionId) { }
1099
1100         @Override
1101         public void onActiveChanged(int sessionId, boolean active) { }
1102
1103         @Override
1104         public void onProgressChanged(int sessionId, float progress) { }
1105
1106         @Override
1107         public void onFinished(int sessionId, boolean success) {
1108             if (LOCAL_LOGV) {
1109                 Slog.v(TAG, "SessionCallback.onFinished id=" + sessionId + " success=" + success);
1110             }
1111
1112             if (success) {
1113                 Rollback rollback;
1114                 synchronized (mLock) {
1115                     rollback = getRollbackForSessionLocked(sessionId);
1116                 }
1117                 if (rollback != null && !rollback.isStaged() && rollback.isEnabling()
1118                         && rollback.notifySessionWithSuccess()
1119                         && completeEnableRollback(rollback)) {
1120                     makeRollbackAvailable(rollback);
1121                 }
1122             } else {
1123                 synchronized (mLock) {
1124                     Rollback rollback = getRollbackForSessionLocked(sessionId);
1125                     if (rollback != null && rollback.isEnabling()) {
1126                         Slog.w(TAG, "Delete rollback id=" + rollback.info.getRollbackId()
1127                                 + " for failed session id=" + sessionId);
1128                         mRollbacks.remove(rollback);
1129                         rollback.delete(mAppDataRollbackHelper);
1130                     }
1131                 }
1132             }
1133         }
1134     }
1135
1136     /**
1137      * Persist a rollback as enable-completed. It does not make the rollback available yet.
1138      * This rollback will be deleted and removed from {@link #mRollbacks} should any error happens.
1139      *
1140      * @return {code true} if {code rollback} is successfully enable-completed,
1141      * or {code false} otherwise.
1142      */
1143     @WorkerThread
1144     private boolean completeEnableRollback(Rollback rollback) {
1145         if (LOCAL_LOGV) {
1146             Slog.v(TAG, "completeEnableRollback id=" + rollback.info.getRollbackId());
1147         }
1148
1149         // We are checking if number of packages (excluding apk-in-apex) we enabled for rollback is
1150         // equal to the number of sessions we are installing, to ensure we didn't skip enabling
1151         // of any sessions. If we successfully enable an apex, then we can assume we enabled
1152         // rollback for the embedded apk-in-apex, if any.
1153         if (!rollback.allPackagesEnabled()) {
1154             Slog.e(TAG, "Failed to enable rollback for all packages in session.");
1155             mRollbacks.remove(rollback);
1156             rollback.delete(mAppDataRollbackHelper);
1157             return false;
1158         }
1159
1160         // Note: There is a small window of time between when
1161         // the session has been committed by the package
1162         // manager and when we make the rollback available
1163         // here. Presumably the window is small enough that
1164         // nobody will want to roll back the newly installed
1165         // package before we make the rollback available.
1166         // TODO: We'll lose the rollback if the
1167         // device reboots between when the session is
1168         // committed and this point. Revisit this after
1169         // adding support for rollback of staged installs.
1170         rollback.saveRollback();
1171
1172         return true;
1173     }
1174
1175     @WorkerThread
1176     @GuardedBy("rollback.getLock")
1177     private void makeRollbackAvailable(Rollback rollback) {
1178         Slog.i(TAG, "makeRollbackAvailable id=" + rollback.info.getRollbackId());
1179         rollback.makeAvailable();
1180
1181         // TODO(zezeozue): Provide API to explicitly start observing instead
1182         // of doing this for all rollbacks. If we do this for all rollbacks,
1183         // should document in PackageInstaller.SessionParams#setEnableRollback
1184         // After enabling and committing any rollback, observe packages and
1185         // prepare to rollback if packages crashes too frequently.
1186         mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
1187                 mRollbackLifetimeDurationInMillis);
1188         runExpiration();
1189     }
1190
1191     /*
1192      * Returns the rollback with the given rollbackId, if any.
1193      */
1194     @WorkerThread
1195     private Rollback getRollbackForId(int rollbackId) {
1196         synchronized (mLock) {
1197             for (int i = 0; i < mRollbacks.size(); ++i) {
1198                 Rollback rollback = mRollbacks.get(i);
1199                 if (rollback.info.getRollbackId() == rollbackId) {
1200                     return rollback;
1201                 }
1202             }
1203         }
1204
1205         return null;
1206     }
1207
1208     @WorkerThread
1209     @GuardedBy("mLock")
1210     private int allocateRollbackIdLocked() {
1211         int n = 0;
1212         int rollbackId;
1213         do {
1214             rollbackId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
1215             if (!mAllocatedRollbackIds.get(rollbackId, false)) {
1216                 mAllocatedRollbackIds.put(rollbackId, true);
1217                 return rollbackId;
1218             }
1219         } while (n++ < 32);
1220
1221         throw new IllegalStateException("Failed to allocate rollback ID");
1222     }
1223
1224     @Override
1225     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1226         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
1227
1228         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
1229         synchronized (mLock) {
1230             for (Rollback rollback : mRollbacks) {
1231                 rollback.dump(ipw);
1232             }
1233             ipw.println();
1234             PackageWatchdog.getInstance(mContext).dump(ipw);
1235         }
1236     }
1237
1238     @AnyThread
1239     private void enforceManageRollbacks(@NonNull String message) {
1240         if ((PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
1241                         Manifest.permission.MANAGE_ROLLBACKS))
1242                 && (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
1243                         Manifest.permission.TEST_MANAGE_ROLLBACKS))) {
1244             throw new SecurityException(message + " requires "
1245                     + Manifest.permission.MANAGE_ROLLBACKS + " or "
1246                     + Manifest.permission.TEST_MANAGE_ROLLBACKS);
1247         }
1248     }
1249
1250     /**
1251      * Creates and returns a Rollback according to the given SessionInfo
1252      * and adds it to {@link #mRollbacks}.
1253      */
1254     @WorkerThread
1255     @GuardedBy("mLock")
1256     private Rollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
1257         int rollbackId = allocateRollbackIdLocked();
1258         final int userId;
1259         if (parentSession.getUser() == UserHandle.ALL) {
1260             userId = UserHandle.USER_SYSTEM;
1261         } else {
1262             userId = parentSession.getUser().getIdentifier();
1263         }
1264         String installerPackageName = parentSession.getInstallerPackageName();
1265         final Rollback rollback;
1266         int parentSessionId = parentSession.getSessionId();
1267
1268         if (LOCAL_LOGV) {
1269             Slog.v(TAG, "createNewRollback id=" + rollbackId
1270                     + " user=" + userId + " installer=" + installerPackageName);
1271         }
1272
1273         int[] packageSessionIds;
1274         if (parentSession.isMultiPackage()) {
1275             packageSessionIds = parentSession.getChildSessionIds();
1276         } else {
1277             packageSessionIds = new int[]{parentSessionId};
1278         }
1279
1280         if (parentSession.isStaged()) {
1281             rollback = mRollbackStore.createStagedRollback(rollbackId, parentSessionId, userId,
1282                     installerPackageName, packageSessionIds, getExtensionVersions());
1283         } else {
1284             rollback = mRollbackStore.createNonStagedRollback(rollbackId, userId,
1285                     installerPackageName, packageSessionIds, getExtensionVersions());
1286         }
1287
1288         mRollbacks.add(rollback);
1289         return rollback;
1290     }
1291
1292     private SparseIntArray getExtensionVersions() {
1293         // This list must be updated whenever the current API level is increased, or should be
1294         // replaced when we have another way of determining the relevant SDK versions.
1295         final int[] relevantSdkVersions = { Build.VERSION_CODES.R };
1296
1297         SparseIntArray result = new SparseIntArray(relevantSdkVersions.length);
1298         for (int i = 0; i < relevantSdkVersions.length; i++) {
1299             result.put(relevantSdkVersions[i],
1300                     SdkExtensions.getExtensionVersion(relevantSdkVersions[i]));
1301         }
1302         return result;
1303     }
1304
1305     /**
1306      * Returns the Rollback associated with the given session if parent or child session id matches.
1307      * Returns null if not found.
1308      */
1309     @WorkerThread
1310     @GuardedBy("mLock")
1311     @Nullable
1312     private Rollback getRollbackForSessionLocked(int sessionId) {
1313         // We expect mRollbacks to be a very small list; linear search should be plenty fast.
1314         for (int i = 0; i < mRollbacks.size(); ++i) {
1315             Rollback rollback = mRollbacks.get(i);
1316             if (rollback.getStagedSessionId() == sessionId
1317                     || rollback.containsSessionId(sessionId)) {
1318                 return rollback;
1319             }
1320         }
1321         return null;
1322     }
1323 }