diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index 98d87d349de72..eec3cb0be11f5 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -16,8 +16,6 @@ package com.android.internal.content; -import static android.net.TrafficStats.MB_IN_BYTES; - import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -38,7 +36,7 @@ import android.provider.Settings; import android.util.ArraySet; import android.util.Log; -import libcore.io.IoUtils; +import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.FileOutputStream; @@ -50,6 +48,11 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; +import libcore.io.IoUtils; + +import static android.net.TrafficStats.MB_IN_BYTES; +import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL; + /** * Constants used internally between the PackageManager * and media container service transports. @@ -74,6 +77,8 @@ public class PackageHelper { public static final int APP_INSTALL_INTERNAL = 1; public static final int APP_INSTALL_EXTERNAL = 2; + private static TestableInterface sDefaultTestableInterface = null; + public static IStorageManager getStorageManager() throws RemoteException { IBinder service = ServiceManager.getService("mount"); if (service != null) { @@ -337,6 +342,65 @@ public class PackageHelper { return false; } + /** + * A group of external dependencies used in + * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values + * from the system or mocked ones for testing purposes. + */ + public static abstract class TestableInterface { + abstract public StorageManager getStorageManager(Context context); + abstract public boolean getForceAllowOnExternalSetting(Context context); + abstract public boolean getAllow3rdPartyOnInternalConfig(Context context); + abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName); + abstract public File getDataDirectory(); + + public boolean fitsOnInternalStorage(Context context, long sizeBytes) { + StorageManager storage = getStorageManager(context); + File target = getDataDirectory(); + return (sizeBytes <= storage.getStorageBytesUntilLow(target)); + } + } + + private synchronized static TestableInterface getDefaultTestableInterface() { + if (sDefaultTestableInterface == null) { + sDefaultTestableInterface = new TestableInterface() { + @Override + public StorageManager getStorageManager(Context context) { + return context.getSystemService(StorageManager.class); + } + + @Override + public boolean getForceAllowOnExternalSetting(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0; + } + + @Override + public boolean getAllow3rdPartyOnInternalConfig(Context context) { + return context.getResources().getBoolean( + com.android.internal.R.bool.config_allow3rdPartyAppOnInternal); + } + + @Override + public ApplicationInfo getExistingAppInfo(Context context, String packageName) { + ApplicationInfo existingInfo = null; + try { + existingInfo = context.getPackageManager().getApplicationInfo(packageName, + PackageManager.MATCH_ANY_USER); + } catch (NameNotFoundException ignored) { + } + return existingInfo; + } + + @Override + public File getDataDirectory() { + return Environment.getDataDirectory(); + } + }; + } + return sDefaultTestableInterface; + } + /** * Given a requested {@link PackageInfo#installLocation} and calculated * install size, pick the actual volume to install the app. Only considers @@ -348,25 +412,44 @@ public class PackageHelper { */ public static String resolveInstallVolume(Context context, String packageName, int installLocation, long sizeBytes) throws IOException { - final boolean forceAllowOnExternal = Settings.Global.getInt( - context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0; + TestableInterface testableInterface = getDefaultTestableInterface(); + return resolveInstallVolume(context, packageName, + installLocation, sizeBytes, testableInterface); + } + + @VisibleForTesting + public static String resolveInstallVolume(Context context, String packageName, + int installLocation, long sizeBytes, TestableInterface testInterface) + throws IOException { + final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context); + final boolean allow3rdPartyOnInternal = + testInterface.getAllow3rdPartyOnInternalConfig(context); // TODO: handle existing apps installed in ASEC; currently assumes // they'll end up back on internal storage - ApplicationInfo existingInfo = null; - try { - existingInfo = context.getPackageManager().getApplicationInfo(packageName, - PackageManager.MATCH_ANY_USER); - } catch (NameNotFoundException ignored) { + ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context, packageName); + + final boolean fitsOnInternal = testInterface.fitsOnInternalStorage(context, sizeBytes); + final StorageManager storageManager = + testInterface.getStorageManager(context); + + // System apps always forced to internal storage + if (existingInfo != null && existingInfo.isSystemApp()) { + if (fitsOnInternal) { + return StorageManager.UUID_PRIVATE_INTERNAL; + } else { + throw new IOException("Not enough space on existing volume " + + existingInfo.volumeUuid + " for system app " + packageName + " upgrade"); + } } - final StorageManager storageManager = context.getSystemService(StorageManager.class); - final boolean fitsOnInternal = fitsOnInternal(context, sizeBytes); - + // Now deal with non-system apps. final ArraySet allCandidates = new ArraySet<>(); VolumeInfo bestCandidate = null; long bestCandidateAvailBytes = Long.MIN_VALUE; for (VolumeInfo vol : storageManager.getVolumes()) { - if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) { + boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id); + if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable() + && (!isInternalStorage || allow3rdPartyOnInternal)) { final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path)); if (availBytes >= sizeBytes) { allCandidates.add(vol.fsUuid); @@ -378,11 +461,6 @@ public class PackageHelper { } } - // System apps always forced to internal storage - if (existingInfo != null && existingInfo.isSystemApp()) { - installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY; - } - // If app expresses strong desire for internal storage, honor it if (!forceAllowOnExternal && installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { @@ -391,6 +469,11 @@ public class PackageHelper { throw new IOException("Cannot automatically move " + packageName + " from " + existingInfo.volumeUuid + " to internal storage"); } + + if (!allow3rdPartyOnInternal) { + throw new IOException("Not allowed to install non-system apps on internal storage"); + } + if (fitsOnInternal) { return StorageManager.UUID_PRIVATE_INTERNAL; } else { @@ -411,14 +494,13 @@ public class PackageHelper { } } - // We're left with either preferring external or auto, so just pick + // We're left with new installations with either preferring external or auto, so just pick // volume with most space if (bestCandidate != null) { return bestCandidate.fsUuid; - } else if (fitsOnInternal) { - return StorageManager.UUID_PRIVATE_INTERNAL; } else { - throw new IOException("No special requests, but no room anywhere"); + throw new IOException("No special requests, but no room on allowed volumes. " + + " allow3rdPartyOnInternal? " + allow3rdPartyOnInternal); } } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 9e75ff9d7b596..ed49d63254aaf 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2687,4 +2687,7 @@ user-set value if toggled by settings so the "Transition animation scale" setting should also be hidden if intended to be permanent. --> 1.0 + + + true diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a85ddf1e54d64..d5ba1822eaaee 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2753,6 +2753,9 @@ - + + + + diff --git a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java index 5af2667d29ff5..c4d00c677cc8b 100644 --- a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java +++ b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java @@ -16,17 +16,28 @@ package android.content.pm; -import static android.net.TrafficStats.MB_IN_BYTES; - +import android.content.Context; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.storage.IStorageManager; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; import android.test.AndroidTestCase; import android.util.Log; import com.android.internal.content.PackageHelper; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static android.net.TrafficStats.MB_IN_BYTES; +import static android.os.storage.VolumeInfo.STATE_MOUNTED; + public class PackageHelperTests extends AndroidTestCase { private static final boolean localLOGV = true; public static final String TAG = "PackageHelperTests"; @@ -35,6 +46,94 @@ public class PackageHelperTests extends AndroidTestCase { private String fullId; private String fullId2; + private static final String sInternalVolPath = "/data"; + private static final String sAdoptedVolPath = "/mnt/expand/123"; + private static final String sPublicVolPath = "/emulated"; + + private static final String sInternalVolUuid = StorageManager.UUID_PRIVATE_INTERNAL; + private static final String sAdoptedVolUuid = "adopted"; + private static final String sPublicVolUuid = "emulated"; + + private static final long sInternalSize = 20000; + private static final long sAdoptedSize = 10000; + private static final long sPublicSize = 1000000; + + private static final StorageManager sStorageManager = createStorageManagerMock(); + + private static StorageManager createStorageManagerMock() { + VolumeInfo internalVol = new VolumeInfo("private", + VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/); + internalVol.path = sInternalVolPath; + internalVol.state = STATE_MOUNTED; + internalVol.fsUuid = sInternalVolUuid; + + VolumeInfo adoptedVol = new VolumeInfo("adopted", + VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/); + adoptedVol.path = sAdoptedVolPath; + adoptedVol.state = STATE_MOUNTED; + adoptedVol.fsUuid = sAdoptedVolUuid; + + VolumeInfo publicVol = new VolumeInfo("public", + VolumeInfo.TYPE_PUBLIC, null /*DiskInfo*/, null /*partGuid*/); + publicVol.state = STATE_MOUNTED; + publicVol.path = sPublicVolPath; + publicVol.fsUuid = sPublicVolUuid; + + List volumes = new ArrayList<>(); + volumes.add(internalVol); + volumes.add(adoptedVol); + volumes.add(publicVol); + + StorageManager storageManager = Mockito.mock(StorageManager.class); + Mockito.when(storageManager.getVolumes()).thenReturn(volumes); + + File internalFile = new File(sInternalVolPath); + File adoptedFile = new File(sAdoptedVolPath); + File publicFile = new File(sPublicVolPath); + Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)).thenReturn(sInternalSize); + Mockito.when(storageManager.getStorageBytesUntilLow(adoptedFile)).thenReturn(sAdoptedSize); + Mockito.when(storageManager.getStorageBytesUntilLow(publicFile)).thenReturn(sPublicSize); + return storageManager; + } + + private static final class MockedInterface extends PackageHelper.TestableInterface { + private boolean mForceAllowOnExternal = false; + private boolean mAllow3rdPartyOnInternal = true; + private ApplicationInfo mApplicationInfo = null; + + public void setMockValues(ApplicationInfo applicationInfo, + boolean forceAllowOnExternal, boolean allow3rdPartyOnInternal) { + mForceAllowOnExternal = forceAllowOnExternal; + mAllow3rdPartyOnInternal = allow3rdPartyOnInternal; + mApplicationInfo = applicationInfo; + } + + @Override + public StorageManager getStorageManager(Context context) { + return sStorageManager; + } + + @Override + public boolean getForceAllowOnExternalSetting(Context context) { + return mForceAllowOnExternal; + } + + @Override + public boolean getAllow3rdPartyOnInternalConfig(Context context) { + return mAllow3rdPartyOnInternal; + } + + @Override + public ApplicationInfo getExistingAppInfo(Context context, String packagename) { + return mApplicationInfo; + } + + @Override + public File getDataDirectory() { + return new File(sInternalVolPath); + } + } + private IStorageManager getSm() { IBinder service = ServiceManager.getService("mount"); if (service != null) { @@ -131,4 +230,328 @@ public class PackageHelperTests extends AndroidTestCase { }; return r; } + + public void testResolveInstallVolumeInternal_SystemApp() throws IOException { + ApplicationInfo systemAppInfo = new ApplicationInfo(); + systemAppInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + // All test cases for when the system app fits on internal. + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); + + mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); + + mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); + + mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); + + + // All test cases for when the system app does not fit on internal. + // Exception should be thrown. + mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch(IOException e) { + // expected + } + + mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch(IOException e) { + // expected + } + + mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch(IOException e) { + // expected + } + + mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch(IOException e) { + // expected + } + } + + public void testResolveInstallVolumeInternal_3rdParty_existing_not_too_big() + throws IOException { + // Existing apps always stay on the same volume. + // Test cases for existing app on internal. + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.volumeUuid = sInternalVolUuid; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sInternalVolUuid, volume); + } + + public void testResolveInstallVolumeInternal_3rdParty_existing_not_too_big_adopted() + throws IOException { + // Test cases for existing app on the adopted media. + ApplicationInfo appInfo = new ApplicationInfo(); + MockedInterface mockedInterface = new MockedInterface(); + String volume; + appInfo.volumeUuid = sAdoptedVolUuid; + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + } + + public void testResolveInstallVolumeAdopted_3rdParty_existing_too_big() { + // Test: update size too big, will throw exception. + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.volumeUuid = sAdoptedVolUuid; + + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); + fail("Expected exception was not thrown " + appInfo.volumeUuid); + } catch (IOException e) { + //expected + } + + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); + fail("Expected exception was not thrown " + appInfo.volumeUuid); + } catch (IOException e) { + //expected + } + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); + fail("Expected exception was not thrown " + appInfo.volumeUuid); + } catch (IOException e) { + //expected + } + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); + fail("Expected exception was not thrown " + appInfo.volumeUuid); + } catch (IOException e) { + //expected + } + } + + public void testResolveInstallVolumeInternal_3rdParty_auto() throws IOException { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the volume with bigger available space. + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the volume with bigger available space. + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the volume with bigger available space. + assertEquals(sAdoptedVolUuid, volume); + + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the volume with bigger available space. + assertEquals(sAdoptedVolUuid, volume); + + + } + + public void testResolveInstallVolumeInternal_3rdParty_internal_only() throws IOException { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch (IOException e) { + //expected + } + + appInfo = null; + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + } + + public void testResolveInstallVolumeInternal_3rdParty_not_allowed_on_internal() + throws IOException { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the non-internal volume. + assertEquals(sAdoptedVolUuid, volume); + + appInfo = null; + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the non-internal volume. + assertEquals(sAdoptedVolUuid, volume); + } + + public void testResolveInstallVolumeInternal_3rdParty_internal_only_too_big() { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + try { + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal ONLY*/, + 1000000 /*size too big*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch (IOException e) { + //expected + } + } + + public void testResolveInstallVolumeInternal_3rdParty_internal_only_not_allowed() + throws IOException { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + String volume = null; + try { + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch (IOException e) { + //expected + } + + appInfo = null; + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + + } + + public void testResolveInstallVolumeInternal_3rdParty_internal_only_forced_to_external() + throws IOException { + // New/existing installation: New + // app request location: Internal Only + // 3rd party allowed on internal: False + // Force allow external in setting: True + // Size fit? Yes + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + } }