PackageHelper.java revision 77d218e1869e69c8d436b09cd11dcfe45e50b2cf
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.content;
18
19import static android.net.TrafficStats.MB_IN_BYTES;
20
21import android.content.Context;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.PackageInfo;
24import android.content.pm.PackageManager;
25import android.content.pm.PackageManager.NameNotFoundException;
26import android.content.pm.PackageParser.PackageLite;
27import android.os.Environment;
28import android.os.Environment.UserEnvironment;
29import android.os.FileUtils;
30import android.os.IBinder;
31import android.os.RemoteException;
32import android.os.ServiceManager;
33import android.os.UserHandle;
34import android.os.storage.IMountService;
35import android.os.storage.StorageManager;
36import android.os.storage.StorageResultCode;
37import android.util.Log;
38
39import libcore.io.IoUtils;
40
41import java.io.File;
42import java.io.FileOutputStream;
43import java.io.IOException;
44import java.io.InputStream;
45import java.util.Collections;
46import java.util.zip.ZipEntry;
47import java.util.zip.ZipFile;
48import java.util.zip.ZipOutputStream;
49
50/**
51 * Constants used internally between the PackageManager
52 * and media container service transports.
53 * Some utility methods to invoke MountService api.
54 */
55public class PackageHelper {
56    public static final int RECOMMEND_INSTALL_INTERNAL = 1;
57    public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
58    public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1;
59    public static final int RECOMMEND_FAILED_INVALID_APK = -2;
60    public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3;
61    public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4;
62    public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5;
63    public static final int RECOMMEND_FAILED_INVALID_URI = -6;
64    public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7;
65
66    private static final boolean localLOGV = false;
67    private static final String TAG = "PackageHelper";
68    // App installation location settings values
69    public static final int APP_INSTALL_AUTO = 0;
70    public static final int APP_INSTALL_INTERNAL = 1;
71    public static final int APP_INSTALL_EXTERNAL = 2;
72
73    public static IMountService getMountService() throws RemoteException {
74        IBinder service = ServiceManager.getService("mount");
75        if (service != null) {
76            return IMountService.Stub.asInterface(service);
77        } else {
78            Log.e(TAG, "Can't get mount service");
79            throw new RemoteException("Could not contact mount service");
80        }
81    }
82
83    public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid,
84            boolean isExternal) {
85        // Round up to nearest MB, plus another MB for filesystem overhead
86        final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
87        try {
88            IMountService mountService = getMountService();
89
90            if (localLOGV)
91                Log.i(TAG, "Size of container " + sizeMb + " MB");
92
93            int rc = mountService.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid,
94                    isExternal);
95            if (rc != StorageResultCode.OperationSucceeded) {
96                Log.e(TAG, "Failed to create secure container " + cid);
97                return null;
98            }
99            String cachePath = mountService.getSecureContainerPath(cid);
100            if (localLOGV) Log.i(TAG, "Created secure container " + cid +
101                    " at " + cachePath);
102                return cachePath;
103        } catch (RemoteException e) {
104            Log.e(TAG, "MountService running?");
105        }
106        return null;
107    }
108
109    public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) {
110        // Round up to nearest MB, plus another MB for filesystem overhead
111        final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
112        try {
113            IMountService mountService = getMountService();
114            int rc = mountService.resizeSecureContainer(cid, sizeMb, sdEncKey);
115            if (rc == StorageResultCode.OperationSucceeded) {
116                return true;
117            }
118        } catch (RemoteException e) {
119            Log.e(TAG, "MountService running?");
120        }
121        Log.e(TAG, "Failed to create secure container " + cid);
122        return false;
123    }
124
125    public static String mountSdDir(String cid, String key, int ownerUid) {
126        return mountSdDir(cid, key, ownerUid, true);
127    }
128
129    public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) {
130        try {
131            int rc = getMountService().mountSecureContainer(cid, key, ownerUid, readOnly);
132            if (rc != StorageResultCode.OperationSucceeded) {
133                Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc);
134                return null;
135            }
136            return getMountService().getSecureContainerPath(cid);
137        } catch (RemoteException e) {
138            Log.e(TAG, "MountService running?");
139        }
140        return null;
141    }
142
143   public static boolean unMountSdDir(String cid) {
144    try {
145        int rc = getMountService().unmountSecureContainer(cid, true);
146        if (rc != StorageResultCode.OperationSucceeded) {
147            Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc);
148            return false;
149        }
150        return true;
151    } catch (RemoteException e) {
152        Log.e(TAG, "MountService running?");
153    }
154        return false;
155   }
156
157   public static boolean renameSdDir(String oldId, String newId) {
158       try {
159           int rc = getMountService().renameSecureContainer(oldId, newId);
160           if (rc != StorageResultCode.OperationSucceeded) {
161               Log.e(TAG, "Failed to rename " + oldId + " to " +
162                       newId + "with rc " + rc);
163               return false;
164           }
165           return true;
166       } catch (RemoteException e) {
167           Log.i(TAG, "Failed ot rename  " + oldId + " to " + newId +
168                   " with exception : " + e);
169       }
170       return false;
171   }
172
173   public static String getSdDir(String cid) {
174       try {
175            return getMountService().getSecureContainerPath(cid);
176        } catch (RemoteException e) {
177            Log.e(TAG, "Failed to get container path for " + cid +
178                " with exception " + e);
179        }
180        return null;
181   }
182
183   public static String getSdFilesystem(String cid) {
184       try {
185            return getMountService().getSecureContainerFilesystemPath(cid);
186        } catch (RemoteException e) {
187            Log.e(TAG, "Failed to get container path for " + cid +
188                " with exception " + e);
189        }
190        return null;
191   }
192
193    public static boolean finalizeSdDir(String cid) {
194        try {
195            int rc = getMountService().finalizeSecureContainer(cid);
196            if (rc != StorageResultCode.OperationSucceeded) {
197                Log.i(TAG, "Failed to finalize container " + cid);
198                return false;
199            }
200            return true;
201        } catch (RemoteException e) {
202            Log.e(TAG, "Failed to finalize container " + cid +
203                    " with exception " + e);
204        }
205        return false;
206    }
207
208    public static boolean destroySdDir(String cid) {
209        try {
210            if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid);
211            int rc = getMountService().destroySecureContainer(cid, true);
212            if (rc != StorageResultCode.OperationSucceeded) {
213                Log.i(TAG, "Failed to destroy container " + cid);
214                return false;
215            }
216            return true;
217        } catch (RemoteException e) {
218            Log.e(TAG, "Failed to destroy container " + cid +
219                    " with exception " + e);
220        }
221        return false;
222    }
223
224    public static String[] getSecureContainerList() {
225        try {
226            return getMountService().getSecureContainerList();
227        } catch (RemoteException e) {
228            Log.e(TAG, "Failed to get secure container list with exception" +
229                    e);
230        }
231        return null;
232    }
233
234   public static boolean isContainerMounted(String cid) {
235       try {
236           return getMountService().isSecureContainerMounted(cid);
237       } catch (RemoteException e) {
238           Log.e(TAG, "Failed to find out if container " + cid + " mounted");
239       }
240       return false;
241   }
242
243    /**
244     * Extract public files for the single given APK.
245     */
246    public static long extractPublicFiles(File apkFile, File publicZipFile)
247            throws IOException {
248        final FileOutputStream fstr;
249        final ZipOutputStream publicZipOutStream;
250
251        if (publicZipFile == null) {
252            fstr = null;
253            publicZipOutStream = null;
254        } else {
255            fstr = new FileOutputStream(publicZipFile);
256            publicZipOutStream = new ZipOutputStream(fstr);
257            Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile);
258        }
259
260        long size = 0L;
261
262        try {
263            final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath());
264            try {
265                // Copy manifest, resources.arsc and res directory to public zip
266                for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) {
267                    final String zipEntryName = zipEntry.getName();
268                    if ("AndroidManifest.xml".equals(zipEntryName)
269                            || "resources.arsc".equals(zipEntryName)
270                            || zipEntryName.startsWith("res/")) {
271                        size += zipEntry.getSize();
272                        if (publicZipFile != null) {
273                            copyZipEntry(zipEntry, privateZip, publicZipOutStream);
274                        }
275                    }
276                }
277            } finally {
278                try { privateZip.close(); } catch (IOException e) {}
279            }
280
281            if (publicZipFile != null) {
282                publicZipOutStream.finish();
283                publicZipOutStream.flush();
284                FileUtils.sync(fstr);
285                publicZipOutStream.close();
286                FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR
287                        | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1);
288            }
289        } finally {
290            IoUtils.closeQuietly(publicZipOutStream);
291        }
292
293        return size;
294    }
295
296    private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile,
297            ZipOutputStream outZipStream) throws IOException {
298        byte[] buffer = new byte[4096];
299        int num;
300
301        ZipEntry newEntry;
302        if (zipEntry.getMethod() == ZipEntry.STORED) {
303            // Preserve the STORED method of the input entry.
304            newEntry = new ZipEntry(zipEntry);
305        } else {
306            // Create a new entry so that the compressed len is recomputed.
307            newEntry = new ZipEntry(zipEntry.getName());
308        }
309        outZipStream.putNextEntry(newEntry);
310
311        final InputStream data = inZipFile.getInputStream(zipEntry);
312        try {
313            while ((num = data.read(buffer)) > 0) {
314                outZipStream.write(buffer, 0, num);
315            }
316            outZipStream.flush();
317        } finally {
318            IoUtils.closeQuietly(data);
319        }
320    }
321
322    public static boolean fixSdPermissions(String cid, int gid, String filename) {
323        try {
324            int rc = getMountService().fixPermissionsSecureContainer(cid, gid, filename);
325            if (rc != StorageResultCode.OperationSucceeded) {
326                Log.i(TAG, "Failed to fixperms container " + cid);
327                return false;
328            }
329            return true;
330        } catch (RemoteException e) {
331            Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e);
332        }
333        return false;
334    }
335
336    /**
337     * Given a requested {@link PackageInfo#installLocation} and calculated
338     * install size, pick the actual location to install the app.
339     */
340    public static int resolveInstallLocation(Context context, String packageName,
341            int installLocation, long sizeBytes, int installFlags) {
342        ApplicationInfo existingInfo = null;
343        try {
344            existingInfo = context.getPackageManager().getApplicationInfo(packageName,
345                    PackageManager.GET_UNINSTALLED_PACKAGES);
346        } catch (NameNotFoundException ignored) {
347        }
348
349        final int prefer;
350        final boolean checkBoth;
351        if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
352            prefer = RECOMMEND_INSTALL_INTERNAL;
353            checkBoth = false;
354        } else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
355            prefer = RECOMMEND_INSTALL_EXTERNAL;
356            checkBoth = false;
357        } else if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
358            prefer = RECOMMEND_INSTALL_INTERNAL;
359            checkBoth = false;
360        } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
361            prefer = RECOMMEND_INSTALL_EXTERNAL;
362            checkBoth = true;
363        } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
364            // When app is already installed, prefer same medium
365            if (existingInfo != null) {
366                if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
367                    prefer = RECOMMEND_INSTALL_EXTERNAL;
368                } else {
369                    prefer = RECOMMEND_INSTALL_INTERNAL;
370                }
371            } else {
372                prefer = RECOMMEND_INSTALL_INTERNAL;
373            }
374            checkBoth = true;
375        } else {
376            prefer = RECOMMEND_INSTALL_INTERNAL;
377            checkBoth = false;
378        }
379
380        final boolean emulated = Environment.isExternalStorageEmulated();
381        final StorageManager storage = StorageManager.from(context);
382
383        boolean fitsOnInternal = false;
384        if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
385            final File target = Environment.getDataDirectory();
386            fitsOnInternal = (sizeBytes <= storage.getStorageBytesUntilLow(target));
387        }
388
389        boolean fitsOnExternal = false;
390        if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)) {
391            final File target = new UserEnvironment(UserHandle.USER_OWNER)
392                    .getExternalStorageDirectory();
393            // External is only an option when size is known
394            if (sizeBytes > 0) {
395                fitsOnExternal = (sizeBytes <= storage.getStorageBytesUntilLow(target));
396            }
397        }
398
399        if (prefer == RECOMMEND_INSTALL_INTERNAL) {
400            if (fitsOnInternal) {
401                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
402            }
403        } else if (!emulated && prefer == RECOMMEND_INSTALL_EXTERNAL) {
404            if (fitsOnExternal) {
405                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
406            }
407        }
408
409        if (checkBoth) {
410            if (fitsOnInternal) {
411                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
412            } else if (!emulated && fitsOnExternal) {
413                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
414            }
415        }
416
417        /*
418         * If they requested to be on the external media by default, return that
419         * the media was unavailable. Otherwise, indicate there was insufficient
420         * storage space available.
421         */
422        if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)
423                && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
424            return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
425        } else {
426            return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
427        }
428    }
429
430    public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
431            String abiOverride) throws IOException {
432        NativeLibraryHelper.Handle handle = null;
433        try {
434            handle = NativeLibraryHelper.Handle.create(pkg);
435            return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride);
436        } finally {
437            IoUtils.closeQuietly(handle);
438        }
439    }
440
441    public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle,
442            boolean isForwardLocked, String abiOverride) throws IOException {
443        long sizeBytes = 0;
444
445        // Include raw APKs, and possibly unpacked resources
446        for (String codePath : pkg.getAllCodePaths()) {
447            final File codeFile = new File(codePath);
448            sizeBytes += codeFile.length();
449
450            if (isForwardLocked) {
451                sizeBytes += PackageHelper.extractPublicFiles(codeFile, null);
452            }
453        }
454
455        // Include all relevant native code
456        sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride);
457
458        return sizeBytes;
459    }
460
461    public static String replaceEnd(String str, String before, String after) {
462        if (!str.endsWith(before)) {
463            throw new IllegalArgumentException(
464                    "Expected " + str + " to end with " + before);
465        }
466        return str.substring(0, str.length() - before.length()) + after;
467    }
468}
469