blob: b04ddf4eab8e4d7bc5a27715aaa026ad377945bd [file] [log] [blame]
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -08001/*
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
Jeff Sharkey941a8ba2014-08-20 16:26:32 -070019import static android.net.TrafficStats.MB_IN_BYTES;
20
Jeff Sharkey742e7902014-08-16 19:09:13 -070021import android.content.Context;
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -070022import android.content.pm.ApplicationInfo;
Jeff Sharkey742e7902014-08-16 19:09:13 -070023import android.content.pm.PackageInfo;
24import android.content.pm.PackageManager;
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -070025import android.content.pm.PackageManager.NameNotFoundException;
Jeff Sharkey941a8ba2014-08-20 16:26:32 -070026import android.content.pm.PackageParser.PackageLite;
Jeff Sharkey742e7902014-08-16 19:09:13 -070027import android.os.Environment;
Kenny Root2782a472012-04-14 21:48:21 -070028import android.os.FileUtils;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080029import android.os.IBinder;
30import android.os.RemoteException;
31import android.os.ServiceManager;
Kenny Root2782a472012-04-14 21:48:21 -070032import android.os.storage.IMountService;
Jeff Sharkey742e7902014-08-16 19:09:13 -070033import android.os.storage.StorageManager;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080034import android.os.storage.StorageResultCode;
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -070035import android.os.storage.StorageVolume;
36import android.os.storage.VolumeInfo;
37import android.util.ArraySet;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080038import android.util.Log;
39
Jeff Sharkey742e7902014-08-16 19:09:13 -070040import libcore.io.IoUtils;
41
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080042import java.io.File;
Kenny Root2782a472012-04-14 21:48:21 -070043import java.io.FileOutputStream;
44import java.io.IOException;
45import java.io.InputStream;
46import java.util.Collections;
47import java.util.zip.ZipEntry;
48import java.util.zip.ZipFile;
49import java.util.zip.ZipOutputStream;
50
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080051/**
52 * Constants used internally between the PackageManager
53 * and media container service transports.
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080054 * Some utility methods to invoke MountService api.
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080055 */
56public class PackageHelper {
57 public static final int RECOMMEND_INSTALL_INTERNAL = 1;
58 public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
59 public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1;
60 public static final int RECOMMEND_FAILED_INVALID_APK = -2;
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -080061 public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3;
62 public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4;
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -080063 public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5;
Kenny Root1ebd74a2011-08-03 15:09:44 -070064 public static final int RECOMMEND_FAILED_INVALID_URI = -6;
Dianne Hackborn7767eac2012-08-23 18:25:40 -070065 public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7;
Kenny Root1ebd74a2011-08-03 15:09:44 -070066
Dianne Hackborn40e9f292012-11-27 19:12:23 -080067 private static final boolean localLOGV = false;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080068 private static final String TAG = "PackageHelper";
Suchi Amalapurapu089262d2010-03-10 14:19:21 -080069 // App installation location settings values
70 public static final int APP_INSTALL_AUTO = 0;
71 public static final int APP_INSTALL_INTERNAL = 1;
72 public static final int APP_INSTALL_EXTERNAL = 2;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080073
Kenny Root07ba2ae2012-05-09 09:14:28 -070074 public static IMountService getMountService() throws RemoteException {
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080075 IBinder service = ServiceManager.getService("mount");
76 if (service != null) {
77 return IMountService.Stub.asInterface(service);
78 } else {
79 Log.e(TAG, "Can't get mount service");
Kenny Root07ba2ae2012-05-09 09:14:28 -070080 throw new RemoteException("Could not contact mount service");
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080081 }
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080082 }
83
Jeff Sharkey941a8ba2014-08-20 16:26:32 -070084 public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid,
Kenny Root6dceb882012-04-12 14:23:49 -070085 boolean isExternal) {
Jeff Sharkey941a8ba2014-08-20 16:26:32 -070086 // Round up to nearest MB, plus another MB for filesystem overhead
87 final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080088 try {
Kenny Root07ba2ae2012-05-09 09:14:28 -070089 IMountService mountService = getMountService();
90
91 if (localLOGV)
92 Log.i(TAG, "Size of container " + sizeMb + " MB");
93
Kenny Root6dceb882012-04-12 14:23:49 -070094 int rc = mountService.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid,
95 isExternal);
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080096 if (rc != StorageResultCode.OperationSucceeded) {
97 Log.e(TAG, "Failed to create secure container " + cid);
98 return null;
99 }
100 String cachePath = mountService.getSecureContainerPath(cid);
Suchi Amalapurapucf6eaea2010-02-23 19:37:45 -0800101 if (localLOGV) Log.i(TAG, "Created secure container " + cid +
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800102 " at " + cachePath);
103 return cachePath;
104 } catch (RemoteException e) {
105 Log.e(TAG, "MountService running?");
106 }
107 return null;
108 }
109
Jeff Sharkey941a8ba2014-08-20 16:26:32 -0700110 public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) {
111 // Round up to nearest MB, plus another MB for filesystem overhead
112 final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
113 try {
114 IMountService mountService = getMountService();
115 int rc = mountService.resizeSecureContainer(cid, sizeMb, sdEncKey);
116 if (rc == StorageResultCode.OperationSucceeded) {
117 return true;
118 }
119 } catch (RemoteException e) {
120 Log.e(TAG, "MountService running?");
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800121 }
Jeff Sharkey941a8ba2014-08-20 16:26:32 -0700122 Log.e(TAG, "Failed to create secure container " + cid);
123 return false;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800124 }
Jeff Sharkey941a8ba2014-08-20 16:26:32 -0700125
126 public static String mountSdDir(String cid, String key, int ownerUid) {
127 return mountSdDir(cid, key, ownerUid, true);
128 }
129
130 public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) {
131 try {
132 int rc = getMountService().mountSecureContainer(cid, key, ownerUid, readOnly);
133 if (rc != StorageResultCode.OperationSucceeded) {
134 Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc);
135 return null;
136 }
137 return getMountService().getSecureContainerPath(cid);
138 } catch (RemoteException e) {
139 Log.e(TAG, "MountService running?");
140 }
141 return null;
142 }
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800143
144 public static boolean unMountSdDir(String cid) {
145 try {
Suchi Amalapurapucf6eaea2010-02-23 19:37:45 -0800146 int rc = getMountService().unmountSecureContainer(cid, true);
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800147 if (rc != StorageResultCode.OperationSucceeded) {
148 Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc);
149 return false;
150 }
151 return true;
152 } catch (RemoteException e) {
153 Log.e(TAG, "MountService running?");
154 }
155 return false;
156 }
157
158 public static boolean renameSdDir(String oldId, String newId) {
159 try {
160 int rc = getMountService().renameSecureContainer(oldId, newId);
161 if (rc != StorageResultCode.OperationSucceeded) {
162 Log.e(TAG, "Failed to rename " + oldId + " to " +
163 newId + "with rc " + rc);
164 return false;
165 }
166 return true;
167 } catch (RemoteException e) {
168 Log.i(TAG, "Failed ot rename " + oldId + " to " + newId +
169 " with exception : " + e);
170 }
171 return false;
172 }
173
174 public static String getSdDir(String cid) {
175 try {
176 return getMountService().getSecureContainerPath(cid);
177 } catch (RemoteException e) {
178 Log.e(TAG, "Failed to get container path for " + cid +
179 " with exception " + e);
180 }
181 return null;
182 }
183
Dianne Hackborn292f8bc2011-06-27 16:27:41 -0700184 public static String getSdFilesystem(String cid) {
185 try {
186 return getMountService().getSecureContainerFilesystemPath(cid);
187 } catch (RemoteException e) {
188 Log.e(TAG, "Failed to get container path for " + cid +
189 " with exception " + e);
190 }
191 return null;
192 }
193
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800194 public static boolean finalizeSdDir(String cid) {
195 try {
196 int rc = getMountService().finalizeSecureContainer(cid);
197 if (rc != StorageResultCode.OperationSucceeded) {
198 Log.i(TAG, "Failed to finalize container " + cid);
199 return false;
200 }
201 return true;
202 } catch (RemoteException e) {
203 Log.e(TAG, "Failed to finalize container " + cid +
204 " with exception " + e);
205 }
206 return false;
207 }
208
209 public static boolean destroySdDir(String cid) {
210 try {
Suchi Amalapurapucf6eaea2010-02-23 19:37:45 -0800211 if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid);
212 int rc = getMountService().destroySecureContainer(cid, true);
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800213 if (rc != StorageResultCode.OperationSucceeded) {
214 Log.i(TAG, "Failed to destroy container " + cid);
215 return false;
216 }
217 return true;
218 } catch (RemoteException e) {
219 Log.e(TAG, "Failed to destroy container " + cid +
220 " with exception " + e);
221 }
222 return false;
223 }
224
225 public static String[] getSecureContainerList() {
226 try {
227 return getMountService().getSecureContainerList();
228 } catch (RemoteException e) {
229 Log.e(TAG, "Failed to get secure container list with exception" +
230 e);
231 }
232 return null;
233 }
234
235 public static boolean isContainerMounted(String cid) {
236 try {
237 return getMountService().isSecureContainerMounted(cid);
238 } catch (RemoteException e) {
239 Log.e(TAG, "Failed to find out if container " + cid + " mounted");
240 }
241 return false;
242 }
Kenny Root2782a472012-04-14 21:48:21 -0700243
Jeff Sharkeybe520fb2014-07-04 18:23:17 -0700244 /**
245 * Extract public files for the single given APK.
246 */
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700247 public static long extractPublicFiles(File apkFile, File publicZipFile)
Kenny Root2782a472012-04-14 21:48:21 -0700248 throws IOException {
Kenny Root6dceb882012-04-12 14:23:49 -0700249 final FileOutputStream fstr;
250 final ZipOutputStream publicZipOutStream;
251
252 if (publicZipFile == null) {
253 fstr = null;
254 publicZipOutStream = null;
255 } else {
256 fstr = new FileOutputStream(publicZipFile);
257 publicZipOutStream = new ZipOutputStream(fstr);
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700258 Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile);
Kenny Root6dceb882012-04-12 14:23:49 -0700259 }
260
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700261 long size = 0L;
Kenny Root6dceb882012-04-12 14:23:49 -0700262
Kenny Root2782a472012-04-14 21:48:21 -0700263 try {
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700264 final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath());
Kenny Root2782a472012-04-14 21:48:21 -0700265 try {
266 // Copy manifest, resources.arsc and res directory to public zip
267 for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) {
268 final String zipEntryName = zipEntry.getName();
269 if ("AndroidManifest.xml".equals(zipEntryName)
270 || "resources.arsc".equals(zipEntryName)
271 || zipEntryName.startsWith("res/")) {
Kenny Root6dceb882012-04-12 14:23:49 -0700272 size += zipEntry.getSize();
273 if (publicZipFile != null) {
274 copyZipEntry(zipEntry, privateZip, publicZipOutStream);
275 }
Kenny Root2782a472012-04-14 21:48:21 -0700276 }
277 }
278 } finally {
Kenny Root6dceb882012-04-12 14:23:49 -0700279 try { privateZip.close(); } catch (IOException e) {}
Kenny Root2782a472012-04-14 21:48:21 -0700280 }
281
Kenny Root6dceb882012-04-12 14:23:49 -0700282 if (publicZipFile != null) {
283 publicZipOutStream.finish();
284 publicZipOutStream.flush();
285 FileUtils.sync(fstr);
286 publicZipOutStream.close();
287 FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR
288 | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1);
289 }
Kenny Root2782a472012-04-14 21:48:21 -0700290 } finally {
291 IoUtils.closeQuietly(publicZipOutStream);
292 }
Kenny Root6dceb882012-04-12 14:23:49 -0700293
294 return size;
Kenny Root2782a472012-04-14 21:48:21 -0700295 }
296
297 private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile,
298 ZipOutputStream outZipStream) throws IOException {
299 byte[] buffer = new byte[4096];
300 int num;
301
302 ZipEntry newEntry;
303 if (zipEntry.getMethod() == ZipEntry.STORED) {
304 // Preserve the STORED method of the input entry.
305 newEntry = new ZipEntry(zipEntry);
306 } else {
307 // Create a new entry so that the compressed len is recomputed.
308 newEntry = new ZipEntry(zipEntry.getName());
309 }
310 outZipStream.putNextEntry(newEntry);
311
312 final InputStream data = inZipFile.getInputStream(zipEntry);
313 try {
314 while ((num = data.read(buffer)) > 0) {
315 outZipStream.write(buffer, 0, num);
316 }
317 outZipStream.flush();
318 } finally {
319 IoUtils.closeQuietly(data);
320 }
321 }
Kenny Root6dceb882012-04-12 14:23:49 -0700322
323 public static boolean fixSdPermissions(String cid, int gid, String filename) {
324 try {
325 int rc = getMountService().fixPermissionsSecureContainer(cid, gid, filename);
326 if (rc != StorageResultCode.OperationSucceeded) {
327 Log.i(TAG, "Failed to fixperms container " + cid);
328 return false;
329 }
330 return true;
331 } catch (RemoteException e) {
332 Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e);
333 }
334 return false;
335 }
Jeff Sharkey742e7902014-08-16 19:09:13 -0700336
337 /**
338 * Given a requested {@link PackageInfo#installLocation} and calculated
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700339 * install size, pick the actual volume to install the app. Only considers
340 * internal and private volumes, and prefers to keep an existing package on
341 * its current volume.
342 *
343 * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
344 * for internal storage.
345 */
346 public static String resolveInstallVolume(Context context, String packageName,
347 int installLocation, long sizeBytes) throws IOException {
348 // TODO: handle existing apps installed in ASEC; currently assumes
349 // they'll end up back on internal storage
350 ApplicationInfo existingInfo = null;
351 try {
352 existingInfo = context.getPackageManager().getApplicationInfo(packageName,
353 PackageManager.GET_UNINSTALLED_PACKAGES);
354 } catch (NameNotFoundException ignored) {
355 }
356
357 final StorageManager storageManager = context.getSystemService(StorageManager.class);
358 final boolean fitsOnInternal = fitsOnInternal(context, sizeBytes);
359
360 final ArraySet<String> allCandidates = new ArraySet<>();
361 VolumeInfo bestCandidate = null;
362 long bestCandidateAvailBytes = Long.MIN_VALUE;
363 for (VolumeInfo vol : storageManager.getVolumes()) {
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700364 if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700365 final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path));
366 if (availBytes >= sizeBytes) {
367 allCandidates.add(vol.fsUuid);
368 }
369 if (availBytes >= bestCandidateAvailBytes) {
370 bestCandidate = vol;
371 bestCandidateAvailBytes = availBytes;
372 }
373 }
374 }
375
376 // System apps always forced to internal storage
377 if (existingInfo != null && existingInfo.isSystemApp()) {
378 installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
379 }
380
381 // If app expresses strong desire for internal space, honor it
382 if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
383 if (fitsOnInternal) {
384 return null;
385 } else {
386 throw new IOException("Requested internal only, but not enough space");
387 }
388 }
389
390 // If app already exists somewhere, prefer to stay on that volume
391 if (existingInfo != null) {
392 if (existingInfo.volumeUuid == null && fitsOnInternal) {
393 return null;
394 }
395 if (allCandidates.contains(existingInfo.volumeUuid)) {
396 return existingInfo.volumeUuid;
397 }
398 }
399
400 // We're left with either preferring external or auto, so just pick
401 // volume with most space
402 if (bestCandidate != null) {
403 return bestCandidate.fsUuid;
404 } else if (fitsOnInternal) {
405 return null;
406 } else {
407 throw new IOException("No special requests, but no room anywhere");
408 }
409 }
410
411 public static boolean fitsOnInternal(Context context, long sizeBytes) {
412 final StorageManager storage = context.getSystemService(StorageManager.class);
413 final File target = Environment.getDataDirectory();
414 return (sizeBytes <= storage.getStorageBytesUntilLow(target));
415 }
416
417 public static boolean fitsOnExternal(Context context, long sizeBytes) {
418 final StorageManager storage = context.getSystemService(StorageManager.class);
419 final StorageVolume primary = storage.getPrimaryVolume();
420 return (sizeBytes > 0) && !primary.isEmulated()
421 && Environment.MEDIA_MOUNTED.equals(primary.getState())
422 && sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
423 }
424
425 /**
426 * Given a requested {@link PackageInfo#installLocation} and calculated
Jeff Sharkey742e7902014-08-16 19:09:13 -0700427 * install size, pick the actual location to install the app.
428 */
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700429 public static int resolveInstallLocation(Context context, String packageName,
430 int installLocation, long sizeBytes, int installFlags) {
431 ApplicationInfo existingInfo = null;
432 try {
433 existingInfo = context.getPackageManager().getApplicationInfo(packageName,
434 PackageManager.GET_UNINSTALLED_PACKAGES);
435 } catch (NameNotFoundException ignored) {
436 }
437
Jeff Sharkey742e7902014-08-16 19:09:13 -0700438 final int prefer;
439 final boolean checkBoth;
440 if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
441 prefer = RECOMMEND_INSTALL_INTERNAL;
442 checkBoth = false;
443 } else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
444 prefer = RECOMMEND_INSTALL_EXTERNAL;
445 checkBoth = false;
446 } else if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
447 prefer = RECOMMEND_INSTALL_INTERNAL;
448 checkBoth = false;
449 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
450 prefer = RECOMMEND_INSTALL_EXTERNAL;
451 checkBoth = true;
452 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700453 // When app is already installed, prefer same medium
454 if (existingInfo != null) {
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700455 // TODO: distinguish if this is external ASEC
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700456 if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
457 prefer = RECOMMEND_INSTALL_EXTERNAL;
458 } else {
459 prefer = RECOMMEND_INSTALL_INTERNAL;
460 }
461 } else {
462 prefer = RECOMMEND_INSTALL_INTERNAL;
463 }
Jeff Sharkey742e7902014-08-16 19:09:13 -0700464 checkBoth = true;
465 } else {
466 prefer = RECOMMEND_INSTALL_INTERNAL;
467 checkBoth = false;
468 }
469
Jeff Sharkey742e7902014-08-16 19:09:13 -0700470 boolean fitsOnInternal = false;
471 if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700472 fitsOnInternal = fitsOnInternal(context, sizeBytes);
Jeff Sharkey742e7902014-08-16 19:09:13 -0700473 }
474
475 boolean fitsOnExternal = false;
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700476 if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
477 fitsOnExternal = fitsOnExternal(context, sizeBytes);
Jeff Sharkey742e7902014-08-16 19:09:13 -0700478 }
479
480 if (prefer == RECOMMEND_INSTALL_INTERNAL) {
481 if (fitsOnInternal) {
482 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
483 }
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700484 } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
Jeff Sharkey742e7902014-08-16 19:09:13 -0700485 if (fitsOnExternal) {
486 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
487 }
488 }
489
490 if (checkBoth) {
491 if (fitsOnInternal) {
492 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700493 } else if (fitsOnExternal) {
Jeff Sharkey742e7902014-08-16 19:09:13 -0700494 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
495 }
496 }
497
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700498 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
Jeff Sharkey742e7902014-08-16 19:09:13 -0700499 }
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700500
Jeff Sharkey941a8ba2014-08-20 16:26:32 -0700501 public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
502 String abiOverride) throws IOException {
503 NativeLibraryHelper.Handle handle = null;
504 try {
505 handle = NativeLibraryHelper.Handle.create(pkg);
506 return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride);
507 } finally {
508 IoUtils.closeQuietly(handle);
509 }
510 }
511
512 public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle,
513 boolean isForwardLocked, String abiOverride) throws IOException {
514 long sizeBytes = 0;
515
516 // Include raw APKs, and possibly unpacked resources
517 for (String codePath : pkg.getAllCodePaths()) {
518 final File codeFile = new File(codePath);
519 sizeBytes += codeFile.length();
520
521 if (isForwardLocked) {
522 sizeBytes += PackageHelper.extractPublicFiles(codeFile, null);
523 }
524 }
525
526 // Include all relevant native code
527 sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride);
528
529 return sizeBytes;
530 }
531
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700532 public static String replaceEnd(String str, String before, String after) {
533 if (!str.endsWith(before)) {
534 throw new IllegalArgumentException(
535 "Expected " + str + " to end with " + before);
536 }
537 return str.substring(0, str.length() - before.length()) + after;
538 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800539}