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