Override package storage constraint

We want to be able to move arbitrary packages to external storage,
regardless what they specify in their manifest. This is a developer
option and should be used with care. Trouble may ensue if an
application is moved when it really doesn't want to be moved

Bug: 22282121
Change-Id: I7664816a7fd122e6cdf3070fe50ce5464f325380
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0adce5d..0cd02dd 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -74,6 +74,7 @@
 import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
+import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.Display;
@@ -1603,14 +1604,17 @@
         final List<VolumeInfo> vols = storage.getVolumes();
         final List<VolumeInfo> candidates = new ArrayList<>();
         for (VolumeInfo vol : vols) {
-            if (Objects.equals(vol, currentVol) || isPackageCandidateVolume(app, vol)) {
+            if (Objects.equals(vol, currentVol) || isPackageCandidateVolume(mContext, app, vol)) {
                 candidates.add(vol);
             }
         }
         return candidates;
     }
 
-    private static boolean isPackageCandidateVolume(ApplicationInfo app, VolumeInfo vol) {
+    private static boolean isPackageCandidateVolume(
+            ContextImpl context, ApplicationInfo app, VolumeInfo vol) {
+        final boolean forceAllowOnExternal = Settings.Global.getInt(
+                context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
         // Private internal is always an option
         if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
             return true;
@@ -1618,8 +1622,11 @@
 
         // System apps and apps demanding internal storage can't be moved
         // anywhere else
-        if (app.isSystemApp()
-                || app.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+        if (app.isSystemApp()) {
+            return false;
+        }
+        if (!forceAllowOnExternal
+                && app.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
             return false;
         }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3cd01d0..2d78d2b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6226,6 +6226,14 @@
        public static final String MDC_INITIAL_MAX_RETRY = "mdc_initial_max_retry";
 
        /**
+        * Whether any package can be on external storage. When this is true, any
+        * package, regardless of manifest values, is a candidate for installing
+        * or moving onto external storage. (0 = false, 1 = true)
+        * @hide
+        */
+       public static final String FORCE_ALLOW_ON_EXTERNAL = "force_allow_on_external";
+
+       /**
         * Whether user has enabled development settings.
         */
        public static final String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled";
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index b04ddf4..de29a96 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -34,8 +34,10 @@
 import android.os.storage.StorageResultCode;
 import android.os.storage.StorageVolume;
 import android.os.storage.VolumeInfo;
+import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Slog;
 
 import libcore.io.IoUtils;
 
@@ -345,6 +347,8 @@
      */
     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;
         // TODO: handle existing apps installed in ASEC; currently assumes
         // they'll end up back on internal storage
         ApplicationInfo existingInfo = null;
@@ -379,7 +383,8 @@
         }
 
         // If app expresses strong desire for internal space, honor it
-        if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+        if (!forceAllowOnExternal
+                && installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
             if (fitsOnInternal) {
                 return null;
             } else {