DefaultContainerService.java revision 40e472521a544f26cb6956995788f7c36fff1404
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 com.android.internal.app.IMediaContainerService;
20import com.android.internal.content.PackageHelper;
21import android.content.Intent;
22import android.content.pm.IPackageManager;
23import android.content.pm.PackageInfo;
24import android.content.pm.PackageInfoLite;
25import android.content.pm.PackageManager;
26import android.content.pm.PackageParser;
27import android.net.Uri;
28import android.os.Environment;
29import android.os.IBinder;
30import android.os.ParcelFileDescriptor;
31import android.os.Process;
32import android.os.RemoteException;
33import android.os.ServiceManager;
34import android.os.StatFs;
35import android.app.IntentService;
36import android.util.DisplayMetrics;
37import android.util.Log;
38
39import java.io.File;
40import java.io.FileInputStream;
41import java.io.FileNotFoundException;
42import java.io.FileOutputStream;
43import java.io.IOException;
44import java.io.InputStream;
45
46import android.os.FileUtils;
47import android.provider.Settings;
48
49/*
50 * This service copies a downloaded apk to a file passed in as
51 * a ParcelFileDescriptor or to a newly created container specified
52 * by parameters. The DownloadManager gives access to this process
53 * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER
54 * permission to access apks downloaded via the download manager.
55 */
56public class DefaultContainerService extends IntentService {
57    private static final String TAG = "DefContainer";
58    private static final boolean localLOGV = true;
59
60    private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
61        /*
62         * Creates a new container and copies resource there.
63         * @param paackageURI the uri of resource to be copied. Can be either
64         * a content uri or a file uri
65         * @param cid the id of the secure container that should
66         * be used for creating a secure container into which the resource
67         * will be copied.
68         * @param key Refers to key used for encrypting the secure container
69         * @param resFileName Name of the target resource file(relative to newly
70         * created secure container)
71         * @return Returns the new cache path where the resource has been copied into
72         *
73         */
74        public String copyResourceToContainer(final Uri packageURI,
75                final String cid,
76                final String key, final String resFileName) {
77            if (packageURI == null || cid == null) {
78                return null;
79            }
80            return copyResourceInner(packageURI, cid, key, resFileName);
81        }
82
83        /*
84         * Copy specified resource to output stream
85         * @param packageURI the uri of resource to be copied. Should be a
86         * file uri
87         * @param outStream Remote file descriptor to be used for copying
88         * @return Returns true if copy succeded or false otherwise.
89         */
90        public boolean copyResource(final Uri packageURI,
91                ParcelFileDescriptor outStream) {
92            if (packageURI == null ||  outStream == null) {
93                return false;
94            }
95            ParcelFileDescriptor.AutoCloseOutputStream
96            autoOut = new ParcelFileDescriptor.AutoCloseOutputStream(outStream);
97            return copyFile(packageURI, autoOut);
98        }
99
100        /*
101         * Determine the recommended install location for package
102         * specified by file uri location.
103         * @param fileUri the uri of resource to be copied. Should be a
104         * file uri
105         * @return Returns PackageInfoLite object containing
106         * the package info and recommended app location.
107         */
108        public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags) {
109            PackageInfoLite ret = new PackageInfoLite();
110            if (fileUri == null) {
111                Log.i(TAG, "Invalid package uri " + fileUri);
112                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
113                return ret;
114            }
115            String scheme = fileUri.getScheme();
116            if (scheme != null && !scheme.equals("file")) {
117                Log.w(TAG, "Falling back to installing on internal storage only");
118                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_INSTALL_INTERNAL;
119                return ret;
120            }
121            String archiveFilePath = fileUri.getPath();
122            PackageParser packageParser = new PackageParser(archiveFilePath);
123            File sourceFile = new File(archiveFilePath);
124            DisplayMetrics metrics = new DisplayMetrics();
125            metrics.setToDefaults();
126            PackageParser.PackageLite pkg = packageParser.parsePackageLite(
127                    archiveFilePath, 0);
128            ret.packageName = pkg.packageName;
129            ret.installLocation = pkg.installLocation;
130            // Nuke the parser reference right away and force a gc
131            packageParser = null;
132            Runtime.getRuntime().gc();
133            if (pkg == null) {
134                Log.w(TAG, "Failed to parse package");
135                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
136                return ret;
137            }
138            ret.packageName = pkg.packageName;
139            ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, archiveFilePath, flags);
140            return ret;
141        }
142
143        public boolean checkFreeStorage(boolean external, Uri fileUri) {
144            return checkFreeStorageInner(external, fileUri);
145        }
146    };
147
148    public DefaultContainerService() {
149        super("DefaultContainerService");
150        setIntentRedelivery(true);
151    }
152
153    @Override
154    protected void onHandleIntent(Intent intent) {
155        if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
156            IPackageManager pm = IPackageManager.Stub.asInterface(
157                    ServiceManager.getService("package"));
158            String pkg = null;
159            try {
160                while ((pkg=pm.nextPackageToClean(pkg)) != null) {
161                    eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg));
162                    eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg));
163                }
164            } catch (RemoteException e) {
165            }
166        }
167    }
168
169    void eraseFiles(File path) {
170        if (path.isDirectory()) {
171            String[] files = path.list();
172            if (files != null) {
173                for (String file : files) {
174                    eraseFiles(new File(path, file));
175                }
176            }
177        }
178        path.delete();
179    }
180
181    public IBinder onBind(Intent intent) {
182        return mBinder;
183    }
184
185    private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName) {
186        // Make sure the sdcard is mounted.
187        String status = Environment.getExternalStorageState();
188        if (!status.equals(Environment.MEDIA_MOUNTED)) {
189            Log.w(TAG, "Make sure sdcard is mounted.");
190            return null;
191        }
192        // Create new container at newCachePath
193        String codePath = packageURI.getPath();
194        File codeFile = new File(codePath);
195        String newCachePath = null;
196        // Create new container
197        if ((newCachePath = PackageHelper.createSdDir(codeFile,
198                newCid, key, Process.myUid())) == null) {
199            Log.e(TAG, "Failed to create container " + newCid);
200            return null;
201        }
202        if (localLOGV) Log.i(TAG, "Created container for " + newCid
203                + " at path : " + newCachePath);
204        File resFile = new File(newCachePath, resFileName);
205        if (!FileUtils.copyFile(new File(codePath), resFile)) {
206            Log.e(TAG, "Failed to copy " + codePath + " to " + resFile);
207            // Clean up container
208            PackageHelper.destroySdDir(newCid);
209            return null;
210        }
211        if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile);
212        if (!PackageHelper.finalizeSdDir(newCid)) {
213            Log.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath);
214            // Clean up container
215            PackageHelper.destroySdDir(newCid);
216        }
217        if (localLOGV) Log.i(TAG, "Finalized container " + newCid);
218        if (PackageHelper.isContainerMounted(newCid)) {
219            if (localLOGV) Log.i(TAG, "Unmounting " + newCid +
220                    " at path " + newCachePath);
221            // Force a gc to avoid being killed.
222            Runtime.getRuntime().gc();
223            PackageHelper.unMountSdDir(newCid);
224        } else {
225            if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted");
226        }
227        return newCachePath;
228    }
229
230    public static boolean copyToFile(InputStream inputStream, FileOutputStream out) {
231        try {
232            byte[] buffer = new byte[4096];
233            int bytesRead;
234            while ((bytesRead = inputStream.read(buffer)) >= 0) {
235                out.write(buffer, 0, bytesRead);
236            }
237            return true;
238        } catch (IOException e) {
239            Log.i(TAG, "Exception : " + e + " when copying file");
240            return false;
241        }
242    }
243
244    public static boolean copyToFile(File srcFile, FileOutputStream out) {
245        InputStream inputStream = null;
246        try {
247            inputStream = new FileInputStream(srcFile);
248            return copyToFile(inputStream, out);
249        } catch (IOException e) {
250            return false;
251        } finally {
252            try { if (inputStream != null) inputStream.close(); } catch (IOException e) {}
253        }
254    }
255
256    private  boolean copyFile(Uri pPackageURI, FileOutputStream outStream) {
257        String scheme = pPackageURI.getScheme();
258        if (scheme == null || scheme.equals("file")) {
259            final File srcPackageFile = new File(pPackageURI.getPath());
260            // We copy the source package file to a temp file and then rename it to the
261            // destination file in order to eliminate a window where the package directory
262            // scanner notices the new package file but it's not completely copied yet.
263            if (!copyToFile(srcPackageFile, outStream)) {
264                Log.e(TAG, "Couldn't copy file: " + srcPackageFile);
265                return false;
266            }
267        } else if (scheme.equals("content")) {
268            ParcelFileDescriptor fd = null;
269            try {
270                fd = getContentResolver().openFileDescriptor(pPackageURI, "r");
271            } catch (FileNotFoundException e) {
272                Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e);
273                return false;
274            }
275            if (fd == null) {
276                Log.e(TAG, "Couldn't open file descriptor from download service (null).");
277                return false;
278            } else {
279                if (localLOGV) {
280                    Log.v(TAG, "Opened file descriptor from download service.");
281                }
282                ParcelFileDescriptor.AutoCloseInputStream
283                dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd);
284                // We copy the source package file to a temp file and then rename it to the
285                // destination file in order to eliminate a window where the package directory
286                // scanner notices the new package file but it's not completely copied yet.
287                if (!copyToFile(dlStream, outStream)) {
288                    Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file.");
289                    return false;
290                }
291            }
292        } else {
293            Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI);
294            return false;
295        }
296        return true;
297    }
298
299    // Constants related to app heuristics
300    // No-installation limit for internal flash: 10% or less space available
301    private static final double LOW_NAND_FLASH_TRESHOLD = 0.1;
302
303    // SD-to-internal app size threshold: currently set to 1 MB
304    private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024);
305    private static final int ERR_LOC = -1;
306
307    private int recommendAppInstallLocation(int installLocation,
308            String archiveFilePath, int flags) {
309        boolean checkInt = false;
310        boolean checkExt = false;
311        boolean checkBoth = false;
312        check_inner : {
313            // Check flags.
314            if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) {
315                // Check for forward locked app
316                checkInt = true;
317                break check_inner;
318            } else if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
319                // Explicit flag to install internally.
320                // Check internal storage and return
321                checkInt = true;
322                break check_inner;
323            } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) {
324                // Explicit flag to install externally.
325                // Check external storage and return
326                checkExt = true;
327                break check_inner;
328            }
329            // Check for manifest option
330            if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
331                checkInt = true;
332                break check_inner;
333            } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
334                checkExt = true;
335                checkBoth = true;
336                break check_inner;
337            } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
338                checkInt = true;
339                checkBoth = true;
340                break check_inner;
341            }
342            // Pick user preference
343            int installPreference = Settings.System.getInt(getApplicationContext()
344                    .getContentResolver(),
345                    Settings.Secure.DEFAULT_INSTALL_LOCATION,
346                    PackageHelper.APP_INSTALL_AUTO);
347            if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) {
348                checkInt = true;
349                checkBoth = true;
350                break check_inner;
351            } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) {
352                checkExt = true;
353                checkBoth = true;
354                break check_inner;
355            }
356            // Fall back to default policy if nothing else is specified.
357            checkInt = true;
358            checkBoth = true;
359        }
360
361        // Package size = code size + cache size + data size
362        // If code size > 1 MB, install on SD card.
363        // Else install on internal NAND flash, unless space on NAND is less than 10%
364        String status = Environment.getExternalStorageState();
365        long availSDSize = -1;
366        boolean mediaAvailable = false;
367        if (status.equals(Environment.MEDIA_MOUNTED)) {
368            StatFs sdStats = new StatFs(
369                    Environment.getExternalStorageDirectory().getPath());
370            availSDSize = (long)sdStats.getAvailableBlocks() *
371                    (long)sdStats.getBlockSize();
372            mediaAvailable = true;
373        }
374        StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
375        long totalInternalSize = (long)internalStats.getBlockCount() *
376                (long)internalStats.getBlockSize();
377        long availInternalSize = (long)internalStats.getAvailableBlocks() *
378                (long)internalStats.getBlockSize();
379
380        double pctNandFree = (double)availInternalSize / (double)totalInternalSize;
381
382        File apkFile = new File(archiveFilePath);
383        long pkgLen = apkFile.length();
384
385        // To make final copy
386        long reqInstallSize = pkgLen;
387        // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now.
388        long reqInternalSize = 0;
389        boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD);
390        boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize);
391        boolean fitsOnSd = false;
392        if (mediaAvailable && (reqInstallSize < availSDSize)) {
393            // If we do not have an internal size requirement
394            // don't do a threshold check.
395            if (reqInternalSize == 0) {
396                fitsOnSd = true;
397            } else if ((reqInternalSize < availInternalSize) && intThresholdOk) {
398                fitsOnSd = true;
399            }
400        }
401        boolean fitsOnInt = intThresholdOk && intAvailOk;
402        if (checkInt) {
403            // Check for internal memory availability
404            if (fitsOnInt) {
405                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
406            }
407        } else if (checkExt) {
408            if (fitsOnSd) {
409                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
410            }
411        }
412        if (checkBoth) {
413            // Check for internal first
414            if (fitsOnInt) {
415                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
416            }
417            // Check for external next
418            if (fitsOnSd) {
419                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
420            }
421        }
422        if (checkExt || checkBoth && !mediaAvailable) {
423            return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
424        }
425        return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
426    }
427
428    private boolean checkFreeStorageInner(boolean external, Uri packageURI) {
429        File apkFile = new File(packageURI.getPath());
430        long size = apkFile.length();
431        if (external) {
432            String status = Environment.getExternalStorageState();
433            long availSDSize = -1;
434            if (status.equals(Environment.MEDIA_MOUNTED)) {
435                StatFs sdStats = new StatFs(
436                        Environment.getExternalStorageDirectory().getPath());
437                availSDSize = (long)sdStats.getAvailableBlocks() *
438                (long)sdStats.getBlockSize();
439            }
440            return availSDSize > size;
441        }
442        StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
443        long totalInternalSize = (long)internalStats.getBlockCount() *
444        (long)internalStats.getBlockSize();
445        long availInternalSize = (long)internalStats.getAvailableBlocks() *
446        (long)internalStats.getBlockSize();
447
448        double pctNandFree = (double)availInternalSize / (double)totalInternalSize;
449        // To make final copy
450        long reqInstallSize = size;
451        // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now.
452        long reqInternalSize = 0;
453        boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD);
454        boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize);
455        return intThresholdOk && intAvailOk;
456    }
457}
458