1 /* 2 * Copyright (C) 2021 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.pm; 18 19 import static android.content.pm.PackageManager.INSTALL_INTERNAL; 20 import static android.content.pm.PackageManager.MOVE_FAILED_3RD_PARTY_NOT_ALLOWED_ON_INTERNAL; 21 import static android.content.pm.PackageManager.MOVE_FAILED_DEVICE_ADMIN; 22 import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST; 23 import static android.content.pm.PackageManager.MOVE_FAILED_INTERNAL_ERROR; 24 import static android.content.pm.PackageManager.MOVE_FAILED_LOCKED_USER; 25 import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING; 26 import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; 27 28 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL; 29 import static com.android.server.pm.PackageManagerService.TAG; 30 31 import android.app.ApplicationExitInfo; 32 import android.content.Intent; 33 import android.content.pm.IPackageInstallObserver2; 34 import android.content.pm.IPackageMoveObserver; 35 import android.content.pm.PackageInstaller; 36 import android.content.pm.PackageManager; 37 import android.content.pm.PackageStats; 38 import android.content.pm.UserInfo; 39 import android.content.pm.parsing.ApkLiteParseUtils; 40 import android.content.pm.parsing.PackageLite; 41 import android.content.pm.parsing.result.ParseResult; 42 import android.content.pm.parsing.result.ParseTypeImpl; 43 import android.os.Bundle; 44 import android.os.Environment; 45 import android.os.Handler; 46 import android.os.Looper; 47 import android.os.Message; 48 import android.os.RemoteCallbackList; 49 import android.os.RemoteException; 50 import android.os.UserHandle; 51 import android.os.storage.StorageManager; 52 import android.os.storage.StorageManagerInternal; 53 import android.os.storage.VolumeInfo; 54 import android.util.MathUtils; 55 import android.util.Slog; 56 import android.util.SparseIntArray; 57 58 import com.android.internal.annotations.GuardedBy; 59 import com.android.internal.os.SomeArgs; 60 import com.android.internal.util.FrameworkStatsLog; 61 import com.android.server.pm.parsing.pkg.AndroidPackageUtils; 62 import com.android.server.pm.pkg.AndroidPackage; 63 import com.android.server.pm.pkg.PackageStateInternal; 64 import com.android.server.pm.pkg.PackageStateUtils; 65 66 import java.io.File; 67 import java.util.ArrayList; 68 import java.util.Objects; 69 import java.util.concurrent.CountDownLatch; 70 import java.util.concurrent.TimeUnit; 71 72 public final class MovePackageHelper { 73 final PackageManagerService mPm; 74 75 // TODO(b/198166813): remove PMS dependency MovePackageHelper(PackageManagerService pm)76 public MovePackageHelper(PackageManagerService pm) { 77 mPm = pm; 78 } 79 movePackageInternal(final String packageName, final String volumeUuid, final int moveId, final int callingUid, UserHandle user)80 public void movePackageInternal(final String packageName, final String volumeUuid, 81 final int moveId, final int callingUid, UserHandle user) 82 throws PackageManagerException { 83 final StorageManager storage = mPm.mInjector.getSystemService(StorageManager.class); 84 final PackageManager pm = mPm.mContext.getPackageManager(); 85 86 Computer snapshot = mPm.snapshotComputer(); 87 final PackageStateInternal packageState = snapshot.getPackageStateForInstalledAndFiltered( 88 packageName, callingUid, user.getIdentifier()); 89 if (packageState == null || packageState.getPkg() == null) { 90 throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package"); 91 } 92 final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState, 93 mPm.mUserManager.getUserIds(), true); 94 final UserHandle userForMove; 95 if (installedUserIds.length > 0) { 96 userForMove = UserHandle.of(installedUserIds[0]); 97 } else { 98 throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, 99 "Package is not installed for any user"); 100 } 101 for (int userId : installedUserIds) { 102 if (snapshot.shouldFilterApplicationIncludingUninstalled(packageState, callingUid, 103 userId)) { 104 throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package"); 105 } 106 } 107 final AndroidPackage pkg = packageState.getPkg(); 108 if (packageState.isSystem()) { 109 throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE, 110 "Cannot move system application"); 111 } 112 113 final boolean isInternalStorage = VolumeInfo.ID_PRIVATE_INTERNAL.equals(volumeUuid); 114 final boolean allow3rdPartyOnInternal = mPm.mContext.getResources().getBoolean( 115 com.android.internal.R.bool.config_allow3rdPartyAppOnInternal); 116 if (isInternalStorage && !allow3rdPartyOnInternal) { 117 throw new PackageManagerException(MOVE_FAILED_3RD_PARTY_NOT_ALLOWED_ON_INTERNAL, 118 "3rd party apps are not allowed on internal storage"); 119 } 120 121 final File probe = new File(pkg.getPath()); 122 if (!probe.isDirectory()) { 123 throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR, 124 "Move only supported for modern cluster style installs"); 125 } 126 127 final String currentVolumeUuid = packageState.getVolumeUuid(); 128 if (Objects.equals(currentVolumeUuid, volumeUuid)) { 129 throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR, 130 "Package already moved to " + volumeUuid); 131 } 132 if (!pkg.isExternalStorage() 133 && mPm.isPackageDeviceAdminOnAnyUser(snapshot, packageName)) { 134 throw new PackageManagerException(MOVE_FAILED_DEVICE_ADMIN, 135 "Device admin cannot be moved"); 136 } 137 138 if (snapshot.getFrozenPackages().containsKey(packageName)) { 139 throw new PackageManagerException(MOVE_FAILED_OPERATION_PENDING, 140 "Failed to move already frozen package"); 141 } 142 143 final boolean isCurrentLocationExternal = pkg.isExternalStorage(); 144 final File codeFile = new File(pkg.getPath()); 145 final InstallSource installSource = packageState.getInstallSource(); 146 final String packageAbiOverride = packageState.getCpuAbiOverride(); 147 final int appId = UserHandle.getAppId(pkg.getUid()); 148 final String seinfo = packageState.getSeInfo(); 149 final String label = String.valueOf(pm.getApplicationLabel( 150 AndroidPackageUtils.generateAppInfoWithoutState(pkg))); 151 final int targetSdkVersion = pkg.getTargetSdkVersion(); 152 final String fromCodePath; 153 if (codeFile.getParentFile().getName().startsWith( 154 PackageManagerService.RANDOM_DIR_PREFIX)) { 155 fromCodePath = codeFile.getParentFile().getAbsolutePath(); 156 } else { 157 fromCodePath = codeFile.getAbsolutePath(); 158 } 159 160 final PackageFreezer freezer; 161 synchronized (mPm.mLock) { 162 freezer = mPm.freezePackage(packageName, UserHandle.USER_ALL, 163 "movePackageInternal", ApplicationExitInfo.REASON_USER_REQUESTED, 164 null /* request */); 165 } 166 167 final Bundle extras = new Bundle(); 168 extras.putString(Intent.EXTRA_PACKAGE_NAME, packageName); 169 extras.putString(Intent.EXTRA_TITLE, label); 170 mPm.mMoveCallbacks.notifyCreated(moveId, extras); 171 172 int installFlags; 173 final boolean moveCompleteApp; 174 final File measurePath; 175 176 installFlags = INSTALL_INTERNAL; 177 if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) { 178 moveCompleteApp = true; 179 measurePath = Environment.getDataAppDirectory(volumeUuid); 180 } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) { 181 moveCompleteApp = false; 182 measurePath = storage.getPrimaryPhysicalVolume().getPath(); 183 } else { 184 final VolumeInfo volume = storage.findVolumeByUuid(volumeUuid); 185 if (volume == null || volume.getType() != VolumeInfo.TYPE_PRIVATE 186 || !volume.isMountedWritable()) { 187 freezer.close(); 188 throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR, 189 "Move location not mounted private volume"); 190 } 191 192 moveCompleteApp = true; 193 measurePath = Environment.getDataAppDirectory(volumeUuid); 194 } 195 196 // If we're moving app data around, we need all the users unlocked 197 if (moveCompleteApp) { 198 for (int userId : installedUserIds) { 199 if (StorageManager.isFileEncrypted() 200 && !StorageManager.isCeStorageUnlocked(userId)) { 201 freezer.close(); 202 throw new PackageManagerException(MOVE_FAILED_LOCKED_USER, 203 "User " + userId + " must be unlocked"); 204 } 205 } 206 } 207 208 final PackageStats stats = new PackageStats(null, -1); 209 try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) { 210 for (int userId : installedUserIds) { 211 if (!getPackageSizeInfoLI(packageName, userId, stats)) { 212 freezer.close(); 213 throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR, 214 "Failed to measure package size"); 215 } 216 } 217 } 218 219 if (DEBUG_INSTALL) { 220 Slog.d(TAG, "Measured code size " + stats.codeSize + ", data size " 221 + stats.dataSize); 222 } 223 224 final long startFreeBytes = measurePath.getUsableSpace(); 225 final long sizeBytes; 226 if (moveCompleteApp) { 227 sizeBytes = stats.codeSize + stats.dataSize; 228 } else { 229 sizeBytes = stats.codeSize; 230 } 231 232 if (sizeBytes > storage.getStorageBytesUntilLow(measurePath)) { 233 freezer.close(); 234 throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR, 235 "Not enough free space to move"); 236 } 237 238 try { 239 prepareUserStorageForMove(currentVolumeUuid, volumeUuid, installedUserIds); 240 } catch (RuntimeException e) { 241 freezer.close(); 242 throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR, 243 "Failed to prepare user storage while moving app"); 244 } 245 246 mPm.mMoveCallbacks.notifyStatusChanged(moveId, 10); 247 248 final CountDownLatch installedLatch = new CountDownLatch(1); 249 final IPackageInstallObserver2 installObserver = new IPackageInstallObserver2.Stub() { 250 @Override 251 public void onUserActionRequired(Intent intent) throws RemoteException { 252 freezer.close(); 253 throw new IllegalStateException(); 254 } 255 256 @Override 257 public void onPackageInstalled(String basePackageName, int returnCode, String msg, 258 Bundle extras) throws RemoteException { 259 if (DEBUG_INSTALL) { 260 Slog.d(TAG, "Install result for move: " 261 + PackageManager.installStatusToString(returnCode, msg)); 262 } 263 264 installedLatch.countDown(); 265 freezer.close(); 266 267 final int status = PackageManager.installStatusToPublicStatus(returnCode); 268 switch (status) { 269 case PackageInstaller.STATUS_SUCCESS: 270 mPm.mMoveCallbacks.notifyStatusChanged(moveId, 271 PackageManager.MOVE_SUCCEEDED); 272 logAppMovedStorage(packageName, isCurrentLocationExternal); 273 break; 274 case PackageInstaller.STATUS_FAILURE_STORAGE: 275 mPm.mMoveCallbacks.notifyStatusChanged(moveId, 276 PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE); 277 break; 278 default: 279 mPm.mMoveCallbacks.notifyStatusChanged(moveId, 280 PackageManager.MOVE_FAILED_INTERNAL_ERROR); 281 break; 282 } 283 } 284 }; 285 286 final MoveInfo move; 287 if (moveCompleteApp) { 288 // Kick off a thread to report progress estimates 289 new Thread(() -> { 290 while (true) { 291 try { 292 if (installedLatch.await(1, TimeUnit.SECONDS)) { 293 break; 294 } 295 } catch (InterruptedException ignored) { 296 } 297 298 final long deltaFreeBytes = startFreeBytes - measurePath.getUsableSpace(); 299 final int progress = 10 + (int) MathUtils.constrain( 300 ((deltaFreeBytes * 80) / sizeBytes), 0, 80); 301 mPm.mMoveCallbacks.notifyStatusChanged(moveId, progress); 302 } 303 }).start(); 304 305 move = new MoveInfo(moveId, currentVolumeUuid, volumeUuid, packageName, 306 appId, seinfo, targetSdkVersion, fromCodePath); 307 } else { 308 move = null; 309 } 310 311 installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; 312 313 final OriginInfo origin = OriginInfo.fromExistingFile(codeFile); 314 final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); 315 final ParseResult<PackageLite> ret = ApkLiteParseUtils.parsePackageLite(input, 316 new File(origin.mResolvedPath), /* flags */ 0); 317 final PackageLite lite = ret.isSuccess() ? ret.getResult() : null; 318 final InstallingSession installingSession = new InstallingSession(origin, move, 319 installObserver, installFlags, /* developmentInstallFlags= */ 0, installSource, 320 volumeUuid, userForMove, packageAbiOverride, 321 PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm); 322 installingSession.movePackage(); 323 } 324 325 /** 326 * Logs that an app has been moved from internal to external storage and vice versa. 327 * @param packageName The package that was moved. 328 */ logAppMovedStorage(String packageName, boolean isPreviousLocationExternal)329 private void logAppMovedStorage(String packageName, boolean isPreviousLocationExternal) { 330 final Computer snapshot = mPm.snapshotComputer(); 331 final AndroidPackage pkg = snapshot.getPackage(packageName); 332 if (pkg == null) { 333 return; 334 } 335 336 final StorageManager storage = mPm.mInjector.getSystemService(StorageManager.class); 337 VolumeInfo volume = storage.findVolumeByUuid( 338 StorageManager.convert(pkg.getVolumeUuid()).toString()); 339 int packageExternalStorageType = PackageManagerServiceUtils.getPackageExternalStorageType( 340 volume, pkg.isExternalStorage()); 341 342 if (!isPreviousLocationExternal && pkg.isExternalStorage()) { 343 // Move from internal to external storage. 344 FrameworkStatsLog.write(FrameworkStatsLog.APP_MOVED_STORAGE_REPORTED, 345 packageExternalStorageType, 346 FrameworkStatsLog.APP_MOVED_STORAGE_REPORTED__MOVE_TYPE__TO_EXTERNAL, 347 packageName); 348 } else if (isPreviousLocationExternal && !pkg.isExternalStorage()) { 349 // Move from external to internal storage. 350 FrameworkStatsLog.write(FrameworkStatsLog.APP_MOVED_STORAGE_REPORTED, 351 packageExternalStorageType, 352 FrameworkStatsLog.APP_MOVED_STORAGE_REPORTED__MOVE_TYPE__TO_INTERNAL, 353 packageName); 354 } 355 } 356 357 @GuardedBy("mPm.mInstallLock") getPackageSizeInfoLI(String packageName, int userId, PackageStats stats)358 private boolean getPackageSizeInfoLI(String packageName, int userId, PackageStats stats) { 359 final Computer snapshot = mPm.snapshotComputer(); 360 final PackageStateInternal packageStateInternal = 361 snapshot.getPackageStateInternal(packageName); 362 if (packageStateInternal == null) { 363 Slog.w(TAG, "Failed to find settings for " + packageName); 364 return false; 365 } 366 367 final String[] packageNames = { packageName }; 368 final long[] ceDataInodes = { 369 packageStateInternal.getUserStateOrDefault(userId).getCeDataInode() }; 370 final String[] codePaths = { packageStateInternal.getPathString() }; 371 372 try { 373 mPm.mInstaller.getAppSize(packageStateInternal.getVolumeUuid(), packageNames, userId, 374 0, packageStateInternal.getAppId(), ceDataInodes, codePaths, stats); 375 376 // For now, ignore code size of packages on system partition 377 if (PackageManagerServiceUtils.isSystemApp(packageStateInternal) 378 && !PackageManagerServiceUtils.isUpdatedSystemApp(packageStateInternal)) { 379 stats.codeSize = 0; 380 } 381 382 // External clients expect these to be tracked separately 383 stats.dataSize -= stats.cacheSize; 384 385 } catch (Installer.InstallerException e) { 386 Slog.w(TAG, String.valueOf(e)); 387 return false; 388 } 389 390 return true; 391 } 392 prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid, int[] userIds)393 private void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid, 394 int[] userIds) { 395 if (DEBUG_INSTALL) { 396 Slog.d(TAG, "Preparing user directories before moving app, from UUID " + fromVolumeUuid 397 + " to UUID " + toVolumeUuid); 398 } 399 final StorageManagerInternal smInternal = 400 mPm.mInjector.getLocalService(StorageManagerInternal.class); 401 final ArrayList<UserInfo> users = new ArrayList<>(); 402 for (int userId : userIds) { 403 final UserInfo user = mPm.mUserManager.getUserInfo(userId); 404 users.add(user); 405 } 406 smInternal.prepareUserStorageForMove(fromVolumeUuid, toVolumeUuid, users); 407 } 408 409 public static class MoveCallbacks extends Handler { 410 private static final int MSG_CREATED = 1; 411 private static final int MSG_STATUS_CHANGED = 2; 412 413 private final RemoteCallbackList<IPackageMoveObserver> 414 mCallbacks = new RemoteCallbackList<>(); 415 416 public final SparseIntArray mLastStatus = new SparseIntArray(); 417 MoveCallbacks(Looper looper)418 public MoveCallbacks(Looper looper) { 419 super(looper); 420 } 421 register(IPackageMoveObserver callback)422 public void register(IPackageMoveObserver callback) { 423 mCallbacks.register(callback); 424 } 425 unregister(IPackageMoveObserver callback)426 public void unregister(IPackageMoveObserver callback) { 427 mCallbacks.unregister(callback); 428 } 429 430 @Override handleMessage(Message msg)431 public void handleMessage(Message msg) { 432 final SomeArgs args = (SomeArgs) msg.obj; 433 final int n = mCallbacks.beginBroadcast(); 434 for (int i = 0; i < n; i++) { 435 final IPackageMoveObserver callback = mCallbacks.getBroadcastItem(i); 436 try { 437 invokeCallback(callback, msg.what, args); 438 } catch (RemoteException ignored) { 439 } 440 } 441 mCallbacks.finishBroadcast(); 442 args.recycle(); 443 } 444 invokeCallback(IPackageMoveObserver callback, int what, SomeArgs args)445 private void invokeCallback(IPackageMoveObserver callback, int what, SomeArgs args) 446 throws RemoteException { 447 switch (what) { 448 case MSG_CREATED: { 449 callback.onCreated(args.argi1, (Bundle) args.arg2); 450 break; 451 } 452 case MSG_STATUS_CHANGED: { 453 callback.onStatusChanged(args.argi1, args.argi2, (long) args.arg3); 454 break; 455 } 456 } 457 } 458 notifyCreated(int moveId, Bundle extras)459 public void notifyCreated(int moveId, Bundle extras) { 460 Slog.v(TAG, "Move " + moveId + " created " + extras.toString()); 461 462 final SomeArgs args = SomeArgs.obtain(); 463 args.argi1 = moveId; 464 args.arg2 = extras; 465 obtainMessage(MSG_CREATED, args).sendToTarget(); 466 } 467 notifyStatusChanged(int moveId, int status)468 public void notifyStatusChanged(int moveId, int status) { 469 notifyStatusChanged(moveId, status, -1); 470 } 471 notifyStatusChanged(int moveId, int status, long estMillis)472 public void notifyStatusChanged(int moveId, int status, long estMillis) { 473 Slog.v(TAG, "Move " + moveId + " status " + status); 474 475 final SomeArgs args = SomeArgs.obtain(); 476 args.argi1 = moveId; 477 args.argi2 = status; 478 args.arg3 = estMillis; 479 obtainMessage(MSG_STATUS_CHANGED, args).sendToTarget(); 480 481 synchronized (mLastStatus) { 482 mLastStatus.put(moveId, status); 483 } 484 } 485 } 486 } 487