DefaultContainerService.java revision be520fba1e45c77ca20eb66005a0cf19e10939a1
1/*
2 * Copyright (C) 2010 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.defcontainer;
18
19import android.app.IntentService;
20import android.content.Intent;
21import android.content.pm.IPackageManager;
22import android.content.pm.PackageCleanItem;
23import android.content.pm.PackageInfo;
24import android.content.pm.PackageInfoLite;
25import android.content.pm.PackageManager;
26import android.content.pm.PackageParser;
27import android.content.pm.PackageParser.PackageLite;
28import android.content.pm.PackageParser.PackageParserException;
29import android.content.res.ObbInfo;
30import android.content.res.ObbScanner;
31import android.os.Build;
32import android.os.Environment;
33import android.os.Environment.UserEnvironment;
34import android.os.FileUtils;
35import android.os.IBinder;
36import android.os.ParcelFileDescriptor;
37import android.os.Process;
38import android.os.RemoteException;
39import android.os.ServiceManager;
40import android.os.StatFs;
41import android.provider.Settings;
42import android.system.ErrnoException;
43import android.system.Os;
44import android.system.StructStatVfs;
45import android.util.Slog;
46
47import com.android.internal.app.IMediaContainerService;
48import com.android.internal.content.NativeLibraryHelper;
49import com.android.internal.content.PackageHelper;
50import com.android.internal.os.IParcelFileDescriptorFactory;
51import com.android.internal.util.ArrayUtils;
52
53import libcore.io.IoUtils;
54import libcore.io.Streams;
55
56import java.io.File;
57import java.io.FileInputStream;
58import java.io.FileNotFoundException;
59import java.io.IOException;
60import java.io.InputStream;
61import java.io.OutputStream;
62
63/**
64 * Service that offers to inspect and copy files that may reside on removable
65 * storage. This is designed to prevent the system process from holding onto
66 * open files that cause the kernel to kill it when the underlying device is
67 * removed.
68 */
69public class DefaultContainerService extends IntentService {
70    private static final String TAG = "DefContainer";
71    private static final boolean localLOGV = false;
72
73    private static final String LIB_DIR_NAME = "lib";
74
75    // TODO: migrate native code unpacking to always be a derivative work
76
77    private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
78        /**
79         * Creates a new container and copies package there.
80         *
81         * @param packagePath absolute path to the package to be copied. Can be
82         *            a single monolithic APK file or a cluster directory
83         *            containing one or more APKs.
84         * @param containerId the id of the secure container that should be used
85         *            for creating a secure container into which the resource
86         *            will be copied.
87         * @param key Refers to key used for encrypting the secure container
88         * @return Returns the new cache path where the resource has been copied
89         *         into
90         */
91        @Override
92        public String copyPackageToContainer(String packagePath, String containerId, String key,
93                boolean isExternal, boolean isForwardLocked, String abiOverride) {
94            if (packagePath == null || containerId == null) {
95                return null;
96            }
97
98            if (isExternal) {
99                // Make sure the sdcard is mounted.
100                String status = Environment.getExternalStorageState();
101                if (!status.equals(Environment.MEDIA_MOUNTED)) {
102                    Slog.w(TAG, "Make sure sdcard is mounted.");
103                    return null;
104                }
105            }
106
107            PackageLite pkg = null;
108            NativeLibraryHelper.Handle handle = null;
109            try {
110                final File packageFile = new File(packagePath);
111                pkg = PackageParser.parsePackageLite(packageFile, 0);
112                handle = NativeLibraryHelper.Handle.create(pkg);
113                return copyPackageToContainerInner(pkg, handle, containerId, key, isExternal,
114                        isForwardLocked, abiOverride);
115            } catch (PackageParserException | IOException e) {
116                Slog.w(TAG, "Failed to parse package at " + packagePath);
117                return null;
118            } finally {
119                IoUtils.closeQuietly(handle);
120            }
121        }
122
123        /**
124         * Copy package to the target location.
125         *
126         * @param packagePath absolute path to the package to be copied. Can be
127         *            a single monolithic APK file or a cluster directory
128         *            containing one or more APKs.
129         * @return returns status code according to those in
130         *         {@link PackageManager}
131         */
132        @Override
133        public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
134            if (packagePath == null || target == null) {
135                return PackageManager.INSTALL_FAILED_INVALID_URI;
136            }
137
138            PackageLite pkg = null;
139            try {
140                final File packageFile = new File(packagePath);
141                pkg = PackageParser.parsePackageLite(packageFile, 0);
142                return copyPackageInner(pkg, target);
143            } catch (PackageParserException | IOException | RemoteException e) {
144                Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
145                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
146            }
147        }
148
149        /**
150         * Parse given package and return minimal details.
151         *
152         * @param packagePath absolute path to the package to be copied. Can be
153         *            a single monolithic APK file or a cluster directory
154         *            containing one or more APKs.
155         */
156        @Override
157        public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
158                long threshold, String abiOverride) {
159            PackageInfoLite ret = new PackageInfoLite();
160
161            if (packagePath == null) {
162                Slog.i(TAG, "Invalid package file " + packagePath);
163                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
164                return ret;
165            }
166
167            final File packageFile = new File(packagePath);
168            final PackageParser.PackageLite pkg;
169            try {
170                pkg = PackageParser.parsePackageLite(packageFile, 0);
171            } catch (PackageParserException e) {
172                Slog.w(TAG, "Failed to parse package at " + packagePath);
173
174                if (!packageFile.exists()) {
175                    ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
176                } else {
177                    ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
178                }
179
180                return ret;
181            }
182
183            ret.packageName = pkg.packageName;
184            ret.versionCode = pkg.versionCode;
185            ret.installLocation = pkg.installLocation;
186            ret.verifiers = pkg.verifiers;
187            ret.recommendedInstallLocation = recommendAppInstallLocation(pkg, flags, threshold,
188                    abiOverride);
189
190            return ret;
191        }
192
193        /**
194         * Determine if package will fit on internal storage.
195         *
196         * @param packagePath absolute path to the package to be copied. Can be
197         *            a single monolithic APK file or a cluster directory
198         *            containing one or more APKs.
199         */
200        @Override
201        public boolean checkInternalFreeStorage(String packagePath, boolean isForwardLocked,
202                long threshold) throws RemoteException {
203            final File packageFile = new File(packagePath);
204            final PackageParser.PackageLite pkg;
205            try {
206                pkg = PackageParser.parsePackageLite(packageFile, 0);
207                return isUnderInternalThreshold(pkg, isForwardLocked, threshold);
208            } catch (PackageParserException | IOException e) {
209                Slog.w(TAG, "Failed to parse package at " + packagePath);
210                return false;
211            }
212        }
213
214        /**
215         * Determine if package will fit on external storage.
216         *
217         * @param packagePath absolute path to the package to be copied. Can be
218         *            a single monolithic APK file or a cluster directory
219         *            containing one or more APKs.
220         */
221        @Override
222        public boolean checkExternalFreeStorage(String packagePath, boolean isForwardLocked,
223                String abiOverride) throws RemoteException {
224            final File packageFile = new File(packagePath);
225            final PackageParser.PackageLite pkg;
226            try {
227                pkg = PackageParser.parsePackageLite(packageFile, 0);
228                return isUnderExternalThreshold(pkg, isForwardLocked, abiOverride);
229            } catch (PackageParserException | IOException e) {
230                Slog.w(TAG, "Failed to parse package at " + packagePath);
231                return false;
232            }
233        }
234
235        @Override
236        public ObbInfo getObbInfo(String filename) {
237            try {
238                return ObbScanner.getObbInfo(filename);
239            } catch (IOException e) {
240                Slog.d(TAG, "Couldn't get OBB info for " + filename);
241                return null;
242            }
243        }
244
245        @Override
246        public long calculateDirectorySize(String path) throws RemoteException {
247            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
248
249            final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path));
250            if (dir.exists() && dir.isDirectory()) {
251                final String targetPath = dir.getAbsolutePath();
252                return MeasurementUtils.measureDirectory(targetPath);
253            } else {
254                return 0L;
255            }
256        }
257
258        @Override
259        public long[] getFileSystemStats(String path) {
260            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
261
262            try {
263                final StructStatVfs stat = Os.statvfs(path);
264                final long totalSize = stat.f_blocks * stat.f_bsize;
265                final long availSize = stat.f_bavail * stat.f_bsize;
266                return new long[] { totalSize, availSize };
267            } catch (ErrnoException e) {
268                throw new IllegalStateException(e);
269            }
270        }
271
272        @Override
273        public void clearDirectory(String path) throws RemoteException {
274            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
275
276            final File directory = new File(path);
277            if (directory.exists() && directory.isDirectory()) {
278                eraseFiles(directory);
279            }
280        }
281
282        /**
283         * Calculate estimated footprint of given package post-installation.
284         *
285         * @param packagePath absolute path to the package to be copied. Can be
286         *            a single monolithic APK file or a cluster directory
287         *            containing one or more APKs.
288         */
289        @Override
290        public long calculateInstalledSize(String packagePath, boolean isForwardLocked,
291                String abiOverride) throws RemoteException {
292            final File packageFile = new File(packagePath);
293            final PackageParser.PackageLite pkg;
294            try {
295                pkg = PackageParser.parsePackageLite(packageFile, 0);
296                return calculateContainerSize(pkg, isForwardLocked, abiOverride) * 1024 * 1024;
297            } catch (PackageParserException | IOException e) {
298                /*
299                 * Okay, something failed, so let's just estimate it to be 2x
300                 * the file size. Note this will be 0 if the file doesn't exist.
301                 */
302                return packageFile.length() * 2;
303            }
304        }
305    };
306
307    public DefaultContainerService() {
308        super("DefaultContainerService");
309        setIntentRedelivery(true);
310    }
311
312    @Override
313    protected void onHandleIntent(Intent intent) {
314        if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
315            final IPackageManager pm = IPackageManager.Stub.asInterface(
316                    ServiceManager.getService("package"));
317            PackageCleanItem item = null;
318            try {
319                while ((item = pm.nextPackageToClean(item)) != null) {
320                    final UserEnvironment userEnv = new UserEnvironment(item.userId);
321                    eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName));
322                    eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName));
323                    if (item.andCode) {
324                        eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName));
325                    }
326                }
327            } catch (RemoteException e) {
328            }
329        }
330    }
331
332    void eraseFiles(File[] paths) {
333        for (File path : paths) {
334            eraseFiles(path);
335        }
336    }
337
338    void eraseFiles(File path) {
339        if (path.isDirectory()) {
340            String[] files = path.list();
341            if (files != null) {
342                for (String file : files) {
343                    eraseFiles(new File(path, file));
344                }
345            }
346        }
347        path.delete();
348    }
349
350    @Override
351    public IBinder onBind(Intent intent) {
352        return mBinder;
353    }
354
355    private String copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle,
356            String newCid, String key, boolean isExternal, boolean isForwardLocked,
357            String abiOverride) {
358        // TODO: extend to support copying all split APKs
359        if (!ArrayUtils.isEmpty(pkg.splitNames)) {
360            throw new UnsupportedOperationException("Copying split APKs not yet supported");
361        }
362
363        final String resFileName = "pkg.apk";
364        final String publicResFileName = "res.zip";
365
366        // The .apk file
367        String codePath = pkg.baseCodePath;
368        File codeFile = new File(codePath);
369
370        String[] abiList = Build.SUPPORTED_ABIS;
371        if (abiOverride != null) {
372            abiList = new String[] { abiOverride };
373        } else {
374            try {
375                if (Build.SUPPORTED_64_BIT_ABIS.length > 0 &&
376                        NativeLibraryHelper.hasRenderscriptBitcode(handle)) {
377                    abiList = Build.SUPPORTED_32_BIT_ABIS;
378                }
379            } catch (IOException ioe) {
380                Slog.w(TAG, "Problem determining ABI for: " + codeFile.getPath());
381                return null;
382            }
383        }
384
385        final int abiIndex = NativeLibraryHelper.findSupportedAbi(handle, abiList);
386
387        // Calculate size of container needed to hold base APK.
388        final int sizeMb;
389        try {
390            sizeMb = calculateContainerSize(pkg, handle, isForwardLocked, abiIndex);
391        } catch (IOException e) {
392            Slog.w(TAG, "Problem when trying to copy " + codeFile.getPath());
393            return null;
394        }
395
396        // Create new container
397        final String newCachePath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid(),
398                isExternal);
399        if (newCachePath == null) {
400            Slog.e(TAG, "Failed to create container " + newCid);
401            return null;
402        }
403
404        if (localLOGV) {
405            Slog.i(TAG, "Created container for " + newCid + " at path : " + newCachePath);
406        }
407
408        final File resFile = new File(newCachePath, resFileName);
409        if (FileUtils.copyFile(new File(codePath), resFile)) {
410            if (localLOGV) {
411                Slog.i(TAG, "Copied " + codePath + " to " + resFile);
412            }
413        } else {
414            Slog.e(TAG, "Failed to copy " + codePath + " to " + resFile);
415            // Clean up container
416            PackageHelper.destroySdDir(newCid);
417            return null;
418        }
419
420        try {
421            Os.chmod(resFile.getAbsolutePath(), 0640);
422        } catch (ErrnoException e) {
423            Slog.e(TAG, "Could not chown APK: " + e.getMessage());
424            PackageHelper.destroySdDir(newCid);
425            return null;
426        }
427
428        if (isForwardLocked) {
429            File publicZipFile = new File(newCachePath, publicResFileName);
430            try {
431                PackageHelper.extractPublicFiles(resFile.getAbsolutePath(), publicZipFile);
432                if (localLOGV) {
433                    Slog.i(TAG, "Copied resources to " + publicZipFile);
434                }
435            } catch (IOException e) {
436                Slog.e(TAG, "Could not chown public APK " + publicZipFile.getAbsolutePath() + ": "
437                        + e.getMessage());
438                PackageHelper.destroySdDir(newCid);
439                return null;
440            }
441
442            try {
443                Os.chmod(publicZipFile.getAbsolutePath(), 0644);
444            } catch (ErrnoException e) {
445                Slog.e(TAG, "Could not chown public resource file: " + e.getMessage());
446                PackageHelper.destroySdDir(newCid);
447                return null;
448            }
449        }
450
451        final File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME);
452        if (sharedLibraryDir.mkdir()) {
453            int ret = PackageManager.INSTALL_SUCCEEDED;
454            if (abiIndex >= 0) {
455                ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle,
456                        sharedLibraryDir, abiList[abiIndex]);
457            } else if (abiIndex != PackageManager.NO_NATIVE_LIBRARIES) {
458                ret = abiIndex;
459            }
460
461            if (ret != PackageManager.INSTALL_SUCCEEDED) {
462                Slog.e(TAG, "Could not copy native libraries to " + sharedLibraryDir.getPath());
463                PackageHelper.destroySdDir(newCid);
464                return null;
465            }
466        } else {
467            Slog.e(TAG, "Could not create native lib directory: " + sharedLibraryDir.getPath());
468            PackageHelper.destroySdDir(newCid);
469            return null;
470        }
471
472        if (!PackageHelper.finalizeSdDir(newCid)) {
473            Slog.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath);
474            // Clean up container
475            PackageHelper.destroySdDir(newCid);
476            return null;
477        }
478
479        if (localLOGV) {
480            Slog.i(TAG, "Finalized container " + newCid);
481        }
482
483        if (PackageHelper.isContainerMounted(newCid)) {
484            if (localLOGV) {
485                Slog.i(TAG, "Unmounting " + newCid + " at path " + newCachePath);
486            }
487
488            // Force a gc to avoid being killed.
489            Runtime.getRuntime().gc();
490            PackageHelper.unMountSdDir(newCid);
491        } else {
492            if (localLOGV) {
493                Slog.i(TAG, "Container " + newCid + " not mounted");
494            }
495        }
496
497        return newCachePath;
498    }
499
500    private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
501            throws IOException, RemoteException {
502        // TODO: extend to support copying all split APKs
503        if (!ArrayUtils.isEmpty(pkg.splitNames)) {
504            throw new UnsupportedOperationException("Copying split APKs not yet supported");
505        }
506
507        InputStream in = null;
508        OutputStream out = null;
509        try {
510            in = new FileInputStream(pkg.baseCodePath);
511            out = new ParcelFileDescriptor.AutoCloseOutputStream(
512                    target.open(null, ParcelFileDescriptor.MODE_READ_WRITE));
513            Streams.copy(in, out);
514            return PackageManager.INSTALL_SUCCEEDED;
515        } finally {
516            IoUtils.closeQuietly(out);
517            IoUtils.closeQuietly(in);
518        }
519    }
520
521    private static final int PREFER_INTERNAL = 1;
522    private static final int PREFER_EXTERNAL = 2;
523
524    private int recommendAppInstallLocation(PackageLite pkg, int flags, long threshold,
525            String abiOverride) {
526        int prefer;
527        boolean checkBoth = false;
528
529        final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
530
531        check_inner : {
532            /*
533             * Explicit install flags should override the manifest settings.
534             */
535            if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
536                prefer = PREFER_INTERNAL;
537                break check_inner;
538            } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) {
539                prefer = PREFER_EXTERNAL;
540                break check_inner;
541            }
542
543            /* No install flags. Check for manifest option. */
544            if (pkg.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
545                prefer = PREFER_INTERNAL;
546                break check_inner;
547            } else if (pkg.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
548                prefer = PREFER_EXTERNAL;
549                checkBoth = true;
550                break check_inner;
551            } else if (pkg.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
552                // We default to preferring internal storage.
553                prefer = PREFER_INTERNAL;
554                checkBoth = true;
555                break check_inner;
556            }
557
558            // Pick user preference
559            int installPreference = Settings.Global.getInt(getApplicationContext()
560                    .getContentResolver(),
561                    Settings.Global.DEFAULT_INSTALL_LOCATION,
562                    PackageHelper.APP_INSTALL_AUTO);
563            if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) {
564                prefer = PREFER_INTERNAL;
565                break check_inner;
566            } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) {
567                prefer = PREFER_EXTERNAL;
568                break check_inner;
569            }
570
571            /*
572             * Fall back to default policy of internal-only if nothing else is
573             * specified.
574             */
575            prefer = PREFER_INTERNAL;
576        }
577
578        final boolean emulated = Environment.isExternalStorageEmulated();
579
580        boolean fitsOnInternal = false;
581        if (checkBoth || prefer == PREFER_INTERNAL) {
582            try {
583                fitsOnInternal = isUnderInternalThreshold(pkg, isForwardLocked, threshold);
584            } catch (IOException e) {
585                return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
586            }
587        }
588
589        boolean fitsOnSd = false;
590        if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) {
591            try {
592                fitsOnSd = isUnderExternalThreshold(pkg, isForwardLocked, abiOverride);
593            } catch (IOException e) {
594                return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
595            }
596        }
597
598        if (prefer == PREFER_INTERNAL) {
599            if (fitsOnInternal) {
600                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
601            }
602        } else if (!emulated && prefer == PREFER_EXTERNAL) {
603            if (fitsOnSd) {
604                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
605            }
606        }
607
608        if (checkBoth) {
609            if (fitsOnInternal) {
610                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
611            } else if (!emulated && fitsOnSd) {
612                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
613            }
614        }
615
616        /*
617         * If they requested to be on the external media by default, return that
618         * the media was unavailable. Otherwise, indicate there was insufficient
619         * storage space available.
620         */
621        if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)
622                && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
623            return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
624        } else {
625            return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
626        }
627    }
628
629    /**
630     * Measure a file to see if it fits within the free space threshold.
631     *
632     * @param threshold byte threshold to compare against
633     * @return true if file fits under threshold
634     * @throws FileNotFoundException when APK does not exist
635     */
636    private boolean isUnderInternalThreshold(PackageLite pkg, boolean isForwardLocked,
637            long threshold) throws IOException {
638        long sizeBytes = 0;
639        for (String codePath : pkg.getAllCodePaths()) {
640            sizeBytes += new File(codePath).length();
641
642            if (isForwardLocked) {
643                sizeBytes += PackageHelper.extractPublicFiles(codePath, null);
644            }
645        }
646
647        final StatFs stat = new StatFs(Environment.getDataDirectory().getPath());
648        final long availBytes = stat.getAvailableBytes();
649        return (availBytes - sizeBytes) > threshold;
650    }
651
652    /**
653     * Measure a file to see if it fits in the external free space.
654     *
655     * @return true if file fits
656     * @throws IOException when file does not exist
657     */
658    private boolean isUnderExternalThreshold(PackageLite pkg, boolean isForwardLocked,
659            String abiOverride) throws IOException {
660        if (Environment.isExternalStorageEmulated()) {
661            return false;
662        }
663
664        final int sizeMb = calculateContainerSize(pkg, isForwardLocked, abiOverride);
665
666        final int availSdMb;
667        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
668            final StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
669            final int blocksToMb = (1 << 20) / sdStats.getBlockSize();
670            availSdMb = sdStats.getAvailableBlocks() * blocksToMb;
671        } else {
672            availSdMb = -1;
673        }
674
675        return availSdMb > sizeMb;
676    }
677
678    private int calculateContainerSize(PackageLite pkg, boolean isForwardLocked, String abiOverride)
679            throws IOException {
680        NativeLibraryHelper.Handle handle = null;
681        try {
682            handle = NativeLibraryHelper.Handle.create(pkg);
683            final int abi = NativeLibraryHelper.findSupportedAbi(handle,
684                    (abiOverride != null) ? new String[] { abiOverride } : Build.SUPPORTED_ABIS);
685            return calculateContainerSize(pkg, handle, isForwardLocked, abi);
686        } finally {
687            IoUtils.closeQuietly(handle);
688        }
689    }
690
691    /**
692     * Calculate the container size for a package.
693     *
694     * @return size in megabytes (2^20 bytes)
695     * @throws IOException when there is a problem reading the file
696     */
697    private int calculateContainerSize(PackageLite pkg, NativeLibraryHelper.Handle handle,
698            boolean isForwardLocked, int abiIndex) throws IOException {
699        // Calculate size of container needed to hold APKs.
700        long sizeBytes = 0;
701        for (String codePath : pkg.getAllCodePaths()) {
702            sizeBytes += new File(codePath).length();
703
704            if (isForwardLocked) {
705                sizeBytes += PackageHelper.extractPublicFiles(codePath, null);
706            }
707        }
708
709        // Check all the native files that need to be copied and add that to the
710        // container size.
711        if (abiIndex >= 0) {
712            sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(handle,
713                    Build.SUPPORTED_ABIS[abiIndex]);
714        }
715
716        int sizeMb = (int) (sizeBytes >> 20);
717        if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) {
718            sizeMb++;
719        }
720
721        /*
722         * Add buffer size because we don't have a good way to determine the
723         * real FAT size. Your FAT size varies with how many directory entries
724         * you need, how big the whole filesystem is, and other such headaches.
725         */
726        sizeMb++;
727
728        return sizeMb;
729    }
730}
731