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