blob: e107c9aedf38bd025dd7104df1890edec1a97848 [file] [log] [blame]
Richard Uhlere95d0552018-12-27 15:03:41 +00001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.rollback;
18
Richard Uhler1fc10c12019-03-18 11:38:46 +000019import android.Manifest;
Richard Uhler2a5facc2019-02-18 11:33:03 +000020import android.annotation.NonNull;
Richard Uhler82913b72019-04-01 13:02:31 +010021import android.annotation.UserIdInt;
Richard Uhlere95d0552018-12-27 15:03:41 +000022import android.app.AppOpsManager;
23import android.content.BroadcastReceiver;
24import android.content.Context;
Richard Uhlere95d0552018-12-27 15:03:41 +000025import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.IntentSender;
Richard Uhlerab009ea2019-02-25 12:11:05 +000028import android.content.pm.ApplicationInfo;
Richard Uhler1fc10c12019-03-18 11:38:46 +000029import android.content.pm.ModuleInfo;
Richard Uhlere95d0552018-12-27 15:03:41 +000030import android.content.pm.PackageInfo;
31import android.content.pm.PackageInstaller;
32import android.content.pm.PackageManager;
33import android.content.pm.PackageManagerInternal;
34import android.content.pm.PackageParser;
35import android.content.pm.ParceledListSlice;
Richard Uhler82913b72019-04-01 13:02:31 +010036import android.content.pm.UserInfo;
Richard Uhlera7e9b2d2019-01-22 17:20:58 +000037import android.content.pm.VersionedPackage;
Richard Uhlere95d0552018-12-27 15:03:41 +000038import android.content.rollback.IRollbackManager;
39import android.content.rollback.PackageRollbackInfo;
40import android.content.rollback.RollbackInfo;
Richard Uhler2a48c292019-01-28 17:33:48 +000041import android.content.rollback.RollbackManager;
Richard Uhlere95d0552018-12-27 15:03:41 +000042import android.os.Binder;
Richard Uhlere95d0552018-12-27 15:03:41 +000043import android.os.Environment;
44import android.os.Handler;
45import android.os.HandlerThread;
Richard Uhlere95d0552018-12-27 15:03:41 +000046import android.os.ParcelFileDescriptor;
Narayan Kamath869f7062019-01-10 12:24:15 +000047import android.os.Process;
Richard Uhler82913b72019-04-01 13:02:31 +010048import android.os.UserHandle; // duped to avoid merge conflict
49import android.os.UserManager; // out of order to avoid merge conflict
shafikda5e4ee2019-02-12 16:29:01 +000050import android.os.SystemClock;
shafik74fec182019-03-14 16:29:48 +000051import android.os.UserHandle;
shafik0ad18b82019-01-24 16:27:24 +000052import android.provider.DeviceConfig;
Richard Uhler47569702019-05-02 12:36:39 +010053import android.util.ArraySet;
Narayan Kamathc034fe92019-01-23 10:48:17 +000054import android.util.IntArray;
Richard Uhlere95d0552018-12-27 15:03:41 +000055import android.util.Log;
Richard Uhlerb9d54472019-01-22 12:50:08 +000056import android.util.SparseBooleanArray;
Nikita Ioffe952aa7b2019-01-28 19:49:56 +000057import android.util.SparseLongArray;
Richard Uhlere95d0552018-12-27 15:03:41 +000058
59import com.android.internal.annotations.GuardedBy;
Richard Uhlerab009ea2019-02-25 12:11:05 +000060import com.android.internal.util.ArrayUtils;
shafik60046002019-03-12 17:54:10 +000061import com.android.internal.util.IndentingPrintWriter;
Richard Uhlere95d0552018-12-27 15:03:41 +000062import com.android.server.LocalServices;
shafik18b627e2019-05-01 20:41:48 +010063import com.android.server.Watchdog;
Narayan Kamath869f7062019-01-10 12:24:15 +000064import com.android.server.pm.Installer;
Richard Uhlere95d0552018-12-27 15:03:41 +000065
Richard Uhlere95d0552018-12-27 15:03:41 +000066import java.io.File;
shafik60046002019-03-12 17:54:10 +000067import java.io.FileDescriptor;
Richard Uhlere95d0552018-12-27 15:03:41 +000068import java.io.IOException;
shafik60046002019-03-12 17:54:10 +000069import java.io.PrintWriter;
Richard Uhlerb9d54472019-01-22 12:50:08 +000070import java.security.SecureRandom;
Richard Uhlere95d0552018-12-27 15:03:41 +000071import java.time.Instant;
Richard Uhlere95d0552018-12-27 15:03:41 +000072import java.time.temporal.ChronoUnit;
73import java.util.ArrayList;
Richard Uhler1924d6d2019-04-26 10:20:12 +010074import java.util.HashSet;
Richard Uhlere95d0552018-12-27 15:03:41 +000075import java.util.Iterator;
76import java.util.List;
Richard Uhlerb9d54472019-01-22 12:50:08 +000077import java.util.Random;
Richard Uhlere9aaf632019-03-01 16:03:01 +000078import java.util.Set;
Narayan Kamathfcd4a042019-02-01 14:16:37 +000079import java.util.concurrent.LinkedBlockingQueue;
shafik0ad18b82019-01-24 16:27:24 +000080import java.util.concurrent.TimeUnit;
Richard Uhlere95d0552018-12-27 15:03:41 +000081
82/**
83 * Implementation of service that manages APK level rollbacks.
84 */
85class RollbackManagerServiceImpl extends IRollbackManager.Stub {
86
87 private static final String TAG = "RollbackManager";
88
89 // Rollbacks expire after 48 hours.
shafik0ad18b82019-01-24 16:27:24 +000090 private static final long DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS =
91 TimeUnit.HOURS.toMillis(48);
Richard Uhlere95d0552018-12-27 15:03:41 +000092
93 // Lock used to synchronize accesses to in-memory rollback data
94 // structures. By convention, methods with the suffix "Locked" require
95 // mLock is held when they are called.
96 private final Object mLock = new Object();
97
shafik0ad18b82019-01-24 16:27:24 +000098 // No need for guarding with lock because value is only accessed in handler thread
99 // and the value will be written on boot complete. Initialization here happens before
100 // handler threads are running so that's fine.
101 private long mRollbackLifetimeDurationInMillis = DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS;
102
shafik18b627e2019-05-01 20:41:48 +0100103 private static final long HANDLER_THREAD_TIMEOUT_DURATION_MILLIS =
104 TimeUnit.MINUTES.toMillis(10);
105
Richard Uhlerb9d54472019-01-22 12:50:08 +0000106 // Used for generating rollback IDs.
107 private final Random mRandom = new SecureRandom();
108
109 // Set of allocated rollback ids
110 @GuardedBy("mLock")
111 private final SparseBooleanArray mAllocatedRollbackIds = new SparseBooleanArray();
112
Richard Uhler47569702019-05-02 12:36:39 +0100113 // Package rollback data for rollbacks we are in the process of enabling.
Richard Uhler2d7c7f0d2019-01-04 09:18:21 +0000114 @GuardedBy("mLock")
Richard Uhler47569702019-05-02 12:36:39 +0100115 private final Set<NewRollback> mNewRollbacks = new ArraySet<>();
Richard Uhlerf1910c52019-01-09 14:27:36 +0000116
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000117 // The list of all rollbacks, including available and committed rollbacks.
Richard Uhlere95d0552018-12-27 15:03:41 +0000118 // This list is null until the rollback data has been loaded.
119 @GuardedBy("mLock")
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000120 private List<RollbackData> mRollbacks;
Richard Uhlere95d0552018-12-27 15:03:41 +0000121
Richard Uhler28e73232019-01-21 16:48:55 +0000122 private final RollbackStore mRollbackStore;
Richard Uhlere95d0552018-12-27 15:03:41 +0000123
124 private final Context mContext;
125 private final HandlerThread mHandlerThread;
Narayan Kamath869f7062019-01-10 12:24:15 +0000126 private final Installer mInstaller;
Zimuzoc4073cc2019-01-18 18:39:18 +0000127 private final RollbackPackageHealthObserver mPackageHealthObserver;
Nikita Ioffe952aa7b2019-01-28 19:49:56 +0000128 private final AppDataRollbackHelper mAppDataRollbackHelper;
Richard Uhlere95d0552018-12-27 15:03:41 +0000129
shafikda5e4ee2019-02-12 16:29:01 +0000130 // This field stores the difference in Millis between the uptime (millis since device
131 // has booted) and current time (device wall clock) - it's used to update rollback data
132 // timestamps when the time is changed, by the user or by change of timezone.
133 // No need for guarding with lock because value is only accessed in handler thread.
134 private long mRelativeBootTime = calculateRelativeBootTime();
135
Richard Uhlere95d0552018-12-27 15:03:41 +0000136 RollbackManagerServiceImpl(Context context) {
137 mContext = context;
Narayan Kamath869f7062019-01-10 12:24:15 +0000138 // Note that we're calling onStart here because this object is only constructed on
139 // SystemService#onStart.
140 mInstaller = new Installer(mContext);
141 mInstaller.onStart();
Richard Uhlere95d0552018-12-27 15:03:41 +0000142 mHandlerThread = new HandlerThread("RollbackManagerServiceHandler");
143 mHandlerThread.start();
144
shafik18b627e2019-05-01 20:41:48 +0100145 // Monitor the handler thread
146 Watchdog.getInstance().addThread(getHandler(), HANDLER_THREAD_TIMEOUT_DURATION_MILLIS);
147
Richard Uhler28e73232019-01-21 16:48:55 +0000148 mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback"));
Richard Uhlere95d0552018-12-27 15:03:41 +0000149
Zimuzoc4073cc2019-01-18 18:39:18 +0000150 mPackageHealthObserver = new RollbackPackageHealthObserver(mContext);
Nikita Ioffe952aa7b2019-01-28 19:49:56 +0000151 mAppDataRollbackHelper = new AppDataRollbackHelper(mInstaller);
Zimuzoc4073cc2019-01-18 18:39:18 +0000152
Richard Uhlere95d0552018-12-27 15:03:41 +0000153 // Kick off loading of the rollback data from strorage in a background
154 // thread.
155 // TODO: Consider loading the rollback data directly here instead, to
156 // avoid the need to call ensureRollbackDataLoaded every time before
157 // accessing the rollback data?
158 // TODO: Test that this kicks off initial scheduling of rollback
159 // expiration.
160 getHandler().post(() -> ensureRollbackDataLoaded());
161
Richard Uhler82913b72019-04-01 13:02:31 +0100162 // TODO: Make sure to register these call backs when a new user is
163 // added too.
164 SessionCallback sessionCallback = new SessionCallback();
165 for (UserInfo userInfo : UserManager.get(mContext).getUsers(true)) {
166 registerUserCallbacks(userInfo.getUserHandle());
167 }
Richard Uhlere95d0552018-12-27 15:03:41 +0000168
169 IntentFilter enableRollbackFilter = new IntentFilter();
170 enableRollbackFilter.addAction(Intent.ACTION_PACKAGE_ENABLE_ROLLBACK);
171 try {
172 enableRollbackFilter.addDataType("application/vnd.android.package-archive");
173 } catch (IntentFilter.MalformedMimeTypeException e) {
174 Log.e(TAG, "addDataType", e);
175 }
176
177 mContext.registerReceiver(new BroadcastReceiver() {
178 @Override
179 public void onReceive(Context context, Intent intent) {
180 if (Intent.ACTION_PACKAGE_ENABLE_ROLLBACK.equals(intent.getAction())) {
181 int token = intent.getIntExtra(
182 PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN, -1);
183 int installFlags = intent.getIntExtra(
184 PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, 0);
Narayan Kamath869f7062019-01-10 12:24:15 +0000185 int[] installedUsers = intent.getIntArrayExtra(
186 PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS);
Richard Uhler82913b72019-04-01 13:02:31 +0100187 int user = intent.getIntExtra(
188 PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_USER, 0);
189
Richard Uhlere95d0552018-12-27 15:03:41 +0000190 File newPackageCodePath = new File(intent.getData().getPath());
191
192 getHandler().post(() -> {
Narayan Kamath869f7062019-01-10 12:24:15 +0000193 boolean success = enableRollback(installFlags, newPackageCodePath,
shafik4831ad72019-05-03 17:36:42 +0100194 installedUsers, user, token);
Richard Uhlere95d0552018-12-27 15:03:41 +0000195 int ret = PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED;
196 if (!success) {
197 ret = PackageManagerInternal.ENABLE_ROLLBACK_FAILED;
198 }
199
200 PackageManagerInternal pm = LocalServices.getService(
201 PackageManagerInternal.class);
202 pm.setEnableRollbackCode(token, ret);
203 });
204
205 // We're handling the ordered broadcast. Abort the
206 // broadcast because there is no need for it to go to
207 // anyone else.
208 abortBroadcast();
209 }
210 }
211 }, enableRollbackFilter, null, getHandler());
shafikda5e4ee2019-02-12 16:29:01 +0000212
shafik4831ad72019-05-03 17:36:42 +0100213 IntentFilter enableRollbackTimedOutFilter = new IntentFilter();
214 enableRollbackTimedOutFilter.addAction(Intent.ACTION_CANCEL_ENABLE_ROLLBACK);
215
216 mContext.registerReceiver(new BroadcastReceiver() {
217 @Override
218 public void onReceive(Context context, Intent intent) {
219 if (Intent.ACTION_CANCEL_ENABLE_ROLLBACK.equals(intent.getAction())) {
220 int token = intent.getIntExtra(
221 PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN, -1);
222 synchronized (mLock) {
223 for (NewRollback rollback : mNewRollbacks) {
224 if (rollback.hasToken(token)) {
225 rollback.isCancelled = true;
226 return;
227 }
228 }
229 }
230 }
231 }
232 }, enableRollbackTimedOutFilter, null, getHandler());
233
shafik8aea1ad2019-05-21 12:34:50 +0100234 IntentFilter userAddedIntentFilter = new IntentFilter(Intent.ACTION_USER_ADDED);
235 mContext.registerReceiver(new BroadcastReceiver() {
236 @Override
237 public void onReceive(Context context, Intent intent) {
238 if (Intent.ACTION_USER_ADDED.equals(intent.getAction())) {
239 final int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
240 if (newUserId == -1) {
241 return;
242 }
243 registerUserCallbacks(UserHandle.of(newUserId));
244 }
245 }
246 }, userAddedIntentFilter, null, getHandler());
247
shafikda5e4ee2019-02-12 16:29:01 +0000248 registerTimeChangeReceiver();
Richard Uhlere95d0552018-12-27 15:03:41 +0000249 }
250
Richard Uhler82913b72019-04-01 13:02:31 +0100251 private void registerUserCallbacks(UserHandle user) {
252 Context context = getContextAsUser(user);
253 if (context == null) {
254 Log.e(TAG, "Unable to register user callbacks for user " + user);
255 return;
256 }
257
258 // TODO: Reuse the same SessionCallback and broadcast receiver
259 // instances, rather than creating new instances for each user.
260
261 context.getPackageManager().getPackageInstaller()
262 .registerSessionCallback(new SessionCallback(), getHandler());
263
264 IntentFilter filter = new IntentFilter();
265 filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
266 filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
267 filter.addDataScheme("package");
268 context.registerReceiver(new BroadcastReceiver() {
269 @Override
270 public void onReceive(Context context, Intent intent) {
271 String action = intent.getAction();
272 if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
273 String packageName = intent.getData().getSchemeSpecificPart();
274 onPackageReplaced(packageName);
275 }
276 if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
277 String packageName = intent.getData().getSchemeSpecificPart();
278 onPackageFullyRemoved(packageName);
279 }
280 }
281 }, filter, null, getHandler());
282 }
283
shafikcf968e92019-05-01 19:00:06 +0100284 /**
285 * This method posts a blocking call to the handler thread, so it should not be called from
286 * that same thread.
287 * @throws {@link IllegalStateException} if called from {@link #mHandlerThread}
288 */
Richard Uhlere95d0552018-12-27 15:03:41 +0000289 @Override
Richard Uhler150ad982019-01-23 15:16:10 +0000290 public ParceledListSlice getAvailableRollbacks() {
Richard Uhler1fc10c12019-03-18 11:38:46 +0000291 enforceManageRollbacks("getAvailableRollbacks");
shafikcf968e92019-05-01 19:00:06 +0100292 if (Thread.currentThread().equals(mHandlerThread)) {
293 Log.wtf(TAG, "Calling getAvailableRollbacks from mHandlerThread "
294 + "causes a deadlock");
295 throw new IllegalStateException("Cannot call RollbackManager#getAvailableRollbacks "
296 + "from the handler thread!");
297 }
Richard Uhlere95d0552018-12-27 15:03:41 +0000298
Richard Uhler0741b412019-03-27 13:27:55 +0000299 // Wait for the handler thread to get the list of available rollbacks
300 // to get the most up-to-date results. This is intended to reduce test
301 // flakiness when checking available rollbacks immediately after
302 // installing a package with rollback enabled.
303 final LinkedBlockingQueue<Boolean> result = new LinkedBlockingQueue<>();
304 getHandler().post(() -> result.offer(true));
305
306 try {
307 result.take();
308 } catch (InterruptedException ie) {
309 // We may not get the most up-to-date information, but whatever we
310 // can get now is better than nothing, so log but otherwise ignore
311 // the exception.
312 Log.w(TAG, "Interrupted while waiting for handler thread in getAvailableRollbacks");
313 }
314
Richard Uhlere95d0552018-12-27 15:03:41 +0000315 synchronized (mLock) {
316 ensureRollbackDataLoadedLocked();
Richard Uhler150ad982019-01-23 15:16:10 +0000317 List<RollbackInfo> rollbacks = new ArrayList<>();
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000318 for (int i = 0; i < mRollbacks.size(); ++i) {
319 RollbackData data = mRollbacks.get(i);
320 if (data.state == RollbackData.ROLLBACK_STATE_AVAILABLE) {
Richard Uhlercca637a2019-02-27 11:50:48 +0000321 rollbacks.add(data.info);
Richard Uhler60ac7062019-02-05 13:25:39 +0000322 }
Richard Uhlere95d0552018-12-27 15:03:41 +0000323 }
Richard Uhler150ad982019-01-23 15:16:10 +0000324 return new ParceledListSlice<>(rollbacks);
Richard Uhlere95d0552018-12-27 15:03:41 +0000325 }
Richard Uhlere95d0552018-12-27 15:03:41 +0000326 }
327
328 @Override
329 public ParceledListSlice<RollbackInfo> getRecentlyExecutedRollbacks() {
Richard Uhler1fc10c12019-03-18 11:38:46 +0000330 enforceManageRollbacks("getRecentlyCommittedRollbacks");
Richard Uhlere95d0552018-12-27 15:03:41 +0000331
332 synchronized (mLock) {
333 ensureRollbackDataLoadedLocked();
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000334 List<RollbackInfo> rollbacks = new ArrayList<>();
335 for (int i = 0; i < mRollbacks.size(); ++i) {
336 RollbackData data = mRollbacks.get(i);
337 if (data.state == RollbackData.ROLLBACK_STATE_COMMITTED) {
338 rollbacks.add(data.info);
339 }
340 }
Richard Uhlere95d0552018-12-27 15:03:41 +0000341 return new ParceledListSlice<>(rollbacks);
342 }
343 }
344
345 @Override
Richard Uhlerbf5b5c42019-01-28 15:26:37 +0000346 public void commitRollback(int rollbackId, ParceledListSlice causePackages,
347 String callerPackageName, IntentSender statusReceiver) {
Richard Uhler1fc10c12019-03-18 11:38:46 +0000348 enforceManageRollbacks("executeRollback");
Richard Uhlere95d0552018-12-27 15:03:41 +0000349
350 final int callingUid = Binder.getCallingUid();
351 AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
352 appOps.checkPackage(callingUid, callerPackageName);
353
354 getHandler().post(() ->
Richard Uhlerbf5b5c42019-01-28 15:26:37 +0000355 commitRollbackInternal(rollbackId, causePackages.getList(),
356 callerPackageName, statusReceiver));
Richard Uhlere95d0552018-12-27 15:03:41 +0000357 }
358
shafikda5e4ee2019-02-12 16:29:01 +0000359 private void registerTimeChangeReceiver() {
360 final BroadcastReceiver timeChangeIntentReceiver = new BroadcastReceiver() {
361 @Override
362 public void onReceive(Context context, Intent intent) {
363 final long oldRelativeBootTime = mRelativeBootTime;
364 mRelativeBootTime = calculateRelativeBootTime();
365 final long timeDifference = mRelativeBootTime - oldRelativeBootTime;
366
367 synchronized (mLock) {
368 ensureRollbackDataLoadedLocked();
369
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000370 Iterator<RollbackData> iter = mRollbacks.iterator();
shafikda5e4ee2019-02-12 16:29:01 +0000371 while (iter.hasNext()) {
372 RollbackData data = iter.next();
shafikda5e4ee2019-02-12 16:29:01 +0000373 data.timestamp = data.timestamp.plusMillis(timeDifference);
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000374 saveRollbackData(data);
shafikda5e4ee2019-02-12 16:29:01 +0000375 }
shafikda5e4ee2019-02-12 16:29:01 +0000376 }
377 }
378 };
379 final IntentFilter filter = new IntentFilter();
380 filter.addAction(Intent.ACTION_TIME_CHANGED);
381 mContext.registerReceiver(timeChangeIntentReceiver, filter,
382 null /* broadcastPermission */, getHandler());
383 }
384
385 private static long calculateRelativeBootTime() {
386 return System.currentTimeMillis() - SystemClock.elapsedRealtime();
387 }
388
Richard Uhlere95d0552018-12-27 15:03:41 +0000389 /**
Richard Uhlere87368e2019-01-24 16:34:14 +0000390 * Performs the actual work to commit a rollback.
Richard Uhlere95d0552018-12-27 15:03:41 +0000391 * The work is done on the current thread. This may be a long running
392 * operation.
393 */
Richard Uhlerbf5b5c42019-01-28 15:26:37 +0000394 private void commitRollbackInternal(int rollbackId, List<VersionedPackage> causePackages,
Richard Uhlere95d0552018-12-27 15:03:41 +0000395 String callerPackageName, IntentSender statusReceiver) {
Richard Uhler0a79b322019-01-23 13:51:07 +0000396 Log.i(TAG, "Initiating rollback");
Richard Uhlere95d0552018-12-27 15:03:41 +0000397
Richard Uhlere87368e2019-01-24 16:34:14 +0000398 RollbackData data = getRollbackForId(rollbackId);
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000399 if (data == null || data.state != RollbackData.ROLLBACK_STATE_AVAILABLE) {
Richard Uhler2a48c292019-01-28 17:33:48 +0000400 sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
401 "Rollback unavailable");
Narayan Kamathbc36f8d2019-01-23 12:00:08 +0000402 return;
403 }
404
Richard Uhlere95d0552018-12-27 15:03:41 +0000405 // Get a context for the caller to use to install the downgraded
406 // version of the package.
407 Context context = null;
408 try {
409 context = mContext.createPackageContext(callerPackageName, 0);
410 } catch (PackageManager.NameNotFoundException e) {
Richard Uhler2a48c292019-01-28 17:33:48 +0000411 sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
412 "Invalid callerPackageName");
Richard Uhlere95d0552018-12-27 15:03:41 +0000413 return;
414 }
415
416 PackageManager pm = context.getPackageManager();
417 try {
Richard Uhlere95d0552018-12-27 15:03:41 +0000418 PackageInstaller packageInstaller = pm.getPackageInstaller();
Richard Uhler01b06152019-01-09 13:51:54 +0000419 PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
Richard Uhlere95d0552018-12-27 15:03:41 +0000420 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
Nikita Ioffeb1d60f12019-03-06 18:56:49 +0000421 parentParams.setRequestDowngrade(true);
Richard Uhler01b06152019-01-09 13:51:54 +0000422 parentParams.setMultiPackage();
Richard Uhler72fb9612019-02-04 14:37:36 +0000423 if (data.isStaged()) {
424 parentParams.setStaged();
425 }
426
Richard Uhler01b06152019-01-09 13:51:54 +0000427 int parentSessionId = packageInstaller.createSession(parentParams);
428 PackageInstaller.Session parentSession = packageInstaller.openSession(parentSessionId);
Richard Uhlere95d0552018-12-27 15:03:41 +0000429
Richard Uhlercca637a2019-02-27 11:50:48 +0000430 for (PackageRollbackInfo info : data.info.getPackages()) {
Richard Uhler01b06152019-01-09 13:51:54 +0000431 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
432 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
Richard Uhler7a5eeb12019-02-05 12:51:48 +0000433 // TODO: We can't get the installerPackageName for apex
434 // (b/123920130). Is it okay to ignore the installer package
435 // for apex?
436 if (!info.isApex()) {
437 String installerPackageName = pm.getInstallerPackageName(info.getPackageName());
Richard Uhlera7d18bb2019-02-06 11:58:17 +0000438 if (installerPackageName != null) {
439 params.setInstallerPackageName(installerPackageName);
Richard Uhler7a5eeb12019-02-05 12:51:48 +0000440 }
Richard Uhler0a79b322019-01-23 13:51:07 +0000441 }
Nikita Ioffeb1d60f12019-03-06 18:56:49 +0000442 params.setRequestDowngrade(true);
Richard Uhler2124d4b2019-04-25 13:01:39 +0100443 params.setRequiredInstalledVersionCode(
444 info.getVersionRolledBackFrom().getLongVersionCode());
Richard Uhler72fb9612019-02-04 14:37:36 +0000445 if (data.isStaged()) {
446 params.setStaged();
447 }
448 if (info.isApex()) {
449 params.setInstallAsApex();
450 }
Richard Uhler01b06152019-01-09 13:51:54 +0000451 int sessionId = packageInstaller.createSession(params);
452 PackageInstaller.Session session = packageInstaller.openSession(sessionId);
453
Richard Uhlerab009ea2019-02-25 12:11:05 +0000454 File[] packageCodePaths = RollbackStore.getPackageCodePaths(
455 data, info.getPackageName());
456 if (packageCodePaths == null) {
Richard Uhler1f571c62019-01-31 15:16:46 +0000457 sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
Richard Uhlerab009ea2019-02-25 12:11:05 +0000458 "Backup copy of package inaccessible");
Richard Uhler1f571c62019-01-31 15:16:46 +0000459 return;
460 }
461
Richard Uhlerab009ea2019-02-25 12:11:05 +0000462 for (File packageCodePath : packageCodePaths) {
463 try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
464 ParcelFileDescriptor.MODE_READ_ONLY)) {
465 final long token = Binder.clearCallingIdentity();
466 try {
467 session.write(packageCodePath.getName(), 0, packageCodePath.length(),
468 fd);
469 } finally {
470 Binder.restoreCallingIdentity(token);
471 }
Richard Uhler01b06152019-01-09 13:51:54 +0000472 }
Richard Uhlere95d0552018-12-27 15:03:41 +0000473 }
Richard Uhler01b06152019-01-09 13:51:54 +0000474 parentSession.addChildSessionId(sessionId);
Richard Uhlere95d0552018-12-27 15:03:41 +0000475 }
476
Narayan Kamath869f7062019-01-10 12:24:15 +0000477 final LocalIntentReceiver receiver = new LocalIntentReceiver(
478 (Intent result) -> {
Zimuzoc4073cc2019-01-18 18:39:18 +0000479 getHandler().post(() -> {
Narayan Kamathbc36f8d2019-01-23 12:00:08 +0000480
Zimuzoc4073cc2019-01-18 18:39:18 +0000481 int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
482 PackageInstaller.STATUS_FAILURE);
483 if (status != PackageInstaller.STATUS_SUCCESS) {
Richard Uhlere9aaf632019-03-01 16:03:01 +0000484 // Committing the rollback failed, but we
485 // still have all the info we need to try
486 // rolling back again, so restore the rollback
487 // state to how it was before we tried
488 // committing.
489 // TODO: Should we just kill this rollback if
490 // commit failed? Why would we expect commit
491 // not to fail again?
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000492 synchronized (mLock) {
Richard Uhlere9aaf632019-03-01 16:03:01 +0000493 // TODO: Could this cause a rollback to be
494 // resurrected if it should otherwise have
495 // expired by now?
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000496 data.state = RollbackData.ROLLBACK_STATE_AVAILABLE;
497 data.restoreUserDataInProgress = false;
498 }
Richard Uhler2a48c292019-01-28 17:33:48 +0000499 sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_INSTALL,
Zimuzoc4073cc2019-01-18 18:39:18 +0000500 "Rollback downgrade install failed: "
501 + result.getStringExtra(
502 PackageInstaller.EXTRA_STATUS_MESSAGE));
503 return;
504 }
Narayan Kamath869f7062019-01-10 12:24:15 +0000505
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000506 synchronized (mLock) {
507 if (!data.isStaged()) {
508 // All calls to restoreUserData should have
509 // completed by now for a non-staged install.
510 data.restoreUserDataInProgress = false;
511 }
512
513 data.info.setCommittedSessionId(parentSessionId);
514 data.info.getCausePackages().addAll(causePackages);
515 }
516 mRollbackStore.deletePackageCodePaths(data);
517 saveRollbackData(data);
518
Zimuzoc4073cc2019-01-18 18:39:18 +0000519 sendSuccess(statusReceiver);
Narayan Kamath869f7062019-01-10 12:24:15 +0000520
Richard Uhlerdca7beb2019-01-24 09:56:03 +0000521 Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
Narayan Kamath869f7062019-01-10 12:24:15 +0000522
shafik74fec182019-03-14 16:29:48 +0000523 mContext.sendBroadcastAsUser(broadcast, UserHandle.SYSTEM,
524 Manifest.permission.MANAGE_ROLLBACKS);
Zimuzoc4073cc2019-01-18 18:39:18 +0000525 });
Narayan Kamath869f7062019-01-10 12:24:15 +0000526 }
527 );
528
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000529 synchronized (mLock) {
530 data.state = RollbackData.ROLLBACK_STATE_COMMITTED;
531 data.restoreUserDataInProgress = true;
532 }
Richard Uhler01b06152019-01-09 13:51:54 +0000533 parentSession.commit(receiver.getIntentSender());
Richard Uhlere95d0552018-12-27 15:03:41 +0000534 } catch (IOException e) {
Richard Uhler0a79b322019-01-23 13:51:07 +0000535 Log.e(TAG, "Rollback failed", e);
Richard Uhler2a48c292019-01-28 17:33:48 +0000536 sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
537 "IOException: " + e.toString());
Richard Uhlere95d0552018-12-27 15:03:41 +0000538 return;
539 }
540 }
541
542 @Override
543 public void reloadPersistedData() {
544 mContext.enforceCallingOrSelfPermission(
Richard Uhler1fc10c12019-03-18 11:38:46 +0000545 Manifest.permission.TEST_MANAGE_ROLLBACKS,
Richard Uhlere95d0552018-12-27 15:03:41 +0000546 "reloadPersistedData");
547
548 synchronized (mLock) {
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000549 mRollbacks = null;
Richard Uhlere95d0552018-12-27 15:03:41 +0000550 }
shafik6d61f5e2019-02-26 09:33:26 +0000551 getHandler().post(() -> {
552 updateRollbackLifetimeDurationInMillis();
553 ensureRollbackDataLoaded();
554 });
Richard Uhlere95d0552018-12-27 15:03:41 +0000555 }
556
557 @Override
558 public void expireRollbackForPackage(String packageName) {
559 mContext.enforceCallingOrSelfPermission(
Richard Uhler1fc10c12019-03-18 11:38:46 +0000560 Manifest.permission.TEST_MANAGE_ROLLBACKS,
Richard Uhlere95d0552018-12-27 15:03:41 +0000561 "expireRollbackForPackage");
Richard Uhlere95d0552018-12-27 15:03:41 +0000562 synchronized (mLock) {
563 ensureRollbackDataLoadedLocked();
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000564 Iterator<RollbackData> iter = mRollbacks.iterator();
Richard Uhlere95d0552018-12-27 15:03:41 +0000565 while (iter.hasNext()) {
Richard Uhler32f63a72019-01-09 10:43:52 +0000566 RollbackData data = iter.next();
Richard Uhlercca637a2019-02-27 11:50:48 +0000567 for (PackageRollbackInfo info : data.info.getPackages()) {
Richard Uhlera7e9b2d2019-01-22 17:20:58 +0000568 if (info.getPackageName().equals(packageName)) {
Richard Uhler01b06152019-01-09 13:51:54 +0000569 iter.remove();
Nikita Ioffe952aa7b2019-01-28 19:49:56 +0000570 deleteRollback(data);
Richard Uhler01b06152019-01-09 13:51:54 +0000571 break;
572 }
Richard Uhlere95d0552018-12-27 15:03:41 +0000573 }
574 }
575 }
576 }
577
Narayan Kamathc034fe92019-01-23 10:48:17 +0000578 void onUnlockUser(int userId) {
579 getHandler().post(() -> {
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000580 final List<RollbackData> rollbacks;
Narayan Kamathc034fe92019-01-23 10:48:17 +0000581 synchronized (mLock) {
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000582 rollbacks = new ArrayList<>(mRollbacks);
Narayan Kamathc034fe92019-01-23 10:48:17 +0000583 }
584
Richard Uhlere9aaf632019-03-01 16:03:01 +0000585 final Set<RollbackData> changed =
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000586 mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId, rollbacks);
Narayan Kamathc034fe92019-01-23 10:48:17 +0000587
588 for (RollbackData rd : changed) {
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000589 saveRollbackData(rd);
Narayan Kamathc034fe92019-01-23 10:48:17 +0000590 }
591 });
592 }
593
shafik0ad18b82019-01-24 16:27:24 +0000594 private void updateRollbackLifetimeDurationInMillis() {
Stanislav Zholnin6b85dac2019-03-07 12:16:04 +0000595 mRollbackLifetimeDurationInMillis = DeviceConfig.getLong(
Matt Pape12187ae2019-03-14 13:37:32 -0700596 DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
597 RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
Stanislav Zholnin6b85dac2019-03-07 12:16:04 +0000598 DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS);
shafik15c511f2019-03-08 11:52:08 +0000599 if (mRollbackLifetimeDurationInMillis < 0) {
600 mRollbackLifetimeDurationInMillis = DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS;
601 }
shafik0ad18b82019-01-24 16:27:24 +0000602 }
603
Richard Uhlerb2c5cac2019-02-05 16:14:35 +0000604 void onBootCompleted() {
shafik0ad18b82019-01-24 16:27:24 +0000605 getHandler().post(() -> updateRollbackLifetimeDurationInMillis());
606 // Also posts to handler thread
607 scheduleExpiration(0);
608
Richard Uhlerb2c5cac2019-02-05 16:14:35 +0000609 getHandler().post(() -> {
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000610 // Check to see if any rollback-enabled staged sessions or staged
611 // rollback sessions been applied.
Richard Uhlere9aaf632019-03-01 16:03:01 +0000612 List<RollbackData> enabling = new ArrayList<>();
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000613 List<RollbackData> restoreInProgress = new ArrayList<>();
Richard Uhler1924d6d2019-04-26 10:20:12 +0100614 Set<String> apexPackageNames = new HashSet<>();
Richard Uhlerb2c5cac2019-02-05 16:14:35 +0000615 synchronized (mLock) {
616 ensureRollbackDataLoadedLocked();
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000617 for (RollbackData data : mRollbacks) {
618 if (data.isStaged()) {
Richard Uhlere9aaf632019-03-01 16:03:01 +0000619 if (data.state == RollbackData.ROLLBACK_STATE_ENABLING) {
620 enabling.add(data);
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000621 } else if (data.restoreUserDataInProgress) {
622 restoreInProgress.add(data);
623 }
Richard Uhler1924d6d2019-04-26 10:20:12 +0100624
625 for (PackageRollbackInfo info : data.info.getPackages()) {
626 if (info.isApex()) {
627 apexPackageNames.add(info.getPackageName());
628 }
629 }
Richard Uhlerb2c5cac2019-02-05 16:14:35 +0000630 }
631 }
632 }
633
Richard Uhlere9aaf632019-03-01 16:03:01 +0000634 for (RollbackData data : enabling) {
Richard Uhlerb2c5cac2019-02-05 16:14:35 +0000635 PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
636 PackageInstaller.SessionInfo session = installer.getSessionInfo(
637 data.stagedSessionId);
Richard Uhlere9aaf632019-03-01 16:03:01 +0000638 // TODO: What if session is null?
Richard Uhlerb2c5cac2019-02-05 16:14:35 +0000639 if (session != null) {
Dario Freni60a96c12019-02-24 21:01:29 +0000640 if (session.isStagedSessionApplied()) {
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000641 makeRollbackAvailable(data);
Dario Freni60a96c12019-02-24 21:01:29 +0000642 } else if (session.isStagedSessionFailed()) {
Richard Uhlerb2c5cac2019-02-05 16:14:35 +0000643 // TODO: Do we need to remove this from
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000644 // mRollbacks, or is it okay to leave as
Richard Uhlerb2c5cac2019-02-05 16:14:35 +0000645 // unavailable until the next reboot when it will go
646 // away on its own?
Nikita Ioffe952aa7b2019-01-28 19:49:56 +0000647 deleteRollback(data);
Richard Uhlerb2c5cac2019-02-05 16:14:35 +0000648 }
649 }
650 }
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000651
652 for (RollbackData data : restoreInProgress) {
653 PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
654 PackageInstaller.SessionInfo session = installer.getSessionInfo(
655 data.stagedSessionId);
Richard Uhlere9aaf632019-03-01 16:03:01 +0000656 // TODO: What if session is null?
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000657 if (session != null) {
658 if (session.isStagedSessionApplied() || session.isStagedSessionFailed()) {
659 synchronized (mLock) {
660 data.restoreUserDataInProgress = false;
661 }
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000662 saveRollbackData(data);
663 }
664 }
665 }
Zimuzo841c4942019-03-04 12:31:48 +0000666
Richard Uhler1924d6d2019-04-26 10:20:12 +0100667 for (String apexPackageName : apexPackageNames) {
668 // We will not recieve notifications when an apex is updated,
669 // so check now in case any rollbacks ought to be expired. The
670 // onPackagedReplace function is safe to call if the package
671 // hasn't actually been updated.
672 onPackageReplaced(apexPackageName);
673 }
shafikc5805fb92019-04-29 20:08:07 +0100674 mPackageHealthObserver.onBootCompletedAsync();
Richard Uhlerb2c5cac2019-02-05 16:14:35 +0000675 });
676 }
677
Richard Uhlere95d0552018-12-27 15:03:41 +0000678 /**
679 * Load rollback data from storage if it has not already been loaded.
680 * After calling this funciton, mAvailableRollbacks and
681 * mRecentlyExecutedRollbacks will be non-null.
682 */
683 private void ensureRollbackDataLoaded() {
684 synchronized (mLock) {
685 ensureRollbackDataLoadedLocked();
686 }
687 }
688
689 /**
690 * Load rollback data from storage if it has not already been loaded.
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000691 * After calling this function, mRollbacks will be non-null.
Richard Uhlere95d0552018-12-27 15:03:41 +0000692 */
693 @GuardedBy("mLock")
694 private void ensureRollbackDataLoadedLocked() {
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000695 if (mRollbacks == null) {
Richard Uhlerb7e2d922019-01-21 14:34:50 +0000696 loadAllRollbackDataLocked();
Richard Uhlere95d0552018-12-27 15:03:41 +0000697 }
698 }
699
700 /**
Richard Uhlerb7e2d922019-01-21 14:34:50 +0000701 * Load all rollback data from storage.
Richard Uhlere95d0552018-12-27 15:03:41 +0000702 * Note: We do potentially heavy IO here while holding mLock, because we
703 * have to have the rollback data loaded before we can do anything else
704 * meaningful.
705 */
706 @GuardedBy("mLock")
Richard Uhlerb7e2d922019-01-21 14:34:50 +0000707 private void loadAllRollbackDataLocked() {
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000708 mRollbacks = mRollbackStore.loadAllRollbackData();
709 for (RollbackData data : mRollbacks) {
Richard Uhlercca637a2019-02-27 11:50:48 +0000710 mAllocatedRollbackIds.put(data.info.getRollbackId(), true);
Richard Uhlerb9d54472019-01-22 12:50:08 +0000711 }
Richard Uhlere95d0552018-12-27 15:03:41 +0000712 }
713
714 /**
715 * Called when a package has been replaced with a different version.
716 * Removes all backups for the package not matching the currently
717 * installed package version.
718 */
719 private void onPackageReplaced(String packageName) {
720 // TODO: Could this end up incorrectly deleting a rollback for a
721 // package that is about to be installed?
Richard Uhlera7e9b2d2019-01-22 17:20:58 +0000722 VersionedPackage installedVersion = getInstalledPackageVersion(packageName);
Richard Uhlere95d0552018-12-27 15:03:41 +0000723
724 synchronized (mLock) {
725 ensureRollbackDataLoadedLocked();
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000726 Iterator<RollbackData> iter = mRollbacks.iterator();
Richard Uhlere95d0552018-12-27 15:03:41 +0000727 while (iter.hasNext()) {
Richard Uhler32f63a72019-01-09 10:43:52 +0000728 RollbackData data = iter.next();
Richard Uhlere9aaf632019-03-01 16:03:01 +0000729 // TODO: Should we remove rollbacks in the ENABLING state here?
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000730 if (data.state == RollbackData.ROLLBACK_STATE_AVAILABLE
Richard Uhlere9aaf632019-03-01 16:03:01 +0000731 || data.state == RollbackData.ROLLBACK_STATE_ENABLING) {
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000732 for (PackageRollbackInfo info : data.info.getPackages()) {
733 if (info.getPackageName().equals(packageName)
734 && !packageVersionsEqual(
735 info.getVersionRolledBackFrom(),
736 installedVersion)) {
737 iter.remove();
738 deleteRollback(data);
739 break;
740 }
Richard Uhler01b06152019-01-09 13:51:54 +0000741 }
Richard Uhlere95d0552018-12-27 15:03:41 +0000742 }
743 }
744 }
745 }
746
747 /**
748 * Called when a package has been completely removed from the device.
749 * Removes all backups and rollback history for the given package.
750 */
751 private void onPackageFullyRemoved(String packageName) {
752 expireRollbackForPackage(packageName);
Richard Uhlere95d0552018-12-27 15:03:41 +0000753 }
754
755 /**
756 * Notifies an IntentSender of failure.
757 *
758 * @param statusReceiver where to send the failure
Richard Uhler2a48c292019-01-28 17:33:48 +0000759 * @param status the RollbackManager.STATUS_* code with the failure.
Richard Uhlere95d0552018-12-27 15:03:41 +0000760 * @param message the failure message.
761 */
Richard Uhlera4b70302019-03-11 14:34:33 +0000762 private void sendFailure(IntentSender statusReceiver, @RollbackManager.Status int status,
763 String message) {
Richard Uhlere95d0552018-12-27 15:03:41 +0000764 Log.e(TAG, message);
765 try {
Richard Uhlere95d0552018-12-27 15:03:41 +0000766 final Intent fillIn = new Intent();
Richard Uhler2a48c292019-01-28 17:33:48 +0000767 fillIn.putExtra(RollbackManager.EXTRA_STATUS, status);
768 fillIn.putExtra(RollbackManager.EXTRA_STATUS_MESSAGE, message);
Richard Uhlere95d0552018-12-27 15:03:41 +0000769 statusReceiver.sendIntent(mContext, 0, fillIn, null, null);
770 } catch (IntentSender.SendIntentException e) {
771 // Nowhere to send the result back to, so don't bother.
772 }
773 }
774
775 /**
776 * Notifies an IntentSender of success.
777 */
778 private void sendSuccess(IntentSender statusReceiver) {
779 try {
780 final Intent fillIn = new Intent();
Richard Uhler2a48c292019-01-28 17:33:48 +0000781 fillIn.putExtra(RollbackManager.EXTRA_STATUS, RollbackManager.STATUS_SUCCESS);
Richard Uhlere95d0552018-12-27 15:03:41 +0000782 statusReceiver.sendIntent(mContext, 0, fillIn, null, null);
783 } catch (IntentSender.SendIntentException e) {
784 // Nowhere to send the result back to, so don't bother.
785 }
786 }
787
788 // Check to see if anything needs expiration, and if so, expire it.
789 // Schedules future expiration as appropriate.
Richard Uhlere95d0552018-12-27 15:03:41 +0000790 private void runExpiration() {
791 Instant now = Instant.now();
792 Instant oldest = null;
793 synchronized (mLock) {
794 ensureRollbackDataLoadedLocked();
795
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000796 Iterator<RollbackData> iter = mRollbacks.iterator();
Richard Uhlere95d0552018-12-27 15:03:41 +0000797 while (iter.hasNext()) {
Richard Uhler32f63a72019-01-09 10:43:52 +0000798 RollbackData data = iter.next();
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000799 if (data.state != RollbackData.ROLLBACK_STATE_AVAILABLE) {
Richard Uhler60ac7062019-02-05 13:25:39 +0000800 continue;
801 }
shafik0ad18b82019-01-24 16:27:24 +0000802 if (!now.isBefore(data.timestamp.plusMillis(mRollbackLifetimeDurationInMillis))) {
Richard Uhlere95d0552018-12-27 15:03:41 +0000803 iter.remove();
Nikita Ioffe952aa7b2019-01-28 19:49:56 +0000804 deleteRollback(data);
Richard Uhlere95d0552018-12-27 15:03:41 +0000805 } else if (oldest == null || oldest.isAfter(data.timestamp)) {
806 oldest = data.timestamp;
807 }
808 }
809 }
810
811 if (oldest != null) {
shafik0ad18b82019-01-24 16:27:24 +0000812 scheduleExpiration(now.until(oldest.plusMillis(mRollbackLifetimeDurationInMillis),
Richard Uhlere95d0552018-12-27 15:03:41 +0000813 ChronoUnit.MILLIS));
814 }
815 }
816
817 /**
818 * Schedules an expiration check to be run after the given duration in
819 * milliseconds has gone by.
820 */
821 private void scheduleExpiration(long duration) {
822 getHandler().postDelayed(() -> runExpiration(), duration);
823 }
824
825 private Handler getHandler() {
826 return mHandlerThread.getThreadHandler();
827 }
828
Richard Uhler2d7c7f0d2019-01-04 09:18:21 +0000829 // Returns true if <code>session</code> has installFlags and code path
830 // matching the installFlags and new package code path given to
831 // enableRollback.
832 private boolean sessionMatchesForEnableRollback(PackageInstaller.SessionInfo session,
833 int installFlags, File newPackageCodePath) {
834 if (session == null || session.resolvedBaseCodePath == null) {
835 return false;
836 }
837
838 File packageCodePath = new File(session.resolvedBaseCodePath).getParentFile();
839 if (newPackageCodePath.equals(packageCodePath) && installFlags == session.installFlags) {
840 return true;
841 }
842
843 return false;
844 }
845
Richard Uhler82913b72019-04-01 13:02:31 +0100846 private Context getContextAsUser(UserHandle user) {
847 try {
848 return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
849 } catch (PackageManager.NameNotFoundException e) {
850 return null;
851 }
852 }
853
Richard Uhlere95d0552018-12-27 15:03:41 +0000854 /**
855 * Called via broadcast by the package manager when a package is being
856 * staged for install with rollback enabled. Called before the package has
857 * been installed.
858 *
Richard Uhlere95d0552018-12-27 15:03:41 +0000859 * @param installFlags information about what is being installed.
860 * @param newPackageCodePath path to the package about to be installed.
Narayan Kamath869f7062019-01-10 12:24:15 +0000861 * @param installedUsers the set of users for which a given package is installed.
Richard Uhler82913b72019-04-01 13:02:31 +0100862 * @param user the user that owns the install session to enable rollback on.
shafik4831ad72019-05-03 17:36:42 +0100863 * @param token the distinct rollback token sent by package manager.
Richard Uhlere95d0552018-12-27 15:03:41 +0000864 * @return true if enabling the rollback succeeds, false otherwise.
865 */
Narayan Kamath869f7062019-01-10 12:24:15 +0000866 private boolean enableRollback(int installFlags, File newPackageCodePath,
shafik4831ad72019-05-03 17:36:42 +0100867 int[] installedUsers, @UserIdInt int user, int token) {
Richard Uhlere95d0552018-12-27 15:03:41 +0000868
Richard Uhlerf0bdca52019-01-31 16:43:59 +0000869 // Find the session id associated with this install.
870 // TODO: It would be nice if package manager or package installer told
871 // us the session directly, rather than have to search for it
872 // ourselves.
Richard Uhlere95d0552018-12-27 15:03:41 +0000873
Richard Uhler82913b72019-04-01 13:02:31 +0100874 // getAllSessions only returns sessions for the associated user.
875 // Create a context with the right user so we can find the matching
876 // session.
877 final Context context = getContextAsUser(UserHandle.of(user));
878 if (context == null) {
879 Log.e(TAG, "Unable to create context for install session user.");
880 return false;
881 }
882
Richard Uhler47569702019-05-02 12:36:39 +0100883 PackageInstaller.SessionInfo parentSession = null;
884 PackageInstaller.SessionInfo packageSession = null;
Richard Uhler82913b72019-04-01 13:02:31 +0100885 PackageInstaller installer = context.getPackageManager().getPackageInstaller();
Richard Uhler2d7c7f0d2019-01-04 09:18:21 +0000886 for (PackageInstaller.SessionInfo info : installer.getAllSessions()) {
Richard Uhlerf1910c52019-01-09 14:27:36 +0000887 if (info.isMultiPackage()) {
888 for (int childId : info.getChildSessionIds()) {
889 PackageInstaller.SessionInfo child = installer.getSessionInfo(childId);
890 if (sessionMatchesForEnableRollback(child, installFlags, newPackageCodePath)) {
891 // TODO: Check we only have one matching session?
Richard Uhler47569702019-05-02 12:36:39 +0100892 parentSession = info;
893 packageSession = child;
Richard Uhlerf0bdca52019-01-31 16:43:59 +0000894 break;
Richard Uhlerf1910c52019-01-09 14:27:36 +0000895 }
896 }
Richard Uhlerf0bdca52019-01-31 16:43:59 +0000897 } else if (sessionMatchesForEnableRollback(info, installFlags, newPackageCodePath)) {
898 // TODO: Check we only have one matching session?
Richard Uhler47569702019-05-02 12:36:39 +0100899 parentSession = info;
900 packageSession = info;
Richard Uhlerf0bdca52019-01-31 16:43:59 +0000901 break;
Richard Uhler2d7c7f0d2019-01-04 09:18:21 +0000902 }
903 }
904
Richard Uhler47569702019-05-02 12:36:39 +0100905 if (parentSession == null || packageSession == null) {
906 Log.e(TAG, "Unable to find session for enabled rollback.");
Richard Uhler2d7c7f0d2019-01-04 09:18:21 +0000907 return false;
908 }
909
Richard Uhlerac0d0f92019-02-05 15:53:47 +0000910 // Check to see if this is the apk session for a staged session with
911 // rollback enabled.
912 // TODO: This check could be made more efficient.
913 RollbackData rd = null;
914 synchronized (mLock) {
915 ensureRollbackDataLoadedLocked();
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000916 for (int i = 0; i < mRollbacks.size(); ++i) {
917 RollbackData data = mRollbacks.get(i);
Richard Uhler47569702019-05-02 12:36:39 +0100918 if (data.apkSessionId == parentSession.getSessionId()) {
Richard Uhlerac0d0f92019-02-05 15:53:47 +0000919 rd = data;
920 break;
921 }
922 }
923 }
924
925 if (rd != null) {
Gavin Corkeryab5ee412019-05-29 15:33:35 +0100926 // This is the apk session for a staged session. We do not need to create a new rollback
927 // for this session.
Richard Uhlerac0d0f92019-02-05 15:53:47 +0000928 PackageParser.PackageLite newPackage = null;
929 try {
930 newPackage = PackageParser.parsePackageLite(
Richard Uhler47569702019-05-02 12:36:39 +0100931 new File(packageSession.resolvedBaseCodePath), 0);
Richard Uhlerac0d0f92019-02-05 15:53:47 +0000932 } catch (PackageParser.PackageParserException e) {
933 Log.e(TAG, "Unable to parse new package", e);
934 return false;
935 }
936 String packageName = newPackage.packageName;
Richard Uhlercca637a2019-02-27 11:50:48 +0000937 for (PackageRollbackInfo info : rd.info.getPackages()) {
Richard Uhlerac0d0f92019-02-05 15:53:47 +0000938 if (info.getPackageName().equals(packageName)) {
Richard Uhler2a5facc2019-02-18 11:33:03 +0000939 info.getInstalledUsers().addAll(IntArray.wrap(installedUsers));
Richard Uhlerac0d0f92019-02-05 15:53:47 +0000940 return true;
941 }
942 }
943 Log.e(TAG, "Unable to find package in apk session");
944 return false;
945 }
946
Richard Uhler47569702019-05-02 12:36:39 +0100947 NewRollback newRollback;
948 synchronized (mLock) {
949 // See if we already have a NewRollback that contains this package
950 // session. If not, create a NewRollback for the parent session
951 // that we will use for all the packages in the session.
952 newRollback = getNewRollbackForPackageSessionLocked(packageSession.getSessionId());
953 if (newRollback == null) {
954 newRollback = createNewRollbackLocked(parentSession);
955 mNewRollbacks.add(newRollback);
956 }
957 }
shafik4831ad72019-05-03 17:36:42 +0100958 newRollback.addToken(token);
Richard Uhler47569702019-05-02 12:36:39 +0100959
Gavin Corkeryab5ee412019-05-29 15:33:35 +0100960 return enableRollbackForPackageSession(newRollback.data, packageSession, installedUsers);
Richard Uhlerf0bdca52019-01-31 16:43:59 +0000961 }
962
963 /**
964 * Do code and userdata backups to enable rollback of the given session.
965 * In case of multiPackage sessions, <code>session</code> should be one of
966 * the child sessions, not the parent session.
Richard Uhler47569702019-05-02 12:36:39 +0100967 *
968 * @return true on success, false on failure.
Richard Uhlerf0bdca52019-01-31 16:43:59 +0000969 */
Richard Uhler47569702019-05-02 12:36:39 +0100970 private boolean enableRollbackForPackageSession(RollbackData data,
Gavin Corkeryab5ee412019-05-29 15:33:35 +0100971 PackageInstaller.SessionInfo session, @NonNull int[] installedUsers) {
Richard Uhlerf0bdca52019-01-31 16:43:59 +0000972 // TODO: Don't attempt to enable rollback for split installs.
973 final int installFlags = session.installFlags;
974 if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
975 Log.e(TAG, "Rollback is not enabled.");
976 return false;
977 }
978 if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
979 Log.e(TAG, "Rollbacks not supported for instant app install");
980 return false;
981 }
982
Richard Uhlerfcad49a2019-03-13 10:18:46 +0000983 if (session.resolvedBaseCodePath == null) {
984 Log.e(TAG, "Session code path has not been resolved.");
985 return false;
986 }
987
Richard Uhlerf0bdca52019-01-31 16:43:59 +0000988 // Get information about the package to be installed.
989 PackageParser.PackageLite newPackage = null;
990 try {
991 newPackage = PackageParser.parsePackageLite(new File(session.resolvedBaseCodePath), 0);
992 } catch (PackageParser.PackageParserException e) {
993 Log.e(TAG, "Unable to parse new package", e);
994 return false;
995 }
996
997 String packageName = newPackage.packageName;
Narayan Kamathfcd4a042019-02-01 14:16:37 +0000998 Log.i(TAG, "Enabling rollback for install of " + packageName
999 + ", session:" + session.sessionId);
Richard Uhlerf0bdca52019-01-31 16:43:59 +00001000
Richard Uhler1fc10c12019-03-18 11:38:46 +00001001 String installerPackageName = session.getInstallerPackageName();
1002 if (!enableRollbackAllowed(installerPackageName, packageName)) {
1003 Log.e(TAG, "Installer " + installerPackageName
1004 + " is not allowed to enable rollback on " + packageName);
1005 return false;
1006 }
1007
Richard Uhlera7e9b2d2019-01-22 17:20:58 +00001008 VersionedPackage newVersion = new VersionedPackage(packageName, newPackage.versionCode);
Narayan Kamathfcd4a042019-02-01 14:16:37 +00001009 final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0);
Richard Uhlere95d0552018-12-27 15:03:41 +00001010
1011 // Get information about the currently installed package.
Richard Uhler1f571c62019-01-31 15:16:46 +00001012 PackageManager pm = mContext.getPackageManager();
Richard Uhler1fc10c12019-03-18 11:38:46 +00001013 final PackageInfo pkgInfo;
Richard Uhler1f571c62019-01-31 15:16:46 +00001014 try {
Richard Uhler82913b72019-04-01 13:02:31 +01001015 pkgInfo = getPackageInfo(packageName);
Richard Uhler1f571c62019-01-31 15:16:46 +00001016 } catch (PackageManager.NameNotFoundException e) {
Richard Uhlere95d0552018-12-27 15:03:41 +00001017 // TODO: Support rolling back fresh package installs rather than
1018 // fail here. Test this case.
1019 Log.e(TAG, packageName + " is not installed");
1020 return false;
1021 }
Richard Uhlere95d0552018-12-27 15:03:41 +00001022
Richard Uhler1f571c62019-01-31 15:16:46 +00001023 VersionedPackage installedVersion = new VersionedPackage(packageName,
1024 pkgInfo.getLongVersionCode());
Narayan Kamath869f7062019-01-10 12:24:15 +00001025
Richard Uhler47569702019-05-02 12:36:39 +01001026 PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
1027 newVersion, installedVersion,
Nikita Ioffe5dcd17972019-02-04 11:08:13 +00001028 new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
1029 isApex, IntArray.wrap(installedUsers),
1030 new SparseLongArray() /* ceSnapshotInodes */);
Richard Uhlere95d0552018-12-27 15:03:41 +00001031
Richard Uhler1f571c62019-01-31 15:16:46 +00001032 try {
Richard Uhlerab009ea2019-02-25 12:11:05 +00001033 ApplicationInfo appInfo = pkgInfo.applicationInfo;
1034 RollbackStore.backupPackageCodePath(data, packageName, appInfo.sourceDir);
1035 if (!ArrayUtils.isEmpty(appInfo.splitSourceDirs)) {
1036 for (String sourceDir : appInfo.splitSourceDirs) {
1037 RollbackStore.backupPackageCodePath(data, packageName, sourceDir);
1038 }
1039 }
Richard Uhler1f571c62019-01-31 15:16:46 +00001040 } catch (IOException e) {
1041 Log.e(TAG, "Unable to copy package for rollback for " + packageName, e);
Richard Uhlere95d0552018-12-27 15:03:41 +00001042 return false;
1043 }
Richard Uhler47569702019-05-02 12:36:39 +01001044
1045 synchronized (mLock) {
1046 data.info.getPackages().add(packageRollbackInfo);
1047 }
Richard Uhlere95d0552018-12-27 15:03:41 +00001048 return true;
1049 }
1050
Narayan Kamath869f7062019-01-10 12:24:15 +00001051 @Override
Gavin Corkeryab5ee412019-05-29 15:33:35 +01001052 public void snapshotAndRestoreUserData(String packageName, int[] userIds, int appId,
1053 long ceDataInode, String seInfo, int token) {
Narayan Kamath869f7062019-01-10 12:24:15 +00001054 if (Binder.getCallingUid() != Process.SYSTEM_UID) {
Gavin Corkeryab5ee412019-05-29 15:33:35 +01001055 throw new SecurityException(
1056 "snapshotAndRestoreUserData may only be called by the system.");
Narayan Kamath869f7062019-01-10 12:24:15 +00001057 }
1058
1059 getHandler().post(() -> {
Gavin Corkeryab5ee412019-05-29 15:33:35 +01001060 snapshotUserDataInternal(packageName);
Richard Uhler38fab3f2019-02-22 16:53:10 +00001061 restoreUserDataInternal(packageName, userIds, appId, ceDataInode, seInfo, token);
Narayan Kamathc034fe92019-01-23 10:48:17 +00001062 final PackageManagerInternal pmi = LocalServices.getService(
1063 PackageManagerInternal.class);
Narayan Kamath869f7062019-01-10 12:24:15 +00001064 pmi.finishPackageInstall(token, false);
1065 });
1066 }
1067
Gavin Corkeryab5ee412019-05-29 15:33:35 +01001068 private void snapshotUserDataInternal(String packageName) {
1069 synchronized (mLock) {
1070 // staged installs
1071 ensureRollbackDataLoadedLocked();
1072 for (int i = 0; i < mRollbacks.size(); i++) {
1073 RollbackData data = mRollbacks.get(i);
1074 if (data.state != RollbackData.ROLLBACK_STATE_ENABLING) {
1075 continue;
1076 }
1077
1078 for (PackageRollbackInfo info : data.info.getPackages()) {
1079 if (info.getPackageName().equals(packageName)) {
1080 mAppDataRollbackHelper.snapshotAppData(data.info.getRollbackId(), info);
1081 saveRollbackData(data);
Gavin Corkery237dd812019-06-04 22:53:15 +01001082 break;
Gavin Corkeryab5ee412019-05-29 15:33:35 +01001083 }
1084 }
1085 }
1086 // non-staged installs
1087 PackageRollbackInfo info;
1088 for (NewRollback rollback : mNewRollbacks) {
1089 info = getPackageRollbackInfo(rollback.data, packageName);
1090 if (info != null) {
1091 mAppDataRollbackHelper.snapshotAppData(rollback.data.info.getRollbackId(),
1092 info);
1093 saveRollbackData(rollback.data);
Gavin Corkeryab5ee412019-05-29 15:33:35 +01001094 }
1095 }
1096 }
1097 }
1098
Richard Uhler38fab3f2019-02-22 16:53:10 +00001099 private void restoreUserDataInternal(String packageName, int[] userIds, int appId,
1100 long ceDataInode, String seInfo, int token) {
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001101 PackageRollbackInfo info = null;
1102 RollbackData rollbackData = null;
1103 synchronized (mLock) {
1104 ensureRollbackDataLoadedLocked();
1105 for (int i = 0; i < mRollbacks.size(); ++i) {
1106 RollbackData data = mRollbacks.get(i);
1107 if (data.restoreUserDataInProgress) {
1108 info = getPackageRollbackInfo(data, packageName);
1109 if (info != null) {
1110 rollbackData = data;
1111 break;
1112 }
1113 }
1114 }
1115 }
1116
Richard Uhler38fab3f2019-02-22 16:53:10 +00001117 if (rollbackData == null) {
1118 return;
1119 }
1120
Richard Uhler38fab3f2019-02-22 16:53:10 +00001121 for (int userId : userIds) {
Richard Uhler38fab3f2019-02-22 16:53:10 +00001122 final boolean changedRollbackData = mAppDataRollbackHelper.restoreAppData(
Richard Uhlercca637a2019-02-27 11:50:48 +00001123 rollbackData.info.getRollbackId(), info, userId, appId, seInfo);
Richard Uhler38fab3f2019-02-22 16:53:10 +00001124
1125 // We've updated metadata about this rollback, so save it to flash.
1126 if (changedRollbackData) {
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001127 saveRollbackData(rollbackData);
Richard Uhler38fab3f2019-02-22 16:53:10 +00001128 }
1129 }
1130 }
1131
Narayan Kamathfcd4a042019-02-01 14:16:37 +00001132 @Override
1133 public boolean notifyStagedSession(int sessionId) {
shafik3be41a92019-03-25 14:10:32 +00001134 if (Binder.getCallingUid() != Process.SYSTEM_UID) {
1135 throw new SecurityException("notifyStagedSession may only be called by the system.");
1136 }
Narayan Kamathfcd4a042019-02-01 14:16:37 +00001137 final LinkedBlockingQueue<Boolean> result = new LinkedBlockingQueue<>();
1138
1139 // NOTE: We post this runnable on the RollbackManager's binder thread because we'd prefer
1140 // to preserve the invariant that all operations that modify state happen there.
1141 getHandler().post(() -> {
1142 PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
1143
1144 final PackageInstaller.SessionInfo session = installer.getSessionInfo(sessionId);
1145 if (session == null) {
1146 Log.e(TAG, "No matching install session for: " + sessionId);
1147 result.offer(false);
1148 return;
1149 }
1150
Richard Uhler47569702019-05-02 12:36:39 +01001151 NewRollback newRollback;
1152 synchronized (mLock) {
1153 newRollback = createNewRollbackLocked(session);
1154 }
1155
Narayan Kamathfcd4a042019-02-01 14:16:37 +00001156 if (!session.isMultiPackage()) {
Richard Uhler47569702019-05-02 12:36:39 +01001157 if (!enableRollbackForPackageSession(newRollback.data, session,
Gavin Corkeryab5ee412019-05-29 15:33:35 +01001158 new int[0])) {
Narayan Kamathfcd4a042019-02-01 14:16:37 +00001159 Log.e(TAG, "Unable to enable rollback for session: " + sessionId);
1160 result.offer(false);
1161 return;
1162 }
1163 } else {
1164 for (int childSessionId : session.getChildSessionIds()) {
1165 final PackageInstaller.SessionInfo childSession =
1166 installer.getSessionInfo(childSessionId);
1167 if (childSession == null) {
1168 Log.e(TAG, "No matching child install session for: " + childSessionId);
1169 result.offer(false);
1170 return;
1171 }
Richard Uhler47569702019-05-02 12:36:39 +01001172 if (!enableRollbackForPackageSession(newRollback.data, childSession,
Gavin Corkeryab5ee412019-05-29 15:33:35 +01001173 new int[0])) {
Narayan Kamathfcd4a042019-02-01 14:16:37 +00001174 Log.e(TAG, "Unable to enable rollback for session: " + sessionId);
1175 result.offer(false);
1176 return;
1177 }
1178 }
1179 }
1180
Richard Uhler47569702019-05-02 12:36:39 +01001181 result.offer(completeEnableRollback(newRollback, true) != null);
Narayan Kamathfcd4a042019-02-01 14:16:37 +00001182 });
1183
1184 try {
1185 return result.take();
1186 } catch (InterruptedException ie) {
1187 Log.e(TAG, "Interrupted while waiting for notifyStagedSession response");
1188 return false;
1189 }
1190 }
1191
Richard Uhler6fa7d132019-02-05 13:55:11 +00001192 @Override
1193 public void notifyStagedApkSession(int originalSessionId, int apkSessionId) {
shafik3be41a92019-03-25 14:10:32 +00001194 if (Binder.getCallingUid() != Process.SYSTEM_UID) {
1195 throw new SecurityException("notifyStagedApkSession may only be called by the system.");
1196 }
Richard Uhlerba13ab22019-02-05 15:27:12 +00001197 getHandler().post(() -> {
1198 RollbackData rd = null;
1199 synchronized (mLock) {
1200 ensureRollbackDataLoadedLocked();
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001201 for (int i = 0; i < mRollbacks.size(); ++i) {
1202 RollbackData data = mRollbacks.get(i);
Richard Uhlerba13ab22019-02-05 15:27:12 +00001203 if (data.stagedSessionId == originalSessionId) {
1204 data.apkSessionId = apkSessionId;
1205 rd = data;
1206 break;
1207 }
1208 }
1209 }
1210
1211 if (rd != null) {
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001212 saveRollbackData(rd);
Richard Uhlerba13ab22019-02-05 15:27:12 +00001213 }
1214 });
Richard Uhler6fa7d132019-02-05 13:55:11 +00001215 }
1216
Richard Uhlere95d0552018-12-27 15:03:41 +00001217 /**
Richard Uhler1fc10c12019-03-18 11:38:46 +00001218 * Returns true if the installer is allowed to enable rollback for the
1219 * given named package, false otherwise.
1220 */
1221 private boolean enableRollbackAllowed(String installerPackageName, String packageName) {
1222 if (installerPackageName == null) {
1223 return false;
1224 }
1225
1226 PackageManager pm = mContext.getPackageManager();
1227 boolean manageRollbacksGranted = pm.checkPermission(
1228 Manifest.permission.MANAGE_ROLLBACKS,
1229 installerPackageName) == PackageManager.PERMISSION_GRANTED;
1230
1231 boolean testManageRollbacksGranted = pm.checkPermission(
1232 Manifest.permission.TEST_MANAGE_ROLLBACKS,
1233 installerPackageName) == PackageManager.PERMISSION_GRANTED;
1234
1235 // For now only allow rollbacks for modules or for testing.
1236 return (isModule(packageName) && manageRollbacksGranted)
1237 || testManageRollbacksGranted;
1238 }
1239
1240 /**
1241 * Returns true if the package name is the name of a module.
1242 */
1243 private boolean isModule(String packageName) {
1244 PackageManager pm = mContext.getPackageManager();
1245 final ModuleInfo moduleInfo;
1246 try {
1247 moduleInfo = pm.getModuleInfo(packageName, 0);
1248 } catch (PackageManager.NameNotFoundException e) {
1249 return false;
1250 }
1251
1252 return moduleInfo != null;
1253 }
1254
1255 /**
Richard Uhlere95d0552018-12-27 15:03:41 +00001256 * Gets the version of the package currently installed.
1257 * Returns null if the package is not currently installed.
1258 */
Richard Uhlera7e9b2d2019-01-22 17:20:58 +00001259 private VersionedPackage getInstalledPackageVersion(String packageName) {
Richard Uhlere95d0552018-12-27 15:03:41 +00001260 PackageManager pm = mContext.getPackageManager();
1261 PackageInfo pkgInfo = null;
1262 try {
Richard Uhler82913b72019-04-01 13:02:31 +01001263 pkgInfo = getPackageInfo(packageName);
Richard Uhlere95d0552018-12-27 15:03:41 +00001264 } catch (PackageManager.NameNotFoundException e) {
1265 return null;
1266 }
1267
Richard Uhlera7e9b2d2019-01-22 17:20:58 +00001268 return new VersionedPackage(packageName, pkgInfo.getLongVersionCode());
1269 }
1270
Richard Uhler82913b72019-04-01 13:02:31 +01001271 /**
1272 * Gets PackageInfo for the given package.
1273 * Matches any user and apex. Returns null if no such package is
1274 * installed.
1275 */
1276 private PackageInfo getPackageInfo(String packageName)
1277 throws PackageManager.NameNotFoundException {
1278 PackageManager pm = mContext.getPackageManager();
1279 try {
1280 // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX
1281 // flag, so make two separate attempts to get the package info.
1282 // We don't need both flags at the same time because we assume
1283 // apex files are always installed for all users.
1284 return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER);
1285 } catch (PackageManager.NameNotFoundException e) {
1286 return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
1287 }
1288 }
1289
1290
Richard Uhlera7e9b2d2019-01-22 17:20:58 +00001291 private boolean packageVersionsEqual(VersionedPackage a, VersionedPackage b) {
1292 return a.getPackageName().equals(b.getPackageName())
1293 && a.getLongVersionCode() == b.getLongVersionCode();
Richard Uhlere95d0552018-12-27 15:03:41 +00001294 }
Richard Uhler2d7c7f0d2019-01-04 09:18:21 +00001295
1296 private class SessionCallback extends PackageInstaller.SessionCallback {
1297
1298 @Override
1299 public void onCreated(int sessionId) { }
1300
1301 @Override
1302 public void onBadgingChanged(int sessionId) { }
1303
1304 @Override
1305 public void onActiveChanged(int sessionId, boolean active) { }
1306
1307 @Override
1308 public void onProgressChanged(int sessionId, float progress) { }
1309
1310 @Override
1311 public void onFinished(int sessionId, boolean success) {
Richard Uhler47569702019-05-02 12:36:39 +01001312 NewRollback newRollback;
1313 synchronized (mLock) {
1314 newRollback = getNewRollbackForPackageSessionLocked(sessionId);
1315 if (newRollback != null) {
1316 mNewRollbacks.remove(newRollback);
1317 }
1318 }
1319
1320 if (newRollback != null) {
1321 RollbackData rollback = completeEnableRollback(newRollback, success);
1322 if (rollback != null && !rollback.isStaged()) {
1323 makeRollbackAvailable(rollback);
1324 }
Richard Uhler2d7c7f0d2019-01-04 09:18:21 +00001325 }
Narayan Kamathfcd4a042019-02-01 14:16:37 +00001326 }
1327 }
Richard Uhler2d7c7f0d2019-01-04 09:18:21 +00001328
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001329 /**
1330 * Add a rollback to the list of rollbacks.
1331 * This should be called after rollback has been enabled for all packages
1332 * in the rollback. It does not make the rollback available yet.
1333 *
Richard Uhler47569702019-05-02 12:36:39 +01001334 * @return the rollback data for a successfully enable-completed rollback,
1335 * or null on error.
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001336 */
Richard Uhler47569702019-05-02 12:36:39 +01001337 private RollbackData completeEnableRollback(NewRollback newRollback, boolean success) {
1338 RollbackData data = newRollback.data;
1339 if (!success) {
1340 // The install session was aborted, clean up the pending install.
1341 deleteRollback(data);
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001342 return null;
Narayan Kamathfcd4a042019-02-01 14:16:37 +00001343 }
shafik4831ad72019-05-03 17:36:42 +01001344 if (newRollback.isCancelled) {
1345 Log.e(TAG, "Rollback has been cancelled by PackageManager");
1346 deleteRollback(data);
1347 return null;
1348 }
Narayan Kamathfcd4a042019-02-01 14:16:37 +00001349
Richard Uhler47569702019-05-02 12:36:39 +01001350 // It's safe to access data.info outside a synchronized block because
1351 // this is running on the handler thread and all changes to the
1352 // data.info occur on the handler thread.
1353 if (data.info.getPackages().size() != newRollback.packageSessionIds.length) {
1354 Log.e(TAG, "Failed to enable rollback for all packages in session.");
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001355 deleteRollback(data);
1356 return null;
Narayan Kamathfcd4a042019-02-01 14:16:37 +00001357 }
Narayan Kamathfcd4a042019-02-01 14:16:37 +00001358
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001359 saveRollbackData(data);
Richard Uhler035e9742019-01-09 13:11:07 +00001360 synchronized (mLock) {
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001361 // Note: There is a small window of time between when
1362 // the session has been committed by the package
1363 // manager and when we make the rollback available
1364 // here. Presumably the window is small enough that
1365 // nobody will want to roll back the newly installed
1366 // package before we make the rollback available.
1367 // TODO: We'll lose the rollback data if the
1368 // device reboots between when the session is
1369 // committed and this point. Revisit this after
1370 // adding support for rollback of staged installs.
Richard Uhler035e9742019-01-09 13:11:07 +00001371 ensureRollbackDataLoadedLocked();
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001372 mRollbacks.add(data);
Richard Uhler035e9742019-01-09 13:11:07 +00001373 }
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001374
1375 return data;
1376 }
1377
1378 private void makeRollbackAvailable(RollbackData data) {
1379 // TODO: What if the rollback has since been expired, for example due
1380 // to a new package being installed. Won't this revive an expired
1381 // rollback? Consider adding a ROLLBACK_STATE_EXPIRED to address this.
1382 synchronized (mLock) {
1383 data.state = RollbackData.ROLLBACK_STATE_AVAILABLE;
1384 data.timestamp = Instant.now();
1385 }
1386 saveRollbackData(data);
1387
1388 // TODO(zezeozue): Provide API to explicitly start observing instead
1389 // of doing this for all rollbacks. If we do this for all rollbacks,
1390 // should document in PackageInstaller.SessionParams#setEnableRollback
1391 // After enabling and commiting any rollback, observe packages and
1392 // prepare to rollback if packages crashes too frequently.
1393 List<String> packages = new ArrayList<>();
1394 for (int i = 0; i < data.info.getPackages().size(); i++) {
1395 packages.add(data.info.getPackages().get(i).getPackageName());
1396 }
1397 mPackageHealthObserver.startObservingHealth(packages,
1398 mRollbackLifetimeDurationInMillis);
1399 scheduleExpiration(mRollbackLifetimeDurationInMillis);
Richard Uhler035e9742019-01-09 13:11:07 +00001400 }
Richard Uhlerb9d54472019-01-22 12:50:08 +00001401
Richard Uhler0a79b322019-01-23 13:51:07 +00001402 /*
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001403 * Returns the RollbackData, if any, for a rollback with the given
1404 * rollbackId.
Richard Uhler0a79b322019-01-23 13:51:07 +00001405 */
1406 private RollbackData getRollbackForId(int rollbackId) {
1407 synchronized (mLock) {
1408 // TODO: Have ensureRollbackDataLoadedLocked return the list of
1409 // available rollbacks, to hopefully avoid forgetting to call it?
1410 ensureRollbackDataLoadedLocked();
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001411 for (int i = 0; i < mRollbacks.size(); ++i) {
1412 RollbackData data = mRollbacks.get(i);
1413 if (data.info.getRollbackId() == rollbackId) {
Richard Uhler0a79b322019-01-23 13:51:07 +00001414 return data;
1415 }
1416 }
1417 }
Narayan Kamathc034fe92019-01-23 10:48:17 +00001418
1419 return null;
1420 }
1421
1422 /**
1423 * Returns the {@code PackageRollbackInfo} associated with {@code packageName} from
1424 * a specified {@code RollbackData}.
1425 */
Nikita Ioffe5dcd17972019-02-04 11:08:13 +00001426 private static PackageRollbackInfo getPackageRollbackInfo(RollbackData data,
Narayan Kamathc034fe92019-01-23 10:48:17 +00001427 String packageName) {
Richard Uhlercca637a2019-02-27 11:50:48 +00001428 for (PackageRollbackInfo info : data.info.getPackages()) {
Narayan Kamathc034fe92019-01-23 10:48:17 +00001429 if (info.getPackageName().equals(packageName)) {
1430 return info;
1431 }
1432 }
1433
Richard Uhler0a79b322019-01-23 13:51:07 +00001434 return null;
1435 }
1436
Richard Uhlerb9d54472019-01-22 12:50:08 +00001437 @GuardedBy("mLock")
Richard Uhler47569702019-05-02 12:36:39 +01001438 private int allocateRollbackIdLocked() {
Richard Uhlerb9d54472019-01-22 12:50:08 +00001439 int n = 0;
1440 int rollbackId;
1441 do {
1442 rollbackId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
1443 if (!mAllocatedRollbackIds.get(rollbackId, false)) {
1444 mAllocatedRollbackIds.put(rollbackId, true);
1445 return rollbackId;
1446 }
1447 } while (n++ < 32);
1448
Richard Uhler47569702019-05-02 12:36:39 +01001449 throw new IllegalStateException("Failed to allocate rollback ID");
Richard Uhlerb9d54472019-01-22 12:50:08 +00001450 }
Nikita Ioffe952aa7b2019-01-28 19:49:56 +00001451
1452 private void deleteRollback(RollbackData rollbackData) {
Richard Uhlercca637a2019-02-27 11:50:48 +00001453 for (PackageRollbackInfo info : rollbackData.info.getPackages()) {
Nikita Ioffe952aa7b2019-01-28 19:49:56 +00001454 IntArray installedUsers = info.getInstalledUsers();
Nikita Ioffe952aa7b2019-01-28 19:49:56 +00001455 for (int i = 0; i < installedUsers.size(); i++) {
1456 int userId = installedUsers.get(i);
Richard Uhlercca637a2019-02-27 11:50:48 +00001457 mAppDataRollbackHelper.destroyAppDataSnapshot(rollbackData.info.getRollbackId(),
1458 info, userId);
Nikita Ioffe952aa7b2019-01-28 19:49:56 +00001459 }
1460 }
Richard Uhleraad02c02019-02-27 12:57:20 +00001461 mRollbackStore.deleteRollbackData(rollbackData);
Nikita Ioffe952aa7b2019-01-28 19:49:56 +00001462 }
Richard Uhler6f8a33b2019-02-26 10:40:36 +00001463
1464 /**
1465 * Saves rollback data, swallowing any IOExceptions.
1466 * For those times when it's not obvious what to do about the IOException.
1467 * TODO: Double check we can't do a better job handling the IOException in
1468 * a cases where this method is called.
1469 */
1470 private void saveRollbackData(RollbackData rollbackData) {
1471 try {
1472 mRollbackStore.saveRollbackData(rollbackData);
1473 } catch (IOException ioe) {
1474 Log.e(TAG, "Unable to save rollback info for: "
1475 + rollbackData.info.getRollbackId(), ioe);
1476 }
1477 }
shafik60046002019-03-12 17:54:10 +00001478
1479 @Override
1480 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1481 IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
1482 synchronized (mLock) {
1483 for (RollbackData data : mRollbacks) {
1484 RollbackInfo info = data.info;
1485 ipw.println(info.getRollbackId() + ":");
1486 ipw.increaseIndent();
1487 ipw.println("-state: " + data.getStateAsString());
1488 ipw.println("-timestamp: " + data.timestamp);
1489 if (data.stagedSessionId != -1) {
1490 ipw.println("-stagedSessionId: " + data.stagedSessionId);
1491 }
1492 ipw.println("-packages:");
1493 ipw.increaseIndent();
1494 for (PackageRollbackInfo pkg : info.getPackages()) {
1495 ipw.println(pkg.getPackageName()
1496 + " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
1497 + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
1498 }
1499 ipw.decreaseIndent();
1500 if (data.state == RollbackData.ROLLBACK_STATE_COMMITTED) {
1501 ipw.println("-causePackages:");
1502 ipw.increaseIndent();
1503 for (VersionedPackage cPkg : info.getCausePackages()) {
1504 ipw.println(cPkg.getPackageName() + " " + cPkg.getLongVersionCode());
1505 }
1506 ipw.decreaseIndent();
1507 ipw.println("-committedSessionId: " + info.getCommittedSessionId());
1508 }
1509 ipw.decreaseIndent();
1510 }
1511 }
1512 }
Richard Uhler1fc10c12019-03-18 11:38:46 +00001513
1514 private void enforceManageRollbacks(@NonNull String message) {
1515 if ((PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
1516 Manifest.permission.MANAGE_ROLLBACKS))
1517 && (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
1518 Manifest.permission.TEST_MANAGE_ROLLBACKS))) {
1519 throw new SecurityException(message + " requires "
1520 + Manifest.permission.MANAGE_ROLLBACKS + " or "
1521 + Manifest.permission.TEST_MANAGE_ROLLBACKS);
1522 }
1523 }
Richard Uhler47569702019-05-02 12:36:39 +01001524
1525 private static class NewRollback {
1526 public final RollbackData data;
1527
1528 /**
shafik4831ad72019-05-03 17:36:42 +01001529 * This array holds all of the rollback tokens associated with package sessions included
1530 * in this rollback. This is used to identify which rollback should be cancelled in case
1531 * {@link PackageManager} sends an {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent.
1532 */
1533 private final IntArray mTokens = new IntArray();
1534
1535 /**
Richard Uhler47569702019-05-02 12:36:39 +01001536 * Session ids for all packages in the install.
1537 * For multi-package sessions, this is the list of child session ids.
1538 * For normal sessions, this list is a single element with the normal
1539 * session id.
1540 */
1541 public final int[] packageSessionIds;
1542
shafik4831ad72019-05-03 17:36:42 +01001543 /**
1544 * Flag to determine whether the RollbackData has been cancelled.
1545 *
1546 * <p>RollbackData could be invalidated and cancelled if RollbackManager receives
1547 * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} from {@link PackageManager}.
1548 *
1549 * <p>The main underlying assumption here is that if enabling the rollback times out, then
1550 * {@link PackageManager} will NOT send
1551 * {@link PackageInstaller.SessionCallback#onFinished(int, boolean)} before it broadcasts
1552 * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK}.
1553 */
1554 public boolean isCancelled = false;
1555
Richard Uhler47569702019-05-02 12:36:39 +01001556 NewRollback(RollbackData data, int[] packageSessionIds) {
1557 this.data = data;
1558 this.packageSessionIds = packageSessionIds;
1559 }
shafik4831ad72019-05-03 17:36:42 +01001560
1561 public void addToken(int token) {
1562 mTokens.add(token);
1563 }
1564
1565 public boolean hasToken(int token) {
1566 return mTokens.indexOf(token) != -1;
1567 }
Richard Uhler47569702019-05-02 12:36:39 +01001568 }
1569
1570 NewRollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
1571 int rollbackId = allocateRollbackIdLocked();
1572 final RollbackData data;
1573 int parentSessionId = parentSession.getSessionId();
1574
1575 if (parentSession.isStaged()) {
1576 data = mRollbackStore.createStagedRollback(rollbackId, parentSessionId);
1577 } else {
1578 data = mRollbackStore.createNonStagedRollback(rollbackId);
1579 }
1580
1581 int[] packageSessionIds;
1582 if (parentSession.isMultiPackage()) {
1583 packageSessionIds = parentSession.getChildSessionIds();
1584 } else {
1585 packageSessionIds = new int[]{parentSessionId};
1586 }
1587
1588 return new NewRollback(data, packageSessionIds);
1589 }
1590
1591 /**
1592 * Returns the NewRollback associated with the given package session.
1593 * Returns null if no NewRollback is found for the given package
1594 * session.
1595 */
1596 NewRollback getNewRollbackForPackageSessionLocked(int packageSessionId) {
1597 // We expect mNewRollbacks to be a very small list; linear search
1598 // should be plenty fast.
1599 for (NewRollback newRollbackData : mNewRollbacks) {
1600 for (int id : newRollbackData.packageSessionIds) {
1601 if (id == packageSessionId) {
1602 return newRollbackData;
1603 }
1604 }
1605 }
1606 return null;
1607 }
Richard Uhlere95d0552018-12-27 15:03:41 +00001608}