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