blob: e923223de81dce4fca344f2fdafefa5e642efa02 [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 Sharkey4233f032017-07-15 12:58:38 -060019import static android.net.TrafficStats.MB_IN_BYTES;
20import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
21
Jeff Sharkey742e7902014-08-16 19:09:13 -070022import android.content.Context;
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -070023import android.content.pm.ApplicationInfo;
Jeff Sharkey742e7902014-08-16 19:09:13 -070024import android.content.pm.PackageInfo;
Jeff Sharkey683bcd32017-03-18 17:54:51 -060025import android.content.pm.PackageInstaller.SessionParams;
Jeff Sharkey4233f032017-07-15 12:58:38 -060026import android.content.pm.PackageManager;
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -070027import android.content.pm.PackageManager.NameNotFoundException;
Jeff Sharkey941a8ba2014-08-20 16:26:32 -070028import android.content.pm.PackageParser.PackageLite;
Jeff Sharkey742e7902014-08-16 19:09:13 -070029import android.os.Environment;
Kenny Root2782a472012-04-14 21:48:21 -070030import android.os.FileUtils;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080031import android.os.IBinder;
32import android.os.RemoteException;
33import android.os.ServiceManager;
Sudheer Shanka2250d562016-11-07 15:41:02 -080034import android.os.storage.IStorageManager;
Jeff Sharkey742e7902014-08-16 19:09:13 -070035import android.os.storage.StorageManager;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080036import android.os.storage.StorageResultCode;
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -070037import android.os.storage.StorageVolume;
38import android.os.storage.VolumeInfo;
Todd Kennedyf39ca8f2015-08-07 14:15:07 -070039import android.provider.Settings;
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -070040import android.util.ArraySet;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080041import android.util.Log;
42
Yao Chen199b5d62016-11-16 13:45:45 -080043import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkey742e7902014-08-16 19:09:13 -070044
Jeff Sharkey4233f032017-07-15 12:58:38 -060045import libcore.io.IoUtils;
46
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080047import java.io.File;
Kenny Root2782a472012-04-14 21:48:21 -070048import java.io.FileOutputStream;
49import java.io.IOException;
50import java.io.InputStream;
51import java.util.Collections;
Jeff Sharkey2d370922016-05-10 17:01:49 -060052import java.util.Objects;
Jeff Sharkey4233f032017-07-15 12:58:38 -060053import java.util.UUID;
Kenny Root2782a472012-04-14 21:48:21 -070054import java.util.zip.ZipEntry;
55import java.util.zip.ZipFile;
56import java.util.zip.ZipOutputStream;
57
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080058/**
59 * Constants used internally between the PackageManager
60 * and media container service transports.
Sudheer Shanka2250d562016-11-07 15:41:02 -080061 * Some utility methods to invoke StorageManagerService api.
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080062 */
63public class PackageHelper {
64 public static final int RECOMMEND_INSTALL_INTERNAL = 1;
65 public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
Todd Kennedy2699f062015-11-20 13:07:17 -080066 public static final int RECOMMEND_INSTALL_EPHEMERAL = 3;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080067 public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1;
68 public static final int RECOMMEND_FAILED_INVALID_APK = -2;
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -080069 public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3;
70 public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4;
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -080071 public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5;
Kenny Root1ebd74a2011-08-03 15:09:44 -070072 public static final int RECOMMEND_FAILED_INVALID_URI = -6;
Dianne Hackborn7767eac2012-08-23 18:25:40 -070073 public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7;
Kenny Root1ebd74a2011-08-03 15:09:44 -070074
Dianne Hackborn40e9f292012-11-27 19:12:23 -080075 private static final boolean localLOGV = false;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080076 private static final String TAG = "PackageHelper";
Suchi Amalapurapu089262d2010-03-10 14:19:21 -080077 // App installation location settings values
78 public static final int APP_INSTALL_AUTO = 0;
79 public static final int APP_INSTALL_INTERNAL = 1;
80 public static final int APP_INSTALL_EXTERNAL = 2;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080081
Yao Chen199b5d62016-11-16 13:45:45 -080082 private static TestableInterface sDefaultTestableInterface = null;
83
Sudheer Shanka2250d562016-11-07 15:41:02 -080084 public static IStorageManager getStorageManager() throws RemoteException {
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080085 IBinder service = ServiceManager.getService("mount");
86 if (service != null) {
Sudheer Shanka2250d562016-11-07 15:41:02 -080087 return IStorageManager.Stub.asInterface(service);
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080088 } else {
Sudheer Shanka2250d562016-11-07 15:41:02 -080089 Log.e(TAG, "Can't get storagemanager service");
90 throw new RemoteException("Could not contact storagemanager service");
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080091 }
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080092 }
93
Jeff Sharkey941a8ba2014-08-20 16:26:32 -070094 public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid,
Kenny Root6dceb882012-04-12 14:23:49 -070095 boolean isExternal) {
Jeff Sharkey941a8ba2014-08-20 16:26:32 -070096 // Round up to nearest MB, plus another MB for filesystem overhead
97 final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080098 try {
Sudheer Shanka2250d562016-11-07 15:41:02 -080099 IStorageManager storageManager = getStorageManager();
Kenny Root07ba2ae2012-05-09 09:14:28 -0700100
101 if (localLOGV)
102 Log.i(TAG, "Size of container " + sizeMb + " MB");
103
Sudheer Shanka2250d562016-11-07 15:41:02 -0800104 int rc = storageManager.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid,
Kenny Root6dceb882012-04-12 14:23:49 -0700105 isExternal);
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800106 if (rc != StorageResultCode.OperationSucceeded) {
107 Log.e(TAG, "Failed to create secure container " + cid);
108 return null;
109 }
Sudheer Shanka2250d562016-11-07 15:41:02 -0800110 String cachePath = storageManager.getSecureContainerPath(cid);
Suchi Amalapurapucf6eaea2010-02-23 19:37:45 -0800111 if (localLOGV) Log.i(TAG, "Created secure container " + cid +
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800112 " at " + cachePath);
113 return cachePath;
114 } catch (RemoteException e) {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800115 Log.e(TAG, "StorageManagerService running?");
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800116 }
117 return null;
118 }
119
Jeff Sharkey941a8ba2014-08-20 16:26:32 -0700120 public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) {
121 // Round up to nearest MB, plus another MB for filesystem overhead
122 final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
123 try {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800124 IStorageManager storageManager = getStorageManager();
125 int rc = storageManager.resizeSecureContainer(cid, sizeMb, sdEncKey);
Jeff Sharkey941a8ba2014-08-20 16:26:32 -0700126 if (rc == StorageResultCode.OperationSucceeded) {
127 return true;
128 }
129 } catch (RemoteException e) {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800130 Log.e(TAG, "StorageManagerService running?");
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800131 }
Jeff Sharkey941a8ba2014-08-20 16:26:32 -0700132 Log.e(TAG, "Failed to create secure container " + cid);
133 return false;
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800134 }
Jeff Sharkey941a8ba2014-08-20 16:26:32 -0700135
136 public static String mountSdDir(String cid, String key, int ownerUid) {
137 return mountSdDir(cid, key, ownerUid, true);
138 }
139
140 public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) {
141 try {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800142 int rc = getStorageManager().mountSecureContainer(cid, key, ownerUid, readOnly);
Jeff Sharkey941a8ba2014-08-20 16:26:32 -0700143 if (rc != StorageResultCode.OperationSucceeded) {
144 Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc);
145 return null;
146 }
Sudheer Shanka2250d562016-11-07 15:41:02 -0800147 return getStorageManager().getSecureContainerPath(cid);
Jeff Sharkey941a8ba2014-08-20 16:26:32 -0700148 } catch (RemoteException e) {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800149 Log.e(TAG, "StorageManagerService running?");
Jeff Sharkey941a8ba2014-08-20 16:26:32 -0700150 }
151 return null;
152 }
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800153
154 public static boolean unMountSdDir(String cid) {
155 try {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800156 int rc = getStorageManager().unmountSecureContainer(cid, true);
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800157 if (rc != StorageResultCode.OperationSucceeded) {
158 Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc);
159 return false;
160 }
161 return true;
162 } catch (RemoteException e) {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800163 Log.e(TAG, "StorageManagerService running?");
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800164 }
165 return false;
166 }
167
168 public static boolean renameSdDir(String oldId, String newId) {
169 try {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800170 int rc = getStorageManager().renameSecureContainer(oldId, newId);
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800171 if (rc != StorageResultCode.OperationSucceeded) {
172 Log.e(TAG, "Failed to rename " + oldId + " to " +
173 newId + "with rc " + rc);
174 return false;
175 }
176 return true;
177 } catch (RemoteException e) {
178 Log.i(TAG, "Failed ot rename " + oldId + " to " + newId +
179 " with exception : " + e);
180 }
181 return false;
182 }
183
184 public static String getSdDir(String cid) {
185 try {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800186 return getStorageManager().getSecureContainerPath(cid);
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800187 } catch (RemoteException e) {
188 Log.e(TAG, "Failed to get container path for " + cid +
189 " with exception " + e);
190 }
191 return null;
192 }
193
Dianne Hackborn292f8bc2011-06-27 16:27:41 -0700194 public static String getSdFilesystem(String cid) {
195 try {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800196 return getStorageManager().getSecureContainerFilesystemPath(cid);
Dianne Hackborn292f8bc2011-06-27 16:27:41 -0700197 } catch (RemoteException e) {
198 Log.e(TAG, "Failed to get container path for " + cid +
199 " with exception " + e);
200 }
201 return null;
202 }
203
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800204 public static boolean finalizeSdDir(String cid) {
205 try {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800206 int rc = getStorageManager().finalizeSecureContainer(cid);
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800207 if (rc != StorageResultCode.OperationSucceeded) {
208 Log.i(TAG, "Failed to finalize container " + cid);
209 return false;
210 }
211 return true;
212 } catch (RemoteException e) {
213 Log.e(TAG, "Failed to finalize container " + cid +
214 " with exception " + e);
215 }
216 return false;
217 }
218
219 public static boolean destroySdDir(String cid) {
220 try {
Suchi Amalapurapucf6eaea2010-02-23 19:37:45 -0800221 if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid);
Sudheer Shanka2250d562016-11-07 15:41:02 -0800222 int rc = getStorageManager().destroySecureContainer(cid, true);
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800223 if (rc != StorageResultCode.OperationSucceeded) {
224 Log.i(TAG, "Failed to destroy container " + cid);
225 return false;
226 }
227 return true;
228 } catch (RemoteException e) {
229 Log.e(TAG, "Failed to destroy container " + cid +
230 " with exception " + e);
231 }
232 return false;
233 }
234
235 public static String[] getSecureContainerList() {
236 try {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800237 return getStorageManager().getSecureContainerList();
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800238 } catch (RemoteException e) {
239 Log.e(TAG, "Failed to get secure container list with exception" +
240 e);
241 }
242 return null;
243 }
244
245 public static boolean isContainerMounted(String cid) {
246 try {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800247 return getStorageManager().isSecureContainerMounted(cid);
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800248 } catch (RemoteException e) {
249 Log.e(TAG, "Failed to find out if container " + cid + " mounted");
250 }
251 return false;
252 }
Kenny Root2782a472012-04-14 21:48:21 -0700253
Jeff Sharkeybe520fb2014-07-04 18:23:17 -0700254 /**
255 * Extract public files for the single given APK.
256 */
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700257 public static long extractPublicFiles(File apkFile, File publicZipFile)
Kenny Root2782a472012-04-14 21:48:21 -0700258 throws IOException {
Kenny Root6dceb882012-04-12 14:23:49 -0700259 final FileOutputStream fstr;
260 final ZipOutputStream publicZipOutStream;
261
262 if (publicZipFile == null) {
263 fstr = null;
264 publicZipOutStream = null;
265 } else {
266 fstr = new FileOutputStream(publicZipFile);
267 publicZipOutStream = new ZipOutputStream(fstr);
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700268 Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile);
Kenny Root6dceb882012-04-12 14:23:49 -0700269 }
270
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700271 long size = 0L;
Kenny Root6dceb882012-04-12 14:23:49 -0700272
Kenny Root2782a472012-04-14 21:48:21 -0700273 try {
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700274 final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath());
Kenny Root2782a472012-04-14 21:48:21 -0700275 try {
276 // Copy manifest, resources.arsc and res directory to public zip
277 for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) {
278 final String zipEntryName = zipEntry.getName();
279 if ("AndroidManifest.xml".equals(zipEntryName)
280 || "resources.arsc".equals(zipEntryName)
281 || zipEntryName.startsWith("res/")) {
Kenny Root6dceb882012-04-12 14:23:49 -0700282 size += zipEntry.getSize();
283 if (publicZipFile != null) {
284 copyZipEntry(zipEntry, privateZip, publicZipOutStream);
285 }
Kenny Root2782a472012-04-14 21:48:21 -0700286 }
287 }
288 } finally {
Kenny Root6dceb882012-04-12 14:23:49 -0700289 try { privateZip.close(); } catch (IOException e) {}
Kenny Root2782a472012-04-14 21:48:21 -0700290 }
291
Kenny Root6dceb882012-04-12 14:23:49 -0700292 if (publicZipFile != null) {
293 publicZipOutStream.finish();
294 publicZipOutStream.flush();
295 FileUtils.sync(fstr);
296 publicZipOutStream.close();
297 FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR
298 | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1);
299 }
Kenny Root2782a472012-04-14 21:48:21 -0700300 } finally {
301 IoUtils.closeQuietly(publicZipOutStream);
302 }
Kenny Root6dceb882012-04-12 14:23:49 -0700303
304 return size;
Kenny Root2782a472012-04-14 21:48:21 -0700305 }
306
307 private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile,
308 ZipOutputStream outZipStream) throws IOException {
309 byte[] buffer = new byte[4096];
310 int num;
311
312 ZipEntry newEntry;
313 if (zipEntry.getMethod() == ZipEntry.STORED) {
314 // Preserve the STORED method of the input entry.
315 newEntry = new ZipEntry(zipEntry);
316 } else {
317 // Create a new entry so that the compressed len is recomputed.
318 newEntry = new ZipEntry(zipEntry.getName());
319 }
320 outZipStream.putNextEntry(newEntry);
321
322 final InputStream data = inZipFile.getInputStream(zipEntry);
323 try {
324 while ((num = data.read(buffer)) > 0) {
325 outZipStream.write(buffer, 0, num);
326 }
327 outZipStream.flush();
328 } finally {
329 IoUtils.closeQuietly(data);
330 }
331 }
Kenny Root6dceb882012-04-12 14:23:49 -0700332
333 public static boolean fixSdPermissions(String cid, int gid, String filename) {
334 try {
Sudheer Shanka2250d562016-11-07 15:41:02 -0800335 int rc = getStorageManager().fixPermissionsSecureContainer(cid, gid, filename);
Kenny Root6dceb882012-04-12 14:23:49 -0700336 if (rc != StorageResultCode.OperationSucceeded) {
337 Log.i(TAG, "Failed to fixperms container " + cid);
338 return false;
339 }
340 return true;
341 } catch (RemoteException e) {
342 Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e);
343 }
344 return false;
345 }
Jeff Sharkey742e7902014-08-16 19:09:13 -0700346
347 /**
Yao Chen199b5d62016-11-16 13:45:45 -0800348 * A group of external dependencies used in
349 * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values
350 * from the system or mocked ones for testing purposes.
351 */
352 public static abstract class TestableInterface {
353 abstract public StorageManager getStorageManager(Context context);
354 abstract public boolean getForceAllowOnExternalSetting(Context context);
355 abstract public boolean getAllow3rdPartyOnInternalConfig(Context context);
356 abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName);
357 abstract public File getDataDirectory();
358
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600359 public boolean fitsOnInternalStorage(Context context, SessionParams params)
360 throws IOException {
Yao Chen199b5d62016-11-16 13:45:45 -0800361 StorageManager storage = getStorageManager(context);
Jeff Sharkey4233f032017-07-15 12:58:38 -0600362 final UUID target = storage.getUuidForPath(getDataDirectory());
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600363 return (params.sizeBytes <= storage.getAllocatableBytes(target,
364 translateAllocateFlags(params.installFlags)));
Yao Chen199b5d62016-11-16 13:45:45 -0800365 }
366 }
367
368 private synchronized static TestableInterface getDefaultTestableInterface() {
369 if (sDefaultTestableInterface == null) {
370 sDefaultTestableInterface = new TestableInterface() {
371 @Override
372 public StorageManager getStorageManager(Context context) {
373 return context.getSystemService(StorageManager.class);
374 }
375
376 @Override
377 public boolean getForceAllowOnExternalSetting(Context context) {
378 return Settings.Global.getInt(context.getContentResolver(),
379 Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
380 }
381
382 @Override
383 public boolean getAllow3rdPartyOnInternalConfig(Context context) {
384 return context.getResources().getBoolean(
385 com.android.internal.R.bool.config_allow3rdPartyAppOnInternal);
386 }
387
388 @Override
389 public ApplicationInfo getExistingAppInfo(Context context, String packageName) {
390 ApplicationInfo existingInfo = null;
391 try {
392 existingInfo = context.getPackageManager().getApplicationInfo(packageName,
393 PackageManager.MATCH_ANY_USER);
394 } catch (NameNotFoundException ignored) {
395 }
396 return existingInfo;
397 }
398
399 @Override
400 public File getDataDirectory() {
401 return Environment.getDataDirectory();
402 }
403 };
404 }
405 return sDefaultTestableInterface;
406 }
407
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600408 @VisibleForTesting
409 @Deprecated
410 public static String resolveInstallVolume(Context context, String packageName,
411 int installLocation, long sizeBytes, TestableInterface testInterface)
412 throws IOException {
413 final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
414 params.appPackageName = packageName;
415 params.installLocation = installLocation;
416 params.sizeBytes = sizeBytes;
417 return resolveInstallVolume(context, params, testInterface);
418 }
419
Yao Chen199b5d62016-11-16 13:45:45 -0800420 /**
Jeff Sharkey742e7902014-08-16 19:09:13 -0700421 * Given a requested {@link PackageInfo#installLocation} and calculated
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700422 * install size, pick the actual volume to install the app. Only considers
423 * internal and private volumes, and prefers to keep an existing package on
424 * its current volume.
425 *
426 * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
427 * for internal storage.
428 */
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600429 public static String resolveInstallVolume(Context context, SessionParams params)
430 throws IOException {
Yao Chen199b5d62016-11-16 13:45:45 -0800431 TestableInterface testableInterface = getDefaultTestableInterface();
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600432 return resolveInstallVolume(context, params.appPackageName, params.installLocation,
433 params.sizeBytes, testableInterface);
Yao Chen199b5d62016-11-16 13:45:45 -0800434 }
435
436 @VisibleForTesting
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600437 public static String resolveInstallVolume(Context context, SessionParams params,
438 TestableInterface testInterface) throws IOException {
Yao Chen199b5d62016-11-16 13:45:45 -0800439 final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context);
440 final boolean allow3rdPartyOnInternal =
441 testInterface.getAllow3rdPartyOnInternalConfig(context);
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700442 // TODO: handle existing apps installed in ASEC; currently assumes
443 // they'll end up back on internal storage
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600444 ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context,
445 params.appPackageName);
Yao Chen199b5d62016-11-16 13:45:45 -0800446
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600447 final boolean fitsOnInternal = testInterface.fitsOnInternalStorage(context, params);
Yao Chen199b5d62016-11-16 13:45:45 -0800448 final StorageManager storageManager =
449 testInterface.getStorageManager(context);
450
451 // System apps always forced to internal storage
452 if (existingInfo != null && existingInfo.isSystemApp()) {
453 if (fitsOnInternal) {
454 return StorageManager.UUID_PRIVATE_INTERNAL;
455 } else {
456 throw new IOException("Not enough space on existing volume "
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600457 + existingInfo.volumeUuid + " for system app " + params.appPackageName
458 + " upgrade");
Yao Chen199b5d62016-11-16 13:45:45 -0800459 }
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700460 }
461
Yao Chen199b5d62016-11-16 13:45:45 -0800462 // Now deal with non-system apps.
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700463 final ArraySet<String> allCandidates = new ArraySet<>();
464 VolumeInfo bestCandidate = null;
465 long bestCandidateAvailBytes = Long.MIN_VALUE;
466 for (VolumeInfo vol : storageManager.getVolumes()) {
Yao Chen199b5d62016-11-16 13:45:45 -0800467 boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id);
468 if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()
469 && (!isInternalStorage || allow3rdPartyOnInternal)) {
Jeff Sharkey4233f032017-07-15 12:58:38 -0600470 final UUID target = storageManager.getUuidForPath(new File(vol.path));
471 final long availBytes = storageManager.getAllocatableBytes(target,
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600472 translateAllocateFlags(params.installFlags));
473 if (availBytes >= params.sizeBytes) {
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700474 allCandidates.add(vol.fsUuid);
475 }
476 if (availBytes >= bestCandidateAvailBytes) {
477 bestCandidate = vol;
478 bestCandidateAvailBytes = availBytes;
479 }
480 }
481 }
482
Jeff Sharkey2d370922016-05-10 17:01:49 -0600483 // If app expresses strong desire for internal storage, honor it
Todd Kennedyf39ca8f2015-08-07 14:15:07 -0700484 if (!forceAllowOnExternal
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600485 && params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
Jeff Sharkey2d370922016-05-10 17:01:49 -0600486 if (existingInfo != null && !Objects.equals(existingInfo.volumeUuid,
487 StorageManager.UUID_PRIVATE_INTERNAL)) {
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600488 throw new IOException("Cannot automatically move " + params.appPackageName
489 + " from " + existingInfo.volumeUuid + " to internal storage");
Jeff Sharkey2d370922016-05-10 17:01:49 -0600490 }
Yao Chen199b5d62016-11-16 13:45:45 -0800491
492 if (!allow3rdPartyOnInternal) {
493 throw new IOException("Not allowed to install non-system apps on internal storage");
494 }
495
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700496 if (fitsOnInternal) {
Jeff Sharkey2d370922016-05-10 17:01:49 -0600497 return StorageManager.UUID_PRIVATE_INTERNAL;
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700498 } else {
499 throw new IOException("Requested internal only, but not enough space");
500 }
501 }
502
Jeff Sharkey2d370922016-05-10 17:01:49 -0600503 // If app already exists somewhere, we must stay on that volume
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700504 if (existingInfo != null) {
Jeff Sharkey2d370922016-05-10 17:01:49 -0600505 if (Objects.equals(existingInfo.volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)
506 && fitsOnInternal) {
507 return StorageManager.UUID_PRIVATE_INTERNAL;
508 } else if (allCandidates.contains(existingInfo.volumeUuid)) {
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700509 return existingInfo.volumeUuid;
Jeff Sharkey2d370922016-05-10 17:01:49 -0600510 } else {
511 throw new IOException("Not enough space on existing volume "
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600512 + existingInfo.volumeUuid + " for " + params.appPackageName + " upgrade");
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700513 }
514 }
515
Yao Chen199b5d62016-11-16 13:45:45 -0800516 // We're left with new installations with either preferring external or auto, so just pick
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700517 // volume with most space
518 if (bestCandidate != null) {
519 return bestCandidate.fsUuid;
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700520 } else {
Yao Chen199b5d62016-11-16 13:45:45 -0800521 throw new IOException("No special requests, but no room on allowed volumes. "
522 + " allow3rdPartyOnInternal? " + allow3rdPartyOnInternal);
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700523 }
524 }
525
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600526 public static boolean fitsOnInternal(Context context, SessionParams params) throws IOException {
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700527 final StorageManager storage = context.getSystemService(StorageManager.class);
Jeff Sharkey4233f032017-07-15 12:58:38 -0600528 final UUID target = storage.getUuidForPath(Environment.getDataDirectory());
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600529 return (params.sizeBytes <= storage.getAllocatableBytes(target,
530 translateAllocateFlags(params.installFlags)));
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700531 }
532
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600533 public static boolean fitsOnExternal(Context context, SessionParams params) {
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700534 final StorageManager storage = context.getSystemService(StorageManager.class);
535 final StorageVolume primary = storage.getPrimaryVolume();
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600536 return (params.sizeBytes > 0) && !primary.isEmulated()
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700537 && Environment.MEDIA_MOUNTED.equals(primary.getState())
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600538 && params.sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
539 }
540
541 @Deprecated
542 public static int resolveInstallLocation(Context context, String packageName,
543 int installLocation, long sizeBytes, int installFlags) {
544 final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
545 params.appPackageName = packageName;
546 params.installLocation = installLocation;
547 params.sizeBytes = sizeBytes;
548 params.installFlags = installFlags;
549 try {
550 return resolveInstallLocation(context, params);
551 } catch (IOException e) {
552 throw new IllegalStateException(e);
553 }
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700554 }
555
556 /**
557 * Given a requested {@link PackageInfo#installLocation} and calculated
Jeff Sharkey742e7902014-08-16 19:09:13 -0700558 * install size, pick the actual location to install the app.
559 */
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600560 public static int resolveInstallLocation(Context context, SessionParams params)
561 throws IOException {
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700562 ApplicationInfo existingInfo = null;
563 try {
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600564 existingInfo = context.getPackageManager().getApplicationInfo(params.appPackageName,
Amith Yamasani0d1fd8d2016-10-12 14:21:51 -0700565 PackageManager.MATCH_ANY_USER);
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700566 } catch (NameNotFoundException ignored) {
567 }
568
Jeff Sharkey742e7902014-08-16 19:09:13 -0700569 final int prefer;
570 final boolean checkBoth;
Todd Kennedy2699f062015-11-20 13:07:17 -0800571 boolean ephemeral = false;
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600572 if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
Todd Kennedy2699f062015-11-20 13:07:17 -0800573 prefer = RECOMMEND_INSTALL_INTERNAL;
574 ephemeral = true;
575 checkBoth = false;
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600576 } else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
Jeff Sharkey742e7902014-08-16 19:09:13 -0700577 prefer = RECOMMEND_INSTALL_INTERNAL;
578 checkBoth = false;
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600579 } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
Jeff Sharkey742e7902014-08-16 19:09:13 -0700580 prefer = RECOMMEND_INSTALL_EXTERNAL;
581 checkBoth = false;
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600582 } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
Jeff Sharkey742e7902014-08-16 19:09:13 -0700583 prefer = RECOMMEND_INSTALL_INTERNAL;
584 checkBoth = false;
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600585 } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
Jeff Sharkey742e7902014-08-16 19:09:13 -0700586 prefer = RECOMMEND_INSTALL_EXTERNAL;
587 checkBoth = true;
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600588 } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700589 // When app is already installed, prefer same medium
590 if (existingInfo != null) {
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700591 // TODO: distinguish if this is external ASEC
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700592 if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
593 prefer = RECOMMEND_INSTALL_EXTERNAL;
594 } else {
595 prefer = RECOMMEND_INSTALL_INTERNAL;
596 }
597 } else {
598 prefer = RECOMMEND_INSTALL_INTERNAL;
599 }
Jeff Sharkey742e7902014-08-16 19:09:13 -0700600 checkBoth = true;
601 } else {
602 prefer = RECOMMEND_INSTALL_INTERNAL;
603 checkBoth = false;
604 }
605
Jeff Sharkey742e7902014-08-16 19:09:13 -0700606 boolean fitsOnInternal = false;
607 if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600608 fitsOnInternal = fitsOnInternal(context, params);
Jeff Sharkey742e7902014-08-16 19:09:13 -0700609 }
610
611 boolean fitsOnExternal = false;
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700612 if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600613 fitsOnExternal = fitsOnExternal(context, params);
Jeff Sharkey742e7902014-08-16 19:09:13 -0700614 }
615
616 if (prefer == RECOMMEND_INSTALL_INTERNAL) {
Todd Kennedy2699f062015-11-20 13:07:17 -0800617 // The ephemeral case will either fit and return EPHEMERAL, or will not fit
618 // and will fall through to return INSUFFICIENT_STORAGE
Jeff Sharkey742e7902014-08-16 19:09:13 -0700619 if (fitsOnInternal) {
Todd Kennedy2699f062015-11-20 13:07:17 -0800620 return (ephemeral)
621 ? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL
622 : PackageHelper.RECOMMEND_INSTALL_INTERNAL;
Jeff Sharkey742e7902014-08-16 19:09:13 -0700623 }
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700624 } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
Jeff Sharkey742e7902014-08-16 19:09:13 -0700625 if (fitsOnExternal) {
626 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
627 }
628 }
629
630 if (checkBoth) {
631 if (fitsOnInternal) {
632 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700633 } else if (fitsOnExternal) {
Jeff Sharkey742e7902014-08-16 19:09:13 -0700634 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
635 }
636 }
637
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -0700638 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
Jeff Sharkey742e7902014-08-16 19:09:13 -0700639 }
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700640
Jeff Sharkey941a8ba2014-08-20 16:26:32 -0700641 public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
642 String abiOverride) throws IOException {
643 NativeLibraryHelper.Handle handle = null;
644 try {
645 handle = NativeLibraryHelper.Handle.create(pkg);
646 return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride);
647 } finally {
648 IoUtils.closeQuietly(handle);
649 }
650 }
651
652 public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle,
653 boolean isForwardLocked, String abiOverride) throws IOException {
654 long sizeBytes = 0;
655
656 // Include raw APKs, and possibly unpacked resources
657 for (String codePath : pkg.getAllCodePaths()) {
658 final File codeFile = new File(codePath);
659 sizeBytes += codeFile.length();
660
661 if (isForwardLocked) {
662 sizeBytes += PackageHelper.extractPublicFiles(codeFile, null);
663 }
664 }
665
666 // Include all relevant native code
667 sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride);
668
669 return sizeBytes;
670 }
671
Jeff Sharkeybb7b7be2014-08-19 16:18:28 -0700672 public static String replaceEnd(String str, String before, String after) {
673 if (!str.endsWith(before)) {
674 throw new IllegalArgumentException(
675 "Expected " + str + " to end with " + before);
676 }
677 return str.substring(0, str.length() - before.length()) + after;
678 }
Jeff Sharkey683bcd32017-03-18 17:54:51 -0600679
680 public static int translateAllocateFlags(int installFlags) {
681 if ((installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0) {
682 return StorageManager.FLAG_ALLOCATE_AGGRESSIVE;
683 } else {
684 return 0;
685 }
686 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800687}