• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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.internal.content;
18 
19 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
20 import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
21 
22 import android.content.Context;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageInstaller.SessionParams;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.pm.dex.DexMetadataHelper;
29 import android.content.pm.parsing.PackageLite;
30 import android.os.Environment;
31 import android.os.IBinder;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.os.storage.IStorageManager;
35 import android.os.storage.StorageManager;
36 import android.os.storage.StorageVolume;
37 import android.os.storage.VolumeInfo;
38 import android.provider.Settings;
39 import android.util.ArrayMap;
40 import android.util.Log;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 
44 import libcore.io.IoUtils;
45 
46 import java.io.File;
47 import java.io.FileDescriptor;
48 import java.io.IOException;
49 import java.util.Objects;
50 import java.util.UUID;
51 
52 /**
53  * Constants used internally between the PackageManager
54  * and media container service transports.
55  * Some utility methods to invoke StorageManagerService api.
56  */
57 public class InstallLocationUtils {
58     public static final int RECOMMEND_INSTALL_INTERNAL = 1;
59     public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
60     public static final int RECOMMEND_INSTALL_EPHEMERAL = 3;
61     public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1;
62     public static final int RECOMMEND_FAILED_INVALID_APK = -2;
63     public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3;
64     public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4;
65     public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5;
66     public static final int RECOMMEND_FAILED_INVALID_URI = -6;
67 
68     private static final String TAG = "PackageHelper";
69     // App installation location settings values
70     public static final int APP_INSTALL_AUTO = 0;
71     public static final int APP_INSTALL_INTERNAL = 1;
72     public static final int APP_INSTALL_EXTERNAL = 2;
73 
74     private static TestableInterface sDefaultTestableInterface = null;
75 
getStorageManager()76     public static IStorageManager getStorageManager() throws RemoteException {
77         IBinder service = ServiceManager.getService("mount");
78         if (service != null) {
79             return IStorageManager.Stub.asInterface(service);
80         } else {
81             Log.e(TAG, "Can't get storagemanager service");
82             throw new RemoteException("Could not contact storagemanager service");
83         }
84     }
85 
86     /**
87      * A group of external dependencies used in
88      * {@link #resolveInstallVolume(Context, String, int, long, TestableInterface)}.
89      * It can be backed by real values from the system or mocked ones for testing purposes.
90      */
91     public static abstract class TestableInterface {
getStorageManager(Context context)92         abstract public StorageManager getStorageManager(Context context);
93 
getForceAllowOnExternalSetting(Context context)94         abstract public boolean getForceAllowOnExternalSetting(Context context);
95 
getAllow3rdPartyOnInternalConfig(Context context)96         abstract public boolean getAllow3rdPartyOnInternalConfig(Context context);
97 
getExistingAppInfo(Context context, String packageName)98         abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName);
99 
getDataDirectory()100         abstract public File getDataDirectory();
101     }
102 
getDefaultTestableInterface()103     private synchronized static TestableInterface getDefaultTestableInterface() {
104         if (sDefaultTestableInterface == null) {
105             sDefaultTestableInterface = new TestableInterface() {
106                 @Override
107                 public StorageManager getStorageManager(Context context) {
108                     return context.getSystemService(StorageManager.class);
109                 }
110 
111                 @Override
112                 public boolean getForceAllowOnExternalSetting(Context context) {
113                     return Settings.Global.getInt(context.getContentResolver(),
114                             Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
115                 }
116 
117                 @Override
118                 public boolean getAllow3rdPartyOnInternalConfig(Context context) {
119                     return context.getResources().getBoolean(
120                             com.android.internal.R.bool.config_allow3rdPartyAppOnInternal);
121                 }
122 
123                 @Override
124                 public ApplicationInfo getExistingAppInfo(Context context, String packageName) {
125                     ApplicationInfo existingInfo = null;
126                     try {
127                         existingInfo = context.getPackageManager().getApplicationInfo(packageName,
128                                 PackageManager.MATCH_ANY_USER);
129                     } catch (NameNotFoundException ignored) {
130                     }
131                     return existingInfo;
132                 }
133 
134                 @Override
135                 public File getDataDirectory() {
136                     return Environment.getDataDirectory();
137                 }
138             };
139         }
140         return sDefaultTestableInterface;
141     }
142 
143     @VisibleForTesting
144     @Deprecated
resolveInstallVolume(Context context, String packageName, int installLocation, long sizeBytes, TestableInterface testInterface)145     public static String resolveInstallVolume(Context context, String packageName,
146             int installLocation, long sizeBytes, TestableInterface testInterface)
147             throws IOException {
148         final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
149         params.appPackageName = packageName;
150         params.installLocation = installLocation;
151         params.sizeBytes = sizeBytes;
152         return resolveInstallVolume(context, params, testInterface);
153     }
154 
155     /**
156      * Given a requested {@link PackageInfo#installLocation} and calculated
157      * install size, pick the actual volume to install the app. Only considers
158      * internal and private volumes, and prefers to keep an existing package onocation
159      * its current volume.
160      *
161      * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
162      * for internal storage.
163      */
resolveInstallVolume(Context context, SessionParams params)164     public static String resolveInstallVolume(Context context, SessionParams params)
165             throws IOException {
166         TestableInterface testableInterface = getDefaultTestableInterface();
167         return resolveInstallVolume(context, params.appPackageName, params.installLocation,
168                 params.sizeBytes, testableInterface);
169     }
170 
checkFitOnVolume(StorageManager storageManager, String volumePath, SessionParams params)171     private static boolean checkFitOnVolume(StorageManager storageManager, String volumePath,
172             SessionParams params) throws IOException {
173         if (volumePath == null) {
174             return false;
175         }
176         final int installFlags = translateAllocateFlags(params.installFlags);
177         final UUID target = storageManager.getUuidForPath(new File(volumePath));
178         final long availBytes = storageManager.getAllocatableBytes(target,
179                 installFlags | StorageManager.FLAG_ALLOCATE_NON_CACHE_ONLY);
180         if (params.sizeBytes <= availBytes) {
181             return true;
182         }
183         final long cacheClearable = storageManager.getAllocatableBytes(target,
184                 installFlags | StorageManager.FLAG_ALLOCATE_CACHE_ONLY);
185         return params.sizeBytes <= availBytes + cacheClearable;
186     }
187 
188     @VisibleForTesting
resolveInstallVolume(Context context, SessionParams params, TestableInterface testInterface)189     public static String resolveInstallVolume(Context context, SessionParams params,
190             TestableInterface testInterface) throws IOException {
191         final StorageManager storageManager = testInterface.getStorageManager(context);
192         final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context);
193         final boolean allow3rdPartyOnInternal =
194                 testInterface.getAllow3rdPartyOnInternalConfig(context);
195         // TODO: handle existing apps installed in ASEC; currently assumes
196         // they'll end up back on internal storage
197         ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context,
198                 params.appPackageName);
199 
200         final ArrayMap<String, String> volumePaths = new ArrayMap<>();
201         String internalVolumePath = null;
202         for (VolumeInfo vol : storageManager.getVolumes()) {
203             if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
204                 final boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id);
205                 if (isInternalStorage) {
206                     internalVolumePath = vol.path;
207                 }
208                 if (!isInternalStorage || allow3rdPartyOnInternal) {
209                     volumePaths.put(vol.fsUuid, vol.path);
210                 }
211             }
212         }
213 
214         // System apps always forced to internal storage
215         if (existingInfo != null && existingInfo.isSystemApp()) {
216             if (checkFitOnVolume(storageManager, internalVolumePath, params)) {
217                 return StorageManager.UUID_PRIVATE_INTERNAL;
218             } else {
219                 throw new IOException("Not enough space on existing volume "
220                         + existingInfo.volumeUuid + " for system app " + params.appPackageName
221                         + " upgrade");
222             }
223         }
224 
225         // If app expresses strong desire for internal storage, honor it
226         if (!forceAllowOnExternal
227                 && params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
228             if (existingInfo != null && !Objects.equals(existingInfo.volumeUuid,
229                     StorageManager.UUID_PRIVATE_INTERNAL)) {
230                 throw new IOException("Cannot automatically move " + params.appPackageName
231                         + " from " + existingInfo.volumeUuid + " to internal storage");
232             }
233 
234             if (!allow3rdPartyOnInternal) {
235                 throw new IOException("Not allowed to install non-system apps on internal storage");
236             }
237 
238             if (checkFitOnVolume(storageManager, internalVolumePath, params)) {
239                 return StorageManager.UUID_PRIVATE_INTERNAL;
240             } else {
241                 throw new IOException("Requested internal only, but not enough space");
242             }
243         }
244 
245         // If app already exists somewhere, we must stay on that volume
246         if (existingInfo != null) {
247             String existingVolumePath = null;
248             if (Objects.equals(existingInfo.volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
249                 existingVolumePath = internalVolumePath;
250             } else if (volumePaths.containsKey(existingInfo.volumeUuid)) {
251                 existingVolumePath = volumePaths.get(existingInfo.volumeUuid);
252             }
253 
254             if (checkFitOnVolume(storageManager, existingVolumePath, params)) {
255                 return existingInfo.volumeUuid;
256             } else {
257                 throw new IOException("Not enough space on existing volume "
258                         + existingInfo.volumeUuid + " for " + params.appPackageName + " upgrade");
259             }
260         }
261 
262         // We're left with new installations with either preferring external or auto, so just pick
263         // volume with most space
264         if (volumePaths.size() == 1) {
265             if (checkFitOnVolume(storageManager, volumePaths.valueAt(0), params)) {
266                 return volumePaths.keyAt(0);
267             }
268         } else {
269             String bestCandidate = null;
270             long bestCandidateAvailBytes = Long.MIN_VALUE;
271             for (String vol : volumePaths.keySet()) {
272                 final String volumePath = volumePaths.get(vol);
273                 final UUID target = storageManager.getUuidForPath(new File(volumePath));
274 
275                 // We need to take into account freeable cached space, because we're choosing the
276                 // best candidate amongst a list, not just checking if we fit at all.
277                 final long availBytes = storageManager.getAllocatableBytes(target,
278                         translateAllocateFlags(params.installFlags));
279 
280                 if (availBytes >= bestCandidateAvailBytes) {
281                     bestCandidate = vol;
282                     bestCandidateAvailBytes = availBytes;
283                 }
284             }
285 
286             if (bestCandidateAvailBytes >= params.sizeBytes) {
287                 return bestCandidate;
288             }
289 
290         }
291 
292         throw new IOException("No special requests, but no room on allowed volumes. "
293                 + " allow3rdPartyOnInternal? " + allow3rdPartyOnInternal);
294     }
295 
fitsOnInternal(Context context, SessionParams params)296     public static boolean fitsOnInternal(Context context, SessionParams params) throws IOException {
297         final StorageManager storage = context.getSystemService(StorageManager.class);
298         final UUID target = storage.getUuidForPath(Environment.getDataDirectory());
299         final int flags = translateAllocateFlags(params.installFlags);
300 
301         final long allocateableBytes = storage.getAllocatableBytes(target,
302                 flags | StorageManager.FLAG_ALLOCATE_NON_CACHE_ONLY);
303 
304         // If we fit on internal storage without including freeable cache space, don't bother
305         // checking to determine how much space is taken up by the cache.
306         if (params.sizeBytes <= allocateableBytes) {
307             return true;
308         }
309 
310         final long cacheClearable = storage.getAllocatableBytes(target,
311                 flags | StorageManager.FLAG_ALLOCATE_CACHE_ONLY);
312 
313         return params.sizeBytes <= allocateableBytes + cacheClearable;
314     }
315 
fitsOnExternal(Context context, SessionParams params)316     public static boolean fitsOnExternal(Context context, SessionParams params) {
317         final StorageManager storage = context.getSystemService(StorageManager.class);
318         final StorageVolume primary = storage.getPrimaryVolume();
319         return (params.sizeBytes > 0) && !primary.isEmulated()
320                 && Environment.MEDIA_MOUNTED.equals(primary.getState())
321                 && params.sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
322     }
323 
324     /**
325      * Given a requested {@link PackageInfo#installLocation} and calculated
326      * install size, pick the actual location to install the app.
327      */
resolveInstallLocation(Context context, SessionParams params)328     public static int resolveInstallLocation(Context context, SessionParams params)
329             throws IOException {
330         ApplicationInfo existingInfo = null;
331         try {
332             existingInfo = context.getPackageManager().getApplicationInfo(params.appPackageName,
333                     PackageManager.MATCH_ANY_USER);
334         } catch (NameNotFoundException ignored) {
335         }
336 
337         final int prefer;
338         final boolean checkBoth;
339         boolean ephemeral = false;
340         if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
341             prefer = RECOMMEND_INSTALL_INTERNAL;
342             ephemeral = true;
343             checkBoth = false;
344         } else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
345             prefer = RECOMMEND_INSTALL_INTERNAL;
346             checkBoth = false;
347         } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
348             prefer = RECOMMEND_INSTALL_INTERNAL;
349             checkBoth = false;
350         } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
351             prefer = RECOMMEND_INSTALL_EXTERNAL;
352             checkBoth = true;
353         } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
354             // When app is already installed, prefer same medium
355             if (existingInfo != null) {
356                 // TODO: distinguish if this is external ASEC
357                 if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
358                     prefer = RECOMMEND_INSTALL_EXTERNAL;
359                 } else {
360                     prefer = RECOMMEND_INSTALL_INTERNAL;
361                 }
362             } else {
363                 prefer = RECOMMEND_INSTALL_INTERNAL;
364             }
365             checkBoth = true;
366         } else {
367             prefer = RECOMMEND_INSTALL_INTERNAL;
368             checkBoth = false;
369         }
370 
371         boolean fitsOnInternal = false;
372         if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
373             fitsOnInternal = fitsOnInternal(context, params);
374         }
375 
376         boolean fitsOnExternal = false;
377         if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
378             fitsOnExternal = fitsOnExternal(context, params);
379         }
380 
381         if (prefer == RECOMMEND_INSTALL_INTERNAL) {
382             // The ephemeral case will either fit and return EPHEMERAL, or will not fit
383             // and will fall through to return INSUFFICIENT_STORAGE
384             if (fitsOnInternal) {
385                 return (ephemeral)
386                         ? InstallLocationUtils.RECOMMEND_INSTALL_EPHEMERAL
387                         : InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
388             }
389         } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
390             if (fitsOnExternal) {
391                 return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
392             }
393         }
394 
395         if (checkBoth) {
396             if (fitsOnInternal) {
397                 return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
398             } else if (fitsOnExternal) {
399                 return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
400             }
401         }
402 
403         return InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
404     }
405 
406     @Deprecated
calculateInstalledSize(PackageLite pkg, boolean isForwardLocked, String abiOverride)407     public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
408             String abiOverride) throws IOException {
409         return calculateInstalledSize(pkg, abiOverride);
410     }
411 
calculateInstalledSize(PackageLite pkg, String abiOverride)412     public static long calculateInstalledSize(PackageLite pkg, String abiOverride)
413             throws IOException {
414         return calculateInstalledSize(pkg, abiOverride, null);
415     }
416 
calculateInstalledSize(PackageLite pkg, String abiOverride, FileDescriptor fd)417     public static long calculateInstalledSize(PackageLite pkg, String abiOverride,
418             FileDescriptor fd) throws IOException {
419         NativeLibraryHelper.Handle handle = null;
420         try {
421             handle = fd != null ? NativeLibraryHelper.Handle.createFd(pkg, fd)
422                     : NativeLibraryHelper.Handle.create(pkg);
423             return calculateInstalledSize(pkg, handle, abiOverride);
424         } finally {
425             IoUtils.closeQuietly(handle);
426         }
427     }
428 
429     @Deprecated
calculateInstalledSize(PackageLite pkg, boolean isForwardLocked, NativeLibraryHelper.Handle handle, String abiOverride)430     public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
431             NativeLibraryHelper.Handle handle, String abiOverride) throws IOException {
432         return calculateInstalledSize(pkg, handle, abiOverride);
433     }
434 
calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle, String abiOverride)435     public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle,
436             String abiOverride) throws IOException {
437         long sizeBytes = 0;
438 
439         // Include raw APKs, and possibly unpacked resources
440         for (String codePath : pkg.getAllApkPaths()) {
441             final File codeFile = new File(codePath);
442             sizeBytes += codeFile.length();
443         }
444 
445         // Include raw dex metadata files
446         sizeBytes += DexMetadataHelper.getPackageDexMetadataSize(pkg);
447 
448         // Include all relevant native code
449         sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride);
450 
451         return sizeBytes;
452     }
453 
replaceEnd(String str, String before, String after)454     public static String replaceEnd(String str, String before, String after) {
455         if (!str.endsWith(before)) {
456             throw new IllegalArgumentException(
457                     "Expected " + str + " to end with " + before);
458         }
459         return str.substring(0, str.length() - before.length()) + after;
460     }
461 
translateAllocateFlags(int installFlags)462     public static int translateAllocateFlags(int installFlags) {
463         if ((installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0) {
464             return StorageManager.FLAG_ALLOCATE_AGGRESSIVE;
465         } else {
466             return 0;
467         }
468     }
469 
installLocationPolicy(int installLocation, int recommendedInstallLocation, int installFlags, boolean installedPkgIsSystem, boolean installedPackageOnExternal)470     public static int installLocationPolicy(int installLocation, int recommendedInstallLocation,
471             int installFlags, boolean installedPkgIsSystem, boolean installedPackageOnExternal) {
472         if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) {
473             // Invalid install. Return error code
474             return RECOMMEND_FAILED_ALREADY_EXISTS;
475         }
476         // Check for updated system application.
477         if (installedPkgIsSystem) {
478             return RECOMMEND_INSTALL_INTERNAL;
479         }
480         // If current upgrade specifies particular preference
481         if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
482             // Application explicitly specified internal.
483             return RECOMMEND_INSTALL_INTERNAL;
484         } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
485             // App explicitly prefers external. Let policy decide
486             return recommendedInstallLocation;
487         } else {
488             // Prefer previous location
489             if (installedPackageOnExternal) {
490                 return RECOMMEND_INSTALL_EXTERNAL;
491             }
492             return RECOMMEND_INSTALL_INTERNAL;
493         }
494     }
495 
getInstallationErrorCode(int loc)496     public static int getInstallationErrorCode(int loc) {
497         if (loc == RECOMMEND_FAILED_INVALID_LOCATION) {
498             return PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
499         } else if (loc == RECOMMEND_FAILED_ALREADY_EXISTS) {
500             return PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
501         } else if (loc == RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
502             return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
503         } else if (loc == RECOMMEND_FAILED_INVALID_APK) {
504             return PackageManager.INSTALL_FAILED_INVALID_APK;
505         } else if (loc == RECOMMEND_FAILED_INVALID_URI) {
506             return PackageManager.INSTALL_FAILED_INVALID_URI;
507         } else if (loc == RECOMMEND_MEDIA_UNAVAILABLE) {
508             return PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
509         } else {
510             return INSTALL_SUCCEEDED;
511         }
512     }
513 }
514