From 022b8eaa1def76dca0ac9b409065588f55c71597 Mon Sep 17 00:00:00 2001 From: Yao Chen Date: Fri, 16 Dec 2016 11:03:28 -0800 Subject: [PATCH] Disable moving 3rd party apps to internal if not allowed. ag/1633903 added config_allow3rdPartyAppOnInternal flag to specify whether 3rd party apps are allowed on internal storage. We need to respect the flag when moving apps between storages. Bug: 30980219 Test: Added ApplicationPackageManagertest Change-Id: I0f8e76467b5071d70f40da28c2087e689c049c06 --- .../app/ApplicationPackageManager.java | 47 +++- .../android/content/pm/PackageManager.java | 8 + .../app/ApplicationPackageManagerTest.java | 242 ++++++++++++++++++ .../server/pm/PackageManagerService.java | 9 + 4 files changed, 295 insertions(+), 11 deletions(-) create mode 100644 core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 627e6611c930e..0749ab0af070f 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -24,6 +24,7 @@ import android.annotation.XmlRes; 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 @@ public class ApplicationPackageManager extends PackageManager { } } - ApplicationPackageManager(ContextImpl context, + protected ApplicationPackageManager(ContextImpl context, IPackageManager pm) { mContext = context; mPM = pm; @@ -1819,6 +1820,12 @@ public class ApplicationPackageManager extends PackageManager { @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()) { @@ -1830,25 +1837,43 @@ public class ApplicationPackageManager extends PackageManager { @Override public @NonNull List getPackageCandidateVolumes(ApplicationInfo app) { - final StorageManager storage = mContext.getSystemService(StorageManager.class); - final VolumeInfo currentVol = getPackageCurrentVolume(app); - final List vols = storage.getVolumes(); + final StorageManager storageManager = mContext.getSystemService(StorageManager.class); + return getPackageCandidateVolumes(app, storageManager, mPM); + } + + @VisibleForTesting + protected @NonNull List getPackageCandidateVolumes(ApplicationInfo app, + StorageManager storageManager, IPackageManager pm) { + final VolumeInfo currentVol = getPackageCurrentVolume(app, storageManager); + final List vols = storageManager.getVolumes(); final List 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 @@ -1874,7 +1899,7 @@ public class ApplicationPackageManager extends PackageManager { // 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 2fbb5b1c52964..da428855997d8 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1344,6 +1344,14 @@ public abstract class PackageManager { */ 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 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 0000000000000..f60bf94f8c994 --- /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 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 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 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 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 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 2dd95038e4580..b1346294555b5 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_FACTORY_ONLY; 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; @@ -20815,6 +20816,14 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); "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;