Merge "Kernel mapping for package scope by user"
diff --git a/core/tests/packagemanagertests/Android.mk b/core/tests/packagemanagertests/Android.mk
new file mode 100644
index 0000000..c1e8c98
--- /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 0000000..8f49008
--- /dev/null
+++ b/core/tests/packagemanagertests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="internalOnly"
+ package="com.android.frameworks.coretests.packagemanager"
+ android:sharedUserId="android.uid.system">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.coretests.packagemanager"
+ android:label="Frameworks PackageManager Core Tests" />
+
+</manifest>
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 0000000..1097bc7
--- /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 "<error>";
+ }
+ }
+
+ @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 6da916b..0d63f72 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 @@
pkgSetting.setInstalled(install, UserHandle.USER_SYSTEM);
}
}
+ scheduleWritePackageRestrictionsLocked(UserHandle.USER_SYSTEM);
}
}
@@ -13313,6 +13314,7 @@
pkgSetting.setHidden(false, userId);
pkgSetting.setInstallReason(installReason, userId);
mSettings.writePackageRestrictionsLPr(userId);
+ mSettings.writeKernelMappingLPr(pkgSetting);
installed = true;
}
}
@@ -16410,6 +16412,7 @@
//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 @@
} else if (!previousUserIds.contains(userId)) {
ps.setInstallReason(installReason, userId);
}
+ mSettings.writeKernelMappingLPr(ps);
}
res.name = pkgName;
res.uid = newPackage.applicationInfo.uid;
@@ -17667,6 +17671,7 @@
// 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 @@
if (DEBUG_REMOVE) {
Slog.d(TAG, " user " + userId + " => " + installed);
}
+ if (installed != ps.getInstalled(userId)) {
+ installedStateChanged = true;
+ }
ps.setInstalled(installed, userId);
}
}
@@ -17723,6 +17731,9 @@
// 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 b63edfd..0e11b0c 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 @@
* 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 @@
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 281e445..6156802 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 @@
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 @@
/** List of packages that installed other packages */
final ArraySet<String> mInstallerPackages = new ArraySet<>();
- /** Map from package name to appId */
- private final ArrayMap<String, Integer> mKernelMapping = new ArrayMap<>();
+ /** Map from package name to appId and excluded userids */
+ private final ArrayMap<String, KernelPackageState> mKernelMapping = new ArrayMap<>();
// List of replaced system applications
private final ArrayMap<String, PackageSetting> mDisabledSysPackages =
@@ -271,6 +272,11 @@
private final ArrayMap<String, IntentFilterVerificationInfo> mRestoredIntentFilterVerifications =
new ArrayMap<String, IntentFilterVerificationInfo>();
+ private static final class KernelPackageState {
+ int appId;
+ int[] excludedUserIds;
+ }
+
// Bookkeeping for restored user permission grants
final class RestoredPermissionGrant {
String permissionName;
@@ -2512,6 +2518,15 @@
//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 @@
}
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 @@
!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 @@
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) {