PackageHelper.java revision 4233f032fd41e82d1ce09b48860f8ae3f73fa5e0
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
17package com.android.internal.content;
18
19import static android.net.TrafficStats.MB_IN_BYTES;
20import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
21
22import android.content.Context;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.PackageInfo;
25import android.content.pm.PackageInstaller.SessionParams;
26import android.content.pm.PackageManager;
27import android.content.pm.PackageManager.NameNotFoundException;
28import android.content.pm.PackageParser.PackageLite;
29import android.os.Environment;
30import android.os.FileUtils;
31import android.os.IBinder;
32import android.os.RemoteException;
33import android.os.ServiceManager;
34import android.os.storage.IStorageManager;
35import android.os.storage.StorageManager;
36import android.os.storage.StorageResultCode;
37import android.os.storage.StorageVolume;
38import android.os.storage.VolumeInfo;
39import android.provider.Settings;
40import android.util.ArraySet;
41import android.util.Log;
42
43import com.android.internal.annotations.VisibleForTesting;
44
45import libcore.io.IoUtils;
46
47import java.io.File;
48import java.io.FileOutputStream;
49import java.io.IOException;
50import java.io.InputStream;
51import java.util.Collections;
52import java.util.Objects;
53import java.util.UUID;
54import java.util.zip.ZipEntry;
55import java.util.zip.ZipFile;
56import java.util.zip.ZipOutputStream;
57
58/**
59 * Constants used internally between the PackageManager
60 * and media container service transports.
61 * Some utility methods to invoke StorageManagerService api.
62 */
63public class PackageHelper {
64    public static final int RECOMMEND_INSTALL_INTERNAL = 1;
65    public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
66    public static final int RECOMMEND_INSTALL_EPHEMERAL = 3;
67    public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1;
68    public static final int RECOMMEND_FAILED_INVALID_APK = -2;
69    public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3;
70    public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4;
71    public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5;
72    public static final int RECOMMEND_FAILED_INVALID_URI = -6;
73    public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7;
74
75    private static final boolean localLOGV = false;
76    private static final String TAG = "PackageHelper";
77    // App installation location settings values
78    public static final int APP_INSTALL_AUTO = 0;
79    public static final int APP_INSTALL_INTERNAL = 1;
80    public static final int APP_INSTALL_EXTERNAL = 2;
81
82    private static TestableInterface sDefaultTestableInterface = null;
83
84    public static IStorageManager getStorageManager() throws RemoteException {
85        IBinder service = ServiceManager.getService("mount");
86        if (service != null) {
87            return IStorageManager.Stub.asInterface(service);
88        } else {
89            Log.e(TAG, "Can't get storagemanager service");
90            throw new RemoteException("Could not contact storagemanager service");
91        }
92    }
93
94    public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid,
95            boolean isExternal) {
96        // Round up to nearest MB, plus another MB for filesystem overhead
97        final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
98        try {
99            IStorageManager storageManager = getStorageManager();
100
101            if (localLOGV)
102                Log.i(TAG, "Size of container " + sizeMb + " MB");
103
104            int rc = storageManager.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid,
105                    isExternal);
106            if (rc != StorageResultCode.OperationSucceeded) {
107                Log.e(TAG, "Failed to create secure container " + cid);
108                return null;
109            }
110            String cachePath = storageManager.getSecureContainerPath(cid);
111            if (localLOGV) Log.i(TAG, "Created secure container " + cid +
112                    " at " + cachePath);
113                return cachePath;
114        } catch (RemoteException e) {
115            Log.e(TAG, "StorageManagerService running?");
116        }
117        return null;
118    }
119
120    public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) {
121        // Round up to nearest MB, plus another MB for filesystem overhead
122        final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
123        try {
124            IStorageManager storageManager = getStorageManager();
125            int rc = storageManager.resizeSecureContainer(cid, sizeMb, sdEncKey);
126            if (rc == StorageResultCode.OperationSucceeded) {
127                return true;
128            }
129        } catch (RemoteException e) {
130            Log.e(TAG, "StorageManagerService running?");
131        }
132        Log.e(TAG, "Failed to create secure container " + cid);
133        return false;
134    }
135
136    public static String mountSdDir(String cid, String key, int ownerUid) {
137        return mountSdDir(cid, key, ownerUid, true);
138    }
139
140    public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) {
141        try {
142            int rc = getStorageManager().mountSecureContainer(cid, key, ownerUid, readOnly);
143            if (rc != StorageResultCode.OperationSucceeded) {
144                Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc);
145                return null;
146            }
147            return getStorageManager().getSecureContainerPath(cid);
148        } catch (RemoteException e) {
149            Log.e(TAG, "StorageManagerService running?");
150        }
151        return null;
152    }
153
154   public static boolean unMountSdDir(String cid) {
155    try {
156        int rc = getStorageManager().unmountSecureContainer(cid, true);
157        if (rc != StorageResultCode.OperationSucceeded) {
158            Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc);
159            return false;
160        }
161        return true;
162    } catch (RemoteException e) {
163        Log.e(TAG, "StorageManagerService running?");
164    }
165        return false;
166   }
167
168   public static boolean renameSdDir(String oldId, String newId) {
169       try {
170           int rc = getStorageManager().renameSecureContainer(oldId, newId);
171           if (rc != StorageResultCode.OperationSucceeded) {
172               Log.e(TAG, "Failed to rename " + oldId + " to " +
173                       newId + "with rc " + rc);
174               return false;
175           }
176           return true;
177       } catch (RemoteException e) {
178           Log.i(TAG, "Failed ot rename  " + oldId + " to " + newId +
179                   " with exception : " + e);
180       }
181       return false;
182   }
183
184   public static String getSdDir(String cid) {
185       try {
186            return getStorageManager().getSecureContainerPath(cid);
187        } catch (RemoteException e) {
188            Log.e(TAG, "Failed to get container path for " + cid +
189                " with exception " + e);
190        }
191        return null;
192   }
193
194   public static String getSdFilesystem(String cid) {
195       try {
196            return getStorageManager().getSecureContainerFilesystemPath(cid);
197        } catch (RemoteException e) {
198            Log.e(TAG, "Failed to get container path for " + cid +
199                " with exception " + e);
200        }
201        return null;
202   }
203
204    public static boolean finalizeSdDir(String cid) {
205        try {
206            int rc = getStorageManager().finalizeSecureContainer(cid);
207            if (rc != StorageResultCode.OperationSucceeded) {
208                Log.i(TAG, "Failed to finalize container " + cid);
209                return false;
210            }
211            return true;
212        } catch (RemoteException e) {
213            Log.e(TAG, "Failed to finalize container " + cid +
214                    " with exception " + e);
215        }
216        return false;
217    }
218
219    public static boolean destroySdDir(String cid) {
220        try {
221            if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid);
222            int rc = getStorageManager().destroySecureContainer(cid, true);
223            if (rc != StorageResultCode.OperationSucceeded) {
224                Log.i(TAG, "Failed to destroy container " + cid);
225                return false;
226            }
227            return true;
228        } catch (RemoteException e) {
229            Log.e(TAG, "Failed to destroy container " + cid +
230                    " with exception " + e);
231        }
232        return false;
233    }
234
235    public static String[] getSecureContainerList() {
236        try {
237            return getStorageManager().getSecureContainerList();
238        } catch (RemoteException e) {
239            Log.e(TAG, "Failed to get secure container list with exception" +
240                    e);
241        }
242        return null;
243    }
244
245   public static boolean isContainerMounted(String cid) {
246       try {
247           return getStorageManager().isSecureContainerMounted(cid);
248       } catch (RemoteException e) {
249           Log.e(TAG, "Failed to find out if container " + cid + " mounted");
250       }
251       return false;
252   }
253
254    /**
255     * Extract public files for the single given APK.
256     */
257    public static long extractPublicFiles(File apkFile, File publicZipFile)
258            throws IOException {
259        final FileOutputStream fstr;
260        final ZipOutputStream publicZipOutStream;
261
262        if (publicZipFile == null) {
263            fstr = null;
264            publicZipOutStream = null;
265        } else {
266            fstr = new FileOutputStream(publicZipFile);
267            publicZipOutStream = new ZipOutputStream(fstr);
268            Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile);
269        }
270
271        long size = 0L;
272
273        try {
274            final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath());
275            try {
276                // Copy manifest, resources.arsc and res directory to public zip
277                for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) {
278                    final String zipEntryName = zipEntry.getName();
279                    if ("AndroidManifest.xml".equals(zipEntryName)
280                            || "resources.arsc".equals(zipEntryName)
281                            || zipEntryName.startsWith("res/")) {
282                        size += zipEntry.getSize();
283                        if (publicZipFile != null) {
284                            copyZipEntry(zipEntry, privateZip, publicZipOutStream);
285                        }
286                    }
287                }
288            } finally {
289                try { privateZip.close(); } catch (IOException e) {}
290            }
291
292            if (publicZipFile != null) {
293                publicZipOutStream.finish();
294                publicZipOutStream.flush();
295                FileUtils.sync(fstr);
296                publicZipOutStream.close();
297                FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR
298                        | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1);
299            }
300        } finally {
301            IoUtils.closeQuietly(publicZipOutStream);
302        }
303
304        return size;
305    }
306
307    private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile,
308            ZipOutputStream outZipStream) throws IOException {
309        byte[] buffer = new byte[4096];
310        int num;
311
312        ZipEntry newEntry;
313        if (zipEntry.getMethod() == ZipEntry.STORED) {
314            // Preserve the STORED method of the input entry.
315            newEntry = new ZipEntry(zipEntry);
316        } else {
317            // Create a new entry so that the compressed len is recomputed.
318            newEntry = new ZipEntry(zipEntry.getName());
319        }
320        outZipStream.putNextEntry(newEntry);
321
322        final InputStream data = inZipFile.getInputStream(zipEntry);
323        try {
324            while ((num = data.read(buffer)) > 0) {
325                outZipStream.write(buffer, 0, num);
326            }
327            outZipStream.flush();
328        } finally {
329            IoUtils.closeQuietly(data);
330        }
331    }
332
333    public static boolean fixSdPermissions(String cid, int gid, String filename) {
334        try {
335            int rc = getStorageManager().fixPermissionsSecureContainer(cid, gid, filename);
336            if (rc != StorageResultCode.OperationSucceeded) {
337                Log.i(TAG, "Failed to fixperms container " + cid);
338                return false;
339            }
340            return true;
341        } catch (RemoteException e) {
342            Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e);
343        }
344        return false;
345    }
346
347    /**
348     * A group of external dependencies used in
349     * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values
350     * from the system or mocked ones for testing purposes.
351     */
352    public static abstract class TestableInterface {
353        abstract public StorageManager getStorageManager(Context context);
354        abstract public boolean getForceAllowOnExternalSetting(Context context);
355        abstract public boolean getAllow3rdPartyOnInternalConfig(Context context);
356        abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName);
357        abstract public File getDataDirectory();
358
359        public boolean fitsOnInternalStorage(Context context, SessionParams params)
360                throws IOException {
361            StorageManager storage = getStorageManager(context);
362            final UUID target = storage.getUuidForPath(getDataDirectory());
363            return (params.sizeBytes <= storage.getAllocatableBytes(target,
364                    translateAllocateFlags(params.installFlags)));
365        }
366    }
367
368    private synchronized static TestableInterface getDefaultTestableInterface() {
369        if (sDefaultTestableInterface == null) {
370            sDefaultTestableInterface = new TestableInterface() {
371                @Override
372                public StorageManager getStorageManager(Context context) {
373                    return context.getSystemService(StorageManager.class);
374                }
375
376                @Override
377                public boolean getForceAllowOnExternalSetting(Context context) {
378                    return Settings.Global.getInt(context.getContentResolver(),
379                            Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
380                }
381
382                @Override
383                public boolean getAllow3rdPartyOnInternalConfig(Context context) {
384                    return context.getResources().getBoolean(
385                            com.android.internal.R.bool.config_allow3rdPartyAppOnInternal);
386                }
387
388                @Override
389                public ApplicationInfo getExistingAppInfo(Context context, String packageName) {
390                    ApplicationInfo existingInfo = null;
391                    try {
392                        existingInfo = context.getPackageManager().getApplicationInfo(packageName,
393                                PackageManager.MATCH_ANY_USER);
394                    } catch (NameNotFoundException ignored) {
395                    }
396                    return existingInfo;
397                }
398
399                @Override
400                public File getDataDirectory() {
401                    return Environment.getDataDirectory();
402                }
403            };
404        }
405        return sDefaultTestableInterface;
406    }
407
408    @VisibleForTesting
409    @Deprecated
410    public static String resolveInstallVolume(Context context, String packageName,
411            int installLocation, long sizeBytes, TestableInterface testInterface)
412            throws IOException {
413        final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
414        params.appPackageName = packageName;
415        params.installLocation = installLocation;
416        params.sizeBytes = sizeBytes;
417        return resolveInstallVolume(context, params, testInterface);
418    }
419
420    /**
421     * Given a requested {@link PackageInfo#installLocation} and calculated
422     * install size, pick the actual volume to install the app. Only considers
423     * internal and private volumes, and prefers to keep an existing package on
424     * its current volume.
425     *
426     * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
427     *         for internal storage.
428     */
429    public static String resolveInstallVolume(Context context, SessionParams params)
430            throws IOException {
431        TestableInterface testableInterface = getDefaultTestableInterface();
432        return resolveInstallVolume(context, params.appPackageName, params.installLocation,
433                params.sizeBytes, testableInterface);
434    }
435
436    @VisibleForTesting
437    public static String resolveInstallVolume(Context context, SessionParams params,
438            TestableInterface testInterface) throws IOException {
439        final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context);
440        final boolean allow3rdPartyOnInternal =
441                testInterface.getAllow3rdPartyOnInternalConfig(context);
442        // TODO: handle existing apps installed in ASEC; currently assumes
443        // they'll end up back on internal storage
444        ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context,
445                params.appPackageName);
446
447        final boolean fitsOnInternal = testInterface.fitsOnInternalStorage(context, params);
448        final StorageManager storageManager =
449                testInterface.getStorageManager(context);
450
451        // System apps always forced to internal storage
452        if (existingInfo != null && existingInfo.isSystemApp()) {
453            if (fitsOnInternal) {
454                return StorageManager.UUID_PRIVATE_INTERNAL;
455            } else {
456                throw new IOException("Not enough space on existing volume "
457                        + existingInfo.volumeUuid + " for system app " + params.appPackageName
458                        + " upgrade");
459            }
460        }
461
462        // Now deal with non-system apps.
463        final ArraySet<String> allCandidates = new ArraySet<>();
464        VolumeInfo bestCandidate = null;
465        long bestCandidateAvailBytes = Long.MIN_VALUE;
466        for (VolumeInfo vol : storageManager.getVolumes()) {
467            boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id);
468            if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()
469                    && (!isInternalStorage || allow3rdPartyOnInternal)) {
470                final UUID target = storageManager.getUuidForPath(new File(vol.path));
471                final long availBytes = storageManager.getAllocatableBytes(target,
472                        translateAllocateFlags(params.installFlags));
473                if (availBytes >= params.sizeBytes) {
474                    allCandidates.add(vol.fsUuid);
475                }
476                if (availBytes >= bestCandidateAvailBytes) {
477                    bestCandidate = vol;
478                    bestCandidateAvailBytes = availBytes;
479                }
480            }
481        }
482
483        // If app expresses strong desire for internal storage, honor it
484        if (!forceAllowOnExternal
485                && params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
486            if (existingInfo != null && !Objects.equals(existingInfo.volumeUuid,
487                    StorageManager.UUID_PRIVATE_INTERNAL)) {
488                throw new IOException("Cannot automatically move " + params.appPackageName
489                        + " from " + existingInfo.volumeUuid + " to internal storage");
490            }
491
492            if (!allow3rdPartyOnInternal) {
493                throw new IOException("Not allowed to install non-system apps on internal storage");
494            }
495
496            if (fitsOnInternal) {
497                return StorageManager.UUID_PRIVATE_INTERNAL;
498            } else {
499                throw new IOException("Requested internal only, but not enough space");
500            }
501        }
502
503        // If app already exists somewhere, we must stay on that volume
504        if (existingInfo != null) {
505            if (Objects.equals(existingInfo.volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)
506                    && fitsOnInternal) {
507                return StorageManager.UUID_PRIVATE_INTERNAL;
508            } else if (allCandidates.contains(existingInfo.volumeUuid)) {
509                return existingInfo.volumeUuid;
510            } else {
511                throw new IOException("Not enough space on existing volume "
512                        + existingInfo.volumeUuid + " for " + params.appPackageName + " upgrade");
513            }
514        }
515
516        // We're left with new installations with either preferring external or auto, so just pick
517        // volume with most space
518        if (bestCandidate != null) {
519            return bestCandidate.fsUuid;
520        } else {
521            throw new IOException("No special requests, but no room on allowed volumes. "
522                + " allow3rdPartyOnInternal? " + allow3rdPartyOnInternal);
523        }
524    }
525
526    public static boolean fitsOnInternal(Context context, SessionParams params) throws IOException {
527        final StorageManager storage = context.getSystemService(StorageManager.class);
528        final UUID target = storage.getUuidForPath(Environment.getDataDirectory());
529        return (params.sizeBytes <= storage.getAllocatableBytes(target,
530                translateAllocateFlags(params.installFlags)));
531    }
532
533    public static boolean fitsOnExternal(Context context, SessionParams params) {
534        final StorageManager storage = context.getSystemService(StorageManager.class);
535        final StorageVolume primary = storage.getPrimaryVolume();
536        return (params.sizeBytes > 0) && !primary.isEmulated()
537                && Environment.MEDIA_MOUNTED.equals(primary.getState())
538                && params.sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
539    }
540
541    @Deprecated
542    public static int resolveInstallLocation(Context context, String packageName,
543            int installLocation, long sizeBytes, int installFlags) {
544        final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
545        params.appPackageName = packageName;
546        params.installLocation = installLocation;
547        params.sizeBytes = sizeBytes;
548        params.installFlags = installFlags;
549        try {
550            return resolveInstallLocation(context, params);
551        } catch (IOException e) {
552            throw new IllegalStateException(e);
553        }
554    }
555
556    /**
557     * Given a requested {@link PackageInfo#installLocation} and calculated
558     * install size, pick the actual location to install the app.
559     */
560    public static int resolveInstallLocation(Context context, SessionParams params)
561            throws IOException {
562        ApplicationInfo existingInfo = null;
563        try {
564            existingInfo = context.getPackageManager().getApplicationInfo(params.appPackageName,
565                    PackageManager.MATCH_ANY_USER);
566        } catch (NameNotFoundException ignored) {
567        }
568
569        final int prefer;
570        final boolean checkBoth;
571        boolean ephemeral = false;
572        if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
573            prefer = RECOMMEND_INSTALL_INTERNAL;
574            ephemeral = true;
575            checkBoth = false;
576        } else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
577            prefer = RECOMMEND_INSTALL_INTERNAL;
578            checkBoth = false;
579        } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
580            prefer = RECOMMEND_INSTALL_EXTERNAL;
581            checkBoth = false;
582        } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
583            prefer = RECOMMEND_INSTALL_INTERNAL;
584            checkBoth = false;
585        } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
586            prefer = RECOMMEND_INSTALL_EXTERNAL;
587            checkBoth = true;
588        } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
589            // When app is already installed, prefer same medium
590            if (existingInfo != null) {
591                // TODO: distinguish if this is external ASEC
592                if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
593                    prefer = RECOMMEND_INSTALL_EXTERNAL;
594                } else {
595                    prefer = RECOMMEND_INSTALL_INTERNAL;
596                }
597            } else {
598                prefer = RECOMMEND_INSTALL_INTERNAL;
599            }
600            checkBoth = true;
601        } else {
602            prefer = RECOMMEND_INSTALL_INTERNAL;
603            checkBoth = false;
604        }
605
606        boolean fitsOnInternal = false;
607        if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
608            fitsOnInternal = fitsOnInternal(context, params);
609        }
610
611        boolean fitsOnExternal = false;
612        if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
613            fitsOnExternal = fitsOnExternal(context, params);
614        }
615
616        if (prefer == RECOMMEND_INSTALL_INTERNAL) {
617            // The ephemeral case will either fit and return EPHEMERAL, or will not fit
618            // and will fall through to return INSUFFICIENT_STORAGE
619            if (fitsOnInternal) {
620                return (ephemeral)
621                        ? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL
622                        : PackageHelper.RECOMMEND_INSTALL_INTERNAL;
623            }
624        } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
625            if (fitsOnExternal) {
626                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
627            }
628        }
629
630        if (checkBoth) {
631            if (fitsOnInternal) {
632                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
633            } else if (fitsOnExternal) {
634                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
635            }
636        }
637
638        return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
639    }
640
641    public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
642            String abiOverride) throws IOException {
643        NativeLibraryHelper.Handle handle = null;
644        try {
645            handle = NativeLibraryHelper.Handle.create(pkg);
646            return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride);
647        } finally {
648            IoUtils.closeQuietly(handle);
649        }
650    }
651
652    public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle,
653            boolean isForwardLocked, String abiOverride) throws IOException {
654        long sizeBytes = 0;
655
656        // Include raw APKs, and possibly unpacked resources
657        for (String codePath : pkg.getAllCodePaths()) {
658            final File codeFile = new File(codePath);
659            sizeBytes += codeFile.length();
660
661            if (isForwardLocked) {
662                sizeBytes += PackageHelper.extractPublicFiles(codeFile, null);
663            }
664        }
665
666        // Include all relevant native code
667        sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride);
668
669        return sizeBytes;
670    }
671
672    public static String replaceEnd(String str, String before, String after) {
673        if (!str.endsWith(before)) {
674            throw new IllegalArgumentException(
675                    "Expected " + str + " to end with " + before);
676        }
677        return str.substring(0, str.length() - before.length()) + after;
678    }
679
680    public static int translateAllocateFlags(int installFlags) {
681        if ((installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0) {
682            return StorageManager.FLAG_ALLOCATE_AGGRESSIVE;
683        } else {
684            return 0;
685        }
686    }
687}
688