diff --git a/core/tests/packagemanagertests/Android.mk b/core/tests/packagemanagertests/Android.mk new file mode 100644 index 0000000000000..c1e8c981579c0 --- /dev/null +++ b/core/tests/packagemanagertests/Android.mk @@ -0,0 +1,20 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +# Include all test java files. +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test \ + frameworks-base-testutils + +LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_PACKAGE_NAME := FrameworksCorePackageManagerTests + +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) diff --git a/core/tests/packagemanagertests/AndroidManifest.xml b/core/tests/packagemanagertests/AndroidManifest.xml new file mode 100644 index 0000000000000..8f490083b16e0 --- /dev/null +++ b/core/tests/packagemanagertests/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java new file mode 100644 index 0000000000000..1097bc731de8e --- /dev/null +++ b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java @@ -0,0 +1,109 @@ +/* + * 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.content.pm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.os.FileUtils; +import android.os.Process; +import android.os.ServiceManager; +import android.os.UserManager; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; + +/** + * This test needs to be run without any secondary users on the device, + * and selinux needs to be disabled with "adb shell setenforce 0". + */ +@RunWith(AndroidJUnit4.class) +public class KernelPackageMappingTests { + + private static final String TAG = "KernelPackageMapping"; + private static final String SDCARDFS_PATH = "/config/sdcardfs"; + + private UserInfo mSecondaryUser; + + private static File getKernelPackageDir(String packageName) { + return new File(new File(SDCARDFS_PATH), packageName); + } + + private static File getKernelPackageFile(String packageName, String filename) { + return new File(getKernelPackageDir(packageName), filename); + } + + private UserManager getUserManager() { + UserManager um = (UserManager) InstrumentationRegistry.getContext().getSystemService( + Context.USER_SERVICE); + return um; + } + + private IPackageManager getIPackageManager() { + IPackageManager ipm = IPackageManager.Stub.asInterface( + ServiceManager.getService("package")); + return ipm; + } + + private static String getContent(File file) { + try { + return FileUtils.readTextFile(file, 0, null).trim(); + } catch (IOException ioe) { + Log.w(TAG, "Couldn't read file " + file.getAbsolutePath() + "\n" + ioe); + return ""; + } + } + + @Test + public void testInstalledPrimary() throws Exception { + assertEquals("1000", getContent(getKernelPackageFile("com.android.settings", "appid"))); + } + + @Test + public void testInstalledAll() throws Exception { + assertEquals("", getContent(getKernelPackageFile("com.android.settings", + "excluded_userids"))); + } + + @Test + public void testNotInstalledSecondary() throws Exception { + mSecondaryUser = getUserManager().createUser("Secondary", 0); + assertEquals(Integer.toString(mSecondaryUser.id), + getContent( + getKernelPackageFile("com.android.frameworks.coretests.packagemanager", + "excluded_userids"))); + } + + @After + public void shutDown() throws Exception { + if (mSecondaryUser != null) { + getUserManager().removeUser(mSecondaryUser.id); + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6da916ba44669..0d63f72ea37ff 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2157,6 +2157,7 @@ public class PackageManagerService extends IPackageManager.Stub { pkgSetting.setInstalled(install, UserHandle.USER_SYSTEM); } } + scheduleWritePackageRestrictionsLocked(UserHandle.USER_SYSTEM); } } @@ -13313,6 +13314,7 @@ public class PackageManagerService extends IPackageManager.Stub { pkgSetting.setHidden(false, userId); pkgSetting.setInstallReason(installReason, userId); mSettings.writePackageRestrictionsLPr(userId); + mSettings.writeKernelMappingLPr(pkgSetting); installed = true; } } @@ -16410,6 +16412,7 @@ public class PackageManagerService extends IPackageManager.Stub { //note that the new package setting would have already been //added to mPackages. It hasn't been persisted yet. mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_INCOMPLETE); + // TODO: Remove this write? It's also written at the end of this method Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings"); mSettings.writeLPr(); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -16483,6 +16486,7 @@ public class PackageManagerService extends IPackageManager.Stub { } else if (!previousUserIds.contains(userId)) { ps.setInstallReason(installReason, userId); } + mSettings.writeKernelMappingLPr(ps); } res.name = pkgName; res.uid = newPackage.applicationInfo.uid; @@ -17667,6 +17671,7 @@ public class PackageManagerService extends IPackageManager.Stub { // writer synchronized (mPackages) { + boolean installedStateChanged = false; if (deletedPs != null) { if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) { clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL); @@ -17714,6 +17719,9 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_REMOVE) { Slog.d(TAG, " user " + userId + " => " + installed); } + if (installed != ps.getInstalled(userId)) { + installedStateChanged = true; + } ps.setInstalled(installed, userId); } } @@ -17723,6 +17731,9 @@ public class PackageManagerService extends IPackageManager.Stub { // Save settings now mSettings.writeLPr(); } + if (installedStateChanged) { + mSettings.writeKernelMappingLPr(ps); + } } if (removedAppId != -1) { // A user ID was deleted here. Go through all users and remove it @@ -17865,6 +17876,7 @@ public class PackageManagerService extends IPackageManager.Stub { UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG); if (applyUserRestrictions) { + boolean installedStateChanged = false; if (DEBUG_REMOVE) { Slog.d(TAG, "Propagating install state across reinstall"); } @@ -17873,6 +17885,9 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_REMOVE) { Slog.d(TAG, " user " + userId + " => " + installed); } + if (installed != ps.getInstalled(userId)) { + installedStateChanged = true; + } ps.setInstalled(installed, userId); mSettings.writeRuntimePermissionsForUserLPr(userId, false); @@ -17880,6 +17895,9 @@ public class PackageManagerService extends IPackageManager.Stub { // Regardless of writeSettings we need to ensure that this restriction // state propagation is persisted mSettings.writeAllUsersPackageRestrictionsLPr(); + if (installedStateChanged) { + mSettings.writeKernelMappingLPr(ps); + } } // can downgrade to reader here if (writeSettings) { @@ -18083,6 +18101,7 @@ public class PackageManagerService extends IPackageManager.Stub { // broadcasts will be sent correctly. if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete"); ps.setInstalled(true, user.getIdentifier()); + mSettings.writeKernelMappingLPr(ps); } } else { // This is a system app, so we assume that the @@ -18192,6 +18211,7 @@ public class PackageManagerService extends IPackageManager.Stub { ps.readUserState(nextUserId).domainVerificationStatus, 0, PackageManager.INSTALL_REASON_UNKNOWN); } + mSettings.writeKernelMappingLPr(ps); } private boolean clearPackageStateForUserLIF(PackageSetting ps, int userId, diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index b63edfdd0d737..0e11b0cec4c27 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -40,6 +40,9 @@ import java.util.Set; * Settings base class for pending and resolved classes. */ abstract class PackageSettingBase extends SettingBase { + + private static final int[] EMPTY_INT_ARRAY = new int[0]; + /** * Indicates the state of installation. Used by PackageManager to figure out * incomplete installations. Say a package is being installed (the state is @@ -502,6 +505,25 @@ abstract class PackageSettingBase extends SettingBase { userState.delete(userId); } + public int[] getNotInstalledUserIds() { + int count = 0; + int userStateCount = userState.size(); + for (int i = 0; i < userStateCount; i++) { + if (userState.valueAt(i).installed == false) { + count++; + } + } + if (count == 0) return EMPTY_INT_ARRAY; + int[] excludedUserIds = new int[count]; + int idx = 0; + for (int i = 0; i < userStateCount; i++) { + if (userState.valueAt(i).installed == false) { + excludedUserIds[idx++] = userState.keyAt(i); + } + } + return excludedUserIds; + } + IntentFilterVerificationInfo getIntentFilterVerificationInfo() { return verificationInfo; } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 281e445e59b41..61568022eff20 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -252,6 +252,7 @@ final class Settings { private final File mPackageListFilename; private final File mStoppedPackagesFilename; private final File mBackupStoppedPackagesFilename; + /** The top level directory in configfs for sdcardfs to push the package->uid,userId mappings */ private final File mKernelMappingFilename; /** Map from package name to settings */ @@ -260,8 +261,8 @@ final class Settings { /** List of packages that installed other packages */ final ArraySet mInstallerPackages = new ArraySet<>(); - /** Map from package name to appId */ - private final ArrayMap mKernelMapping = new ArrayMap<>(); + /** Map from package name to appId and excluded userids */ + private final ArrayMap mKernelMapping = new ArrayMap<>(); // List of replaced system applications private final ArrayMap mDisabledSysPackages = @@ -271,6 +272,11 @@ final class Settings { private final ArrayMap mRestoredIntentFilterVerifications = new ArrayMap(); + private static final class KernelPackageState { + int appId; + int[] excludedUserIds; + } + // Bookkeeping for restored user permission grants final class RestoredPermissionGrant { String permissionName; @@ -2512,6 +2518,15 @@ final class Settings { //Debug.stopMethodTracing(); } + private void writeKernelRemoveUserLPr(int userId) { + if (mKernelMappingFilename == null) return; + + File removeUserIdFile = new File(mKernelMappingFilename, "remove_userid"); + if (DEBUG_KERNEL) Slog.d(TAG, "Writing " + userId + " to " + removeUserIdFile + .getAbsolutePath()); + writeIntToFile(removeUserIdFile, userId); + } + void writeKernelMappingLPr() { if (mKernelMappingFilename == null) return; @@ -2538,27 +2553,63 @@ final class Settings { } void writeKernelMappingLPr(PackageSetting ps) { - if (mKernelMappingFilename == null) return; + if (mKernelMappingFilename == null || ps == null || ps.name == null) return; - final Integer cur = mKernelMapping.get(ps.name); - if (cur != null && cur.intValue() == ps.appId) { - // Ignore when mapping already matches - return; + KernelPackageState cur = mKernelMapping.get(ps.name); + final boolean firstTime = cur == null; + int[] excludedUserIds = ps.getNotInstalledUserIds(); + final boolean userIdsChanged = firstTime + || !Arrays.equals(excludedUserIds, cur.excludedUserIds); + + // Package directory + final File dir = new File(mKernelMappingFilename, ps.name); + + if (firstTime) { + dir.mkdir(); + // Create a new mapping state + cur = new KernelPackageState(); + mKernelMapping.put(ps.name, cur); } - if (DEBUG_KERNEL) Slog.d(TAG, "Mapping " + ps.name + " to " + ps.appId); + // If mapping is incorrect or non-existent, write the appid file + if (cur.appId != ps.appId) { + final File appIdFile = new File(dir, "appid"); + writeIntToFile(appIdFile, ps.appId); + if (DEBUG_KERNEL) Slog.d(TAG, "Mapping " + ps.name + " to " + ps.appId); + } - final File dir = new File(mKernelMappingFilename, ps.name); - dir.mkdir(); + if (userIdsChanged) { + // Build the exclusion list -- the ids to add to the exclusion list + for (int i = 0; i < excludedUserIds.length; i++) { + if (cur.excludedUserIds == null || !ArrayUtils.contains(cur.excludedUserIds, + excludedUserIds[i])) { + writeIntToFile(new File(dir, "excluded_userids"), excludedUserIds[i]); + if (DEBUG_KERNEL) Slog.d(TAG, "Writing " + excludedUserIds[i] + " to " + + ps.name + "/excluded_userids"); + } + } + // Build the inclusion list -- the ids to remove from the exclusion list + if (cur.excludedUserIds != null) { + for (int i = 0; i < cur.excludedUserIds.length; i++) { + if (!ArrayUtils.contains(excludedUserIds, cur.excludedUserIds[i])) { + writeIntToFile(new File(dir, "clear_userid"), + cur.excludedUserIds[i]); + if (DEBUG_KERNEL) Slog.d(TAG, "Writing " + cur.excludedUserIds[i] + " to " + + ps.name + "/clear_userid"); - final File file = new File(dir, "appid"); + } + } + } + cur.excludedUserIds = excludedUserIds; + } + } + + private void writeIntToFile(File file, int value) { try { - // Note that the use of US_ASCII here is safe, we're only writing a decimal - // number to the file. FileUtils.bytesToFile(file.getAbsolutePath(), - Integer.toString(ps.appId).getBytes(StandardCharsets.US_ASCII)); - mKernelMapping.put(ps.name, ps.appId); + Integer.toString(value).getBytes(StandardCharsets.US_ASCII)); } catch (IOException ignored) { + Slog.w(TAG, "Couldn't write " + value + " to " + file.getAbsolutePath()); } } @@ -4081,6 +4132,9 @@ final class Settings { !ArrayUtils.contains(disallowedPackages, ps.name); // Only system apps are initially installed. ps.setInstalled(shouldInstall, userHandle); + if (!shouldInstall) { + writeKernelMappingLPr(ps); + } // Need to create a data directory for all apps under this user. Accumulate all // required args and call the installer after mPackages lock has been released volumeUuids[i] = ps.volumeUuid; @@ -4123,6 +4177,10 @@ final class Settings { mRuntimePermissionsPersistence.onUserRemovedLPw(userId); writePackageListLPr(); + + // Inform kernel that the user was removed, so that packages are marked uninstalled + // for sdcardfs + writeKernelRemoveUserLPr(userId); } void removeCrossProfileIntentFiltersLPw(int userId) {