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