Merge "Disable moving 3rd party apps to internal if not allowed."
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index f3185a8..20f7e63 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -24,6 +24,7 @@
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
@@ -1386,7 +1387,7 @@
}
}
- ApplicationPackageManager(ContextImpl context,
+ protected ApplicationPackageManager(ContextImpl context,
IPackageManager pm) {
mContext = context;
mPM = pm;
@@ -1820,6 +1821,12 @@
@Override
public @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ return getPackageCurrentVolume(app, storage);
+ }
+
+ @VisibleForTesting
+ protected @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app,
+ StorageManager storage) {
if (app.isInternal()) {
return storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL);
} else if (app.isExternalAsec()) {
@@ -1831,25 +1838,43 @@
@Override
public @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app) {
- final StorageManager storage = mContext.getSystemService(StorageManager.class);
- final VolumeInfo currentVol = getPackageCurrentVolume(app);
- final List<VolumeInfo> vols = storage.getVolumes();
+ final StorageManager storageManager = mContext.getSystemService(StorageManager.class);
+ return getPackageCandidateVolumes(app, storageManager, mPM);
+ }
+
+ @VisibleForTesting
+ protected @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app,
+ StorageManager storageManager, IPackageManager pm) {
+ final VolumeInfo currentVol = getPackageCurrentVolume(app, storageManager);
+ final List<VolumeInfo> vols = storageManager.getVolumes();
final List<VolumeInfo> candidates = new ArrayList<>();
for (VolumeInfo vol : vols) {
- if (Objects.equals(vol, currentVol) || isPackageCandidateVolume(mContext, app, vol)) {
+ if (Objects.equals(vol, currentVol)
+ || isPackageCandidateVolume(mContext, app, vol, pm)) {
candidates.add(vol);
}
}
return candidates;
}
- private boolean isPackageCandidateVolume(
- ContextImpl context, ApplicationInfo app, VolumeInfo vol) {
- final boolean forceAllowOnExternal = Settings.Global.getInt(
+ @VisibleForTesting
+ protected boolean isForceAllowOnExternal(Context context) {
+ return Settings.Global.getInt(
context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
- // Private internal is always an option
+ }
+
+ @VisibleForTesting
+ protected boolean isAllow3rdPartyOnInternal(Context context) {
+ return context.getResources().getBoolean(
+ com.android.internal.R.bool.config_allow3rdPartyAppOnInternal);
+ }
+
+ private boolean isPackageCandidateVolume(
+ ContextImpl context, ApplicationInfo app, VolumeInfo vol, IPackageManager pm) {
+ final boolean forceAllowOnExternal = isForceAllowOnExternal(context);
+
if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
- return true;
+ return app.isSystemApp() || isAllow3rdPartyOnInternal(context);
}
// System apps and apps demanding internal storage can't be moved
@@ -1875,7 +1900,7 @@
// Some apps can't be moved. (e.g. device admins)
try {
- if (mPM.isPackageDeviceAdminOnAnyUser(app.packageName)) {
+ if (pm.isPackageDeviceAdminOnAnyUser(app.packageName)) {
return false;
}
} catch (RemoteException e) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 04e649c..c3fe182 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1360,6 +1360,14 @@
public static final int MOVE_FAILED_DEVICE_ADMIN = -8;
/**
+ * Error code that is passed to the {@link IPackageMoveObserver} if system does not allow
+ * non-system apps to be moved to internal storage.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_3RD_PARTY_NOT_ALLOWED_ON_INTERNAL = -9;
+
+ /**
* Flag parameter for {@link #movePackage} to indicate that
* the package should be moved to internal storage if its
* been installed on external media.
diff --git a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
new file mode 100644
index 0000000..f60bf94
--- /dev/null
+++ b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static android.os.storage.VolumeInfo.STATE_MOUNTED;
+import static android.os.storage.VolumeInfo.STATE_UNMOUNTED;
+
+public class ApplicationPackageManagerTest extends TestCase {
+ private static final String sInternalVolPath = "/data";
+ private static final String sAdoptedVolPath = "/mnt/expand/123";
+ private static final String sPublicVolPath = "/emulated";
+ private static final String sPrivateUnmountedVolPath = "/private";
+
+ private static final String sInternalVolUuid = null; //StorageManager.UUID_PRIVATE_INTERNAL
+ private static final String sAdoptedVolUuid = "adopted";
+ private static final String sPublicVolUuid = "emulated";
+ private static final String sPrivateUnmountedVolUuid = "private";
+
+ private static final VolumeInfo sInternalVol = new VolumeInfo("private",
+ VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/);
+
+ private static final VolumeInfo sAdoptedVol = new VolumeInfo("adopted",
+ VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/);
+
+ private static final VolumeInfo sPublicVol = new VolumeInfo("public",
+ VolumeInfo.TYPE_PUBLIC, null /*DiskInfo*/, null /*partGuid*/);
+
+ private static final VolumeInfo sPrivateUnmountedVol = new VolumeInfo("private2",
+ VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/);
+
+ private static final List<VolumeInfo> sVolumes = new ArrayList<>();
+
+ static {
+ sInternalVol.path = sInternalVolPath;
+ sInternalVol.state = STATE_MOUNTED;
+ sInternalVol.fsUuid = sInternalVolUuid;
+
+ sAdoptedVol.path = sAdoptedVolPath;
+ sAdoptedVol.state = STATE_MOUNTED;
+ sAdoptedVol.fsUuid = sAdoptedVolUuid;
+
+ sPublicVol.state = STATE_MOUNTED;
+ sPublicVol.path = sPublicVolPath;
+ sPublicVol.fsUuid = sPublicVolUuid;
+
+ sPrivateUnmountedVol.state = STATE_UNMOUNTED;
+ sPrivateUnmountedVol.path = sPrivateUnmountedVolPath;
+ sPrivateUnmountedVol.fsUuid = sPrivateUnmountedVolUuid;
+
+ sVolumes.add(sInternalVol);
+ sVolumes.add(sAdoptedVol);
+ sVolumes.add(sPublicVol);
+ sVolumes.add(sPrivateUnmountedVol);
+ }
+
+ private static final class MockedApplicationPackageManager extends ApplicationPackageManager {
+ private boolean mForceAllowOnExternal = false;
+ private boolean mAllow3rdPartyOnInternal = true;
+
+ public MockedApplicationPackageManager() {
+ super(null, null);
+ }
+
+ public void setForceAllowOnExternal(boolean forceAllowOnExternal) {
+ mForceAllowOnExternal = forceAllowOnExternal;
+ }
+
+ public void setAllow3rdPartyOnInternal(boolean allow3rdPartyOnInternal) {
+ mAllow3rdPartyOnInternal = allow3rdPartyOnInternal;
+ }
+
+ @Override
+ public boolean isForceAllowOnExternal(Context context) {
+ return mForceAllowOnExternal;
+ }
+
+ @Override
+ public boolean isAllow3rdPartyOnInternal(Context context) {
+ return mAllow3rdPartyOnInternal;
+ }
+ }
+
+ private StorageManager getMockedStorageManager() {
+ StorageManager storageManager = Mockito.mock(StorageManager.class);
+ Mockito.when(storageManager.getVolumes()).thenReturn(sVolumes);
+ Mockito.when(storageManager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL))
+ .thenReturn(sInternalVol);
+ Mockito.when(storageManager.findVolumeByUuid(sAdoptedVolUuid))
+ .thenReturn(sAdoptedVol);
+ Mockito.when(storageManager.findVolumeByUuid(sPublicVolUuid))
+ .thenReturn(sPublicVol);
+ Mockito.when(storageManager.findVolumeByUuid(sPrivateUnmountedVolUuid))
+ .thenReturn(sPrivateUnmountedVol);
+ return storageManager;
+ }
+
+ private void verifyReturnedVolumes(List<VolumeInfo> actualVols, VolumeInfo... exptectedVols) {
+ boolean failed = false;
+ if (actualVols.size() != exptectedVols.length) {
+ failed = true;
+ } else {
+ for (VolumeInfo vol : exptectedVols) {
+ if (!actualVols.contains(vol)) {
+ failed = true;
+ break;
+ }
+ }
+ }
+
+ if (failed) {
+ fail("Wrong volumes returned.\n Expected: " + Arrays.toString(exptectedVols)
+ + "\n Actual: " + Arrays.toString(actualVols.toArray()));
+ }
+ }
+
+ public void testGetCandidateVolumes_systemApp() throws Exception {
+ ApplicationInfo sysAppInfo = new ApplicationInfo();
+ sysAppInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+ StorageManager storageManager = getMockedStorageManager();
+ IPackageManager pm = Mockito.mock(IPackageManager.class);
+
+ MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
+
+ appPkgMgr.setAllow3rdPartyOnInternal(true);
+ appPkgMgr.setForceAllowOnExternal(true);
+ List<VolumeInfo> candidates =
+ appPkgMgr.getPackageCandidateVolumes(sysAppInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol);
+
+ appPkgMgr.setAllow3rdPartyOnInternal(true);
+ appPkgMgr.setForceAllowOnExternal(false);
+ candidates = appPkgMgr.getPackageCandidateVolumes(sysAppInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol);
+
+ appPkgMgr.setAllow3rdPartyOnInternal(false);
+ appPkgMgr.setForceAllowOnExternal(false);
+ candidates = appPkgMgr.getPackageCandidateVolumes(sysAppInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol);
+
+ appPkgMgr.setAllow3rdPartyOnInternal(false);
+ appPkgMgr.setForceAllowOnExternal(true);
+ candidates = appPkgMgr.getPackageCandidateVolumes(sysAppInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol);
+ }
+
+ public void testGetCandidateVolumes_3rdParty_internalOnly() throws Exception {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ StorageManager storageManager = getMockedStorageManager();
+
+ IPackageManager pm = Mockito.mock(IPackageManager.class);
+ Mockito.when(pm.isPackageDeviceAdminOnAnyUser(Mockito.anyString())).thenReturn(false);
+
+ MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
+
+ // must allow 3rd party on internal, otherwise the app wouldn't have been installed before.
+ appPkgMgr.setAllow3rdPartyOnInternal(true);
+
+ // INSTALL_LOCATION_INTERNAL_ONLY AND INSTALL_LOCATION_UNSPECIFIED are treated the same.
+ int[] locations = {PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY,
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED};
+
+ for (int location : locations) {
+ appInfo.installLocation = location;
+ appPkgMgr.setForceAllowOnExternal(true);
+ List<VolumeInfo> candidates = appPkgMgr.getPackageCandidateVolumes(
+ appInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol, sAdoptedVol);
+
+ appPkgMgr.setForceAllowOnExternal(false);
+ candidates = appPkgMgr.getPackageCandidateVolumes(appInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol);
+ }
+ }
+
+ public void testGetCandidateVolumes_3rdParty_auto() throws Exception {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ StorageManager storageManager = getMockedStorageManager();
+
+ IPackageManager pm = Mockito.mock(IPackageManager.class);
+
+ MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
+ appPkgMgr.setForceAllowOnExternal(true);
+
+ // INSTALL_LOCATION_AUTO AND INSTALL_LOCATION_PREFER_EXTERNAL are treated the same.
+ int[] locations = {PackageInfo.INSTALL_LOCATION_AUTO,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL};
+
+ for (int location : locations) {
+ appInfo.installLocation = location;
+ appInfo.flags = 0;
+
+ appInfo.volumeUuid = sInternalVolUuid;
+ Mockito.when(pm.isPackageDeviceAdminOnAnyUser(Mockito.anyString())).thenReturn(false);
+ appPkgMgr.setAllow3rdPartyOnInternal(true);
+ List<VolumeInfo> candidates = appPkgMgr.getPackageCandidateVolumes(
+ appInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol, sAdoptedVol);
+
+ appInfo.volumeUuid = sInternalVolUuid;
+ appPkgMgr.setAllow3rdPartyOnInternal(true);
+ Mockito.when(pm.isPackageDeviceAdminOnAnyUser(Mockito.anyString())).thenReturn(true);
+ candidates = appPkgMgr.getPackageCandidateVolumes(appInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol);
+
+ appInfo.flags = ApplicationInfo.FLAG_EXTERNAL_STORAGE;
+ appInfo.volumeUuid = sAdoptedVolUuid;
+ appPkgMgr.setAllow3rdPartyOnInternal(false);
+ candidates = appPkgMgr.getPackageCandidateVolumes(appInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sAdoptedVol);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6669889..9d2085a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -68,6 +68,7 @@
import static android.content.pm.PackageManager.MATCH_KNOWN_PACKAGES;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+import static android.content.pm.PackageManager.MOVE_FAILED_3RD_PARTY_NOT_ALLOWED_ON_INTERNAL;
import static android.content.pm.PackageManager.MOVE_FAILED_DEVICE_ADMIN;
import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
import static android.content.pm.PackageManager.MOVE_FAILED_INTERNAL_ERROR;
@@ -21042,6 +21043,14 @@
"Cannot move system application");
}
+ final boolean isInternalStorage = VolumeInfo.ID_PRIVATE_INTERNAL.equals(volumeUuid);
+ final boolean allow3rdPartyOnInternal = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allow3rdPartyAppOnInternal);
+ if (isInternalStorage && !allow3rdPartyOnInternal) {
+ throw new PackageManagerException(MOVE_FAILED_3RD_PARTY_NOT_ALLOWED_ON_INTERNAL,
+ "3rd party apps are not allowed on internal storage");
+ }
+
if (pkg.applicationInfo.isExternalAsec()) {
currentAsec = true;
currentVolumeUuid = StorageManager.UUID_PRIMARY_PHYSICAL;