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 
17 package com.android.internal.content;
18 
19 import static android.net.TrafficStats.MB_IN_BYTES;
20 
21 import android.content.Context;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.PackageManager.NameNotFoundException;
26 import android.content.pm.PackageParser.PackageLite;
27 import android.os.Environment;
28 import android.os.Environment.UserEnvironment;
29 import android.os.FileUtils;
30 import android.os.IBinder;
31 import android.os.RemoteException;
32 import android.os.ServiceManager;
33 import android.os.UserHandle;
34 import android.os.storage.IMountService;
35 import android.os.storage.StorageManager;
36 import android.os.storage.StorageResultCode;
37 import android.util.Log;
38 
39 import libcore.io.IoUtils;
40 
41 import java.io.File;
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.util.Collections;
46 import java.util.zip.ZipEntry;
47 import java.util.zip.ZipFile;
48 import 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  */
55 public 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 
getMountService()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 
createSdDir(long sizeBytes, String cid, String sdEncKey, int uid, boolean isExternal)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 
resizeSdDir(long sizeBytes, String cid, String sdEncKey)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 
mountSdDir(String cid, String key, int ownerUid)125     public static String mountSdDir(String cid, String key, int ownerUid) {
126         return mountSdDir(cid, key, ownerUid, true);
127     }
128 
mountSdDir(String cid, String key, int ownerUid, boolean readOnly)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 
unMountSdDir(String cid)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 
renameSdDir(String oldId, String newId)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 
getSdDir(String cid)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 
getSdFilesystem(String cid)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 
finalizeSdDir(String cid)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 
destroySdDir(String cid)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 
getSecureContainerList()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 
isContainerMounted(String cid)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      */
extractPublicFiles(File apkFile, File publicZipFile)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 
copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile, ZipOutputStream outZipStream)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 
fixSdPermissions(String cid, int gid, String filename)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      */
resolveInstallLocation(Context context, String packageName, int installLocation, long sizeBytes, int installFlags)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 
calculateInstalledSize(PackageLite pkg, boolean isForwardLocked, String abiOverride)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 
calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle, boolean isForwardLocked, String abiOverride)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 
replaceEnd(String str, String before, String after)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