• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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