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.parsing.ApkLiteParseUtils; 39 import android.content.pm.parsing.PackageLite; 40 import android.content.pm.parsing.result.ParseResult; 41 import android.content.pm.parsing.result.ParseTypeImpl; 42 import android.os.Bundle; 43 import android.os.Environment; 44 import android.os.Handler; 45 import android.os.Looper; 46 import android.os.Message; 47 import android.os.RemoteCallbackList; 48 import android.os.RemoteException; 49 import android.os.UserHandle; 50 import android.os.storage.StorageManager; 51 import android.os.storage.VolumeInfo; 52 import android.util.MathUtils; 53 import android.util.Slog; 54 import android.util.SparseIntArray; 55 56 import com.android.internal.annotations.GuardedBy; 57 import com.android.internal.os.SomeArgs; 58 import com.android.internal.util.FrameworkStatsLog; 59 import com.android.server.pm.parsing.pkg.AndroidPackageUtils; 60 import com.android.server.pm.pkg.AndroidPackage; 61 import com.android.server.pm.pkg.PackageStateInternal; 62 import com.android.server.pm.pkg.PackageStateUtils; 63 64 import java.io.File; 65 import java.util.Objects; 66 import java.util.concurrent.CountDownLatch; 67 import java.util.concurrent.TimeUnit; 68 69 public final class MovePackageHelper { 70 final PackageManagerService mPm; 71 72 // TODO(b/198166813): remove PMS dependency MovePackageHelper(PackageManagerService pm)73 public MovePackageHelper(PackageManagerService pm) { 74 mPm = pm; 75 } 76 movePackageInternal(final String packageName, final String volumeUuid, final int moveId, final int callingUid, UserHandle user)77 public void movePackageInternal(final String packageName, final String volumeUuid, 78 final int moveId, final int callingUid, UserHandle user) 79 throws PackageManagerException { 80 final StorageManager storage = mPm.mInjector.getSystemService(StorageManager.class); 81 final PackageManager pm = mPm.mContext.getPackageManager(); 82 83 Computer snapshot = mPm.snapshotComputer(); 84 final PackageStateInternal packageState = snapshot.getPackageStateForInstalledAndFiltered( 85 packageName, callingUid, user.getIdentifier()); 86 if (packageState == null || packageState.getPkg() == null) { 87 throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package"); 88 } 89 final AndroidPackage pkg = packageState.getPkg(); 90 if (packageState.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 = packageState.getSeInfo(); 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, UserHandle.USER_ALL, 150 "movePackageInternal", ApplicationExitInfo.REASON_USER_REQUESTED); 151 } 152 153 final Bundle extras = new Bundle(); 154 extras.putString(Intent.EXTRA_PACKAGE_NAME, packageName); 155 extras.putString(Intent.EXTRA_TITLE, label); 156 mPm.mMoveCallbacks.notifyCreated(moveId, extras); 157 158 int installFlags; 159 final boolean moveCompleteApp; 160 final File measurePath; 161 162 installFlags = INSTALL_INTERNAL; 163 if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) { 164 moveCompleteApp = true; 165 measurePath = Environment.getDataAppDirectory(volumeUuid); 166 } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) { 167 moveCompleteApp = false; 168 measurePath = storage.getPrimaryPhysicalVolume().getPath(); 169 } else { 170 final VolumeInfo volume = storage.findVolumeByUuid(volumeUuid); 171 if (volume == null || volume.getType() != VolumeInfo.TYPE_PRIVATE 172 || !volume.isMountedWritable()) { 173 freezer.close(); 174 throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR, 175 "Move location not mounted private volume"); 176 } 177 178 moveCompleteApp = true; 179 measurePath = Environment.getDataAppDirectory(volumeUuid); 180 } 181 182 // If we're moving app data around, we need all the users unlocked 183 if (moveCompleteApp) { 184 for (int userId : installedUserIds) { 185 if (StorageManager.isFileEncrypted() && !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 InstallingSession installingSession = new InstallingSession(origin, move, 296 installObserver, installFlags, installSource, volumeUuid, user, packageAbiOverride, 297 PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm); 298 installingSession.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 Computer snapshot = mPm.snapshotComputer(); 307 final AndroidPackage pkg = snapshot.getPackage(packageName); 308 if (pkg == null) { 309 return; 310 } 311 312 final StorageManager storage = mPm.mInjector.getSystemService(StorageManager.class); 313 VolumeInfo volume = storage.findVolumeByUuid( 314 StorageManager.convert(pkg.getVolumeUuid()).toString()); 315 int packageExternalStorageType = PackageManagerServiceUtils.getPackageExternalStorageType( 316 volume, pkg.isExternalStorage()); 317 318 if (!isPreviousLocationExternal && pkg.isExternalStorage()) { 319 // Move from internal to external storage. 320 FrameworkStatsLog.write(FrameworkStatsLog.APP_MOVED_STORAGE_REPORTED, 321 packageExternalStorageType, 322 FrameworkStatsLog.APP_MOVED_STORAGE_REPORTED__MOVE_TYPE__TO_EXTERNAL, 323 packageName); 324 } else if (isPreviousLocationExternal && !pkg.isExternalStorage()) { 325 // Move from external to internal storage. 326 FrameworkStatsLog.write(FrameworkStatsLog.APP_MOVED_STORAGE_REPORTED, 327 packageExternalStorageType, 328 FrameworkStatsLog.APP_MOVED_STORAGE_REPORTED__MOVE_TYPE__TO_INTERNAL, 329 packageName); 330 } 331 } 332 333 @GuardedBy("mPm.mInstallLock") getPackageSizeInfoLI(String packageName, int userId, PackageStats stats)334 private boolean getPackageSizeInfoLI(String packageName, int userId, PackageStats stats) { 335 final Computer snapshot = mPm.snapshotComputer(); 336 final PackageStateInternal packageStateInternal = 337 snapshot.getPackageStateInternal(packageName); 338 if (packageStateInternal == null) { 339 Slog.w(TAG, "Failed to find settings for " + packageName); 340 return false; 341 } 342 343 final String[] packageNames = { packageName }; 344 final long[] ceDataInodes = { 345 packageStateInternal.getUserStateOrDefault(userId).getCeDataInode() }; 346 final String[] codePaths = { packageStateInternal.getPathString() }; 347 348 try { 349 mPm.mInstaller.getAppSize(packageStateInternal.getVolumeUuid(), packageNames, userId, 350 0, packageStateInternal.getAppId(), ceDataInodes, codePaths, stats); 351 352 // For now, ignore code size of packages on system partition 353 if (PackageManagerServiceUtils.isSystemApp(packageStateInternal) 354 && !PackageManagerServiceUtils.isUpdatedSystemApp(packageStateInternal)) { 355 stats.codeSize = 0; 356 } 357 358 // External clients expect these to be tracked separately 359 stats.dataSize -= stats.cacheSize; 360 361 } catch (Installer.InstallerException e) { 362 Slog.w(TAG, String.valueOf(e)); 363 return false; 364 } 365 366 return true; 367 } 368 369 public static class MoveCallbacks extends Handler { 370 private static final int MSG_CREATED = 1; 371 private static final int MSG_STATUS_CHANGED = 2; 372 373 private final RemoteCallbackList<IPackageMoveObserver> 374 mCallbacks = new RemoteCallbackList<>(); 375 376 public final SparseIntArray mLastStatus = new SparseIntArray(); 377 MoveCallbacks(Looper looper)378 public MoveCallbacks(Looper looper) { 379 super(looper); 380 } 381 register(IPackageMoveObserver callback)382 public void register(IPackageMoveObserver callback) { 383 mCallbacks.register(callback); 384 } 385 unregister(IPackageMoveObserver callback)386 public void unregister(IPackageMoveObserver callback) { 387 mCallbacks.unregister(callback); 388 } 389 390 @Override handleMessage(Message msg)391 public void handleMessage(Message msg) { 392 final SomeArgs args = (SomeArgs) msg.obj; 393 final int n = mCallbacks.beginBroadcast(); 394 for (int i = 0; i < n; i++) { 395 final IPackageMoveObserver callback = mCallbacks.getBroadcastItem(i); 396 try { 397 invokeCallback(callback, msg.what, args); 398 } catch (RemoteException ignored) { 399 } 400 } 401 mCallbacks.finishBroadcast(); 402 args.recycle(); 403 } 404 invokeCallback(IPackageMoveObserver callback, int what, SomeArgs args)405 private void invokeCallback(IPackageMoveObserver callback, int what, SomeArgs args) 406 throws RemoteException { 407 switch (what) { 408 case MSG_CREATED: { 409 callback.onCreated(args.argi1, (Bundle) args.arg2); 410 break; 411 } 412 case MSG_STATUS_CHANGED: { 413 callback.onStatusChanged(args.argi1, args.argi2, (long) args.arg3); 414 break; 415 } 416 } 417 } 418 notifyCreated(int moveId, Bundle extras)419 public void notifyCreated(int moveId, Bundle extras) { 420 Slog.v(TAG, "Move " + moveId + " created " + extras.toString()); 421 422 final SomeArgs args = SomeArgs.obtain(); 423 args.argi1 = moveId; 424 args.arg2 = extras; 425 obtainMessage(MSG_CREATED, args).sendToTarget(); 426 } 427 notifyStatusChanged(int moveId, int status)428 public void notifyStatusChanged(int moveId, int status) { 429 notifyStatusChanged(moveId, status, -1); 430 } 431 notifyStatusChanged(int moveId, int status, long estMillis)432 public void notifyStatusChanged(int moveId, int status, long estMillis) { 433 Slog.v(TAG, "Move " + moveId + " status " + status); 434 435 final SomeArgs args = SomeArgs.obtain(); 436 args.argi1 = moveId; 437 args.argi2 = status; 438 args.arg3 = estMillis; 439 obtainMessage(MSG_STATUS_CHANGED, args).sendToTarget(); 440 441 synchronized (mLastStatus) { 442 mLastStatus.put(moveId, status); 443 } 444 } 445 } 446 } 447