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