summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt2
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/app/ApplicationPackageManager.java32
-rw-r--r--core/java/android/content/pm/IPackageInstallerSession.aidl3
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl2
-rw-r--r--core/java/android/content/pm/PackageInstaller.java60
-rw-r--r--core/java/android/content/pm/PackageManager.java18
-rw-r--r--core/res/AndroidManifest.xml5
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java116
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java17
11 files changed, 254 insertions, 5 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 1ff2a2a6fa27..85ae6a149d31 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -11809,6 +11809,7 @@ package android.content.pm {
method public void close();
method public void commit(@NonNull android.content.IntentSender);
method public void fsync(@NonNull java.io.OutputStream) throws java.io.IOException;
+ method @NonNull public android.os.PersistableBundle getAppMetadata();
method @NonNull public int[] getChildSessionIds();
method @NonNull public String[] getNames() throws java.io.IOException;
method public int getParentSessionId();
@@ -11821,6 +11822,7 @@ package android.content.pm {
method public void removeSplit(@NonNull String) throws java.io.IOException;
method public void requestChecksums(@NonNull String, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, java.io.FileNotFoundException;
method public void requestUserPreapproval(@NonNull android.content.pm.PackageInstaller.PreapprovalDetails, @NonNull android.content.IntentSender);
+ method public void setAppMetadata(@Nullable android.os.PersistableBundle) throws java.io.IOException;
method @Deprecated public void setChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>, @Nullable byte[]) throws java.io.IOException;
method public void setStagingProgress(float);
method public void transfer(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a97da8805373..8528c95101a7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3614,6 +3614,7 @@ package android.content.pm {
method public abstract boolean arePermissionsIndividuallyControlled();
method @NonNull public boolean[] canPackageQuery(@NonNull String, @NonNull String[]) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(@NonNull String);
+ method @NonNull @RequiresPermission("android.permission.GET_APP_METADATA") public android.os.PersistableBundle getAppMetadata(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.ApplicationInfoFlags, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public android.content.pm.dex.ArtManager getArtManager();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 4d3f9e457ae3..309b2535798e 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -126,6 +126,11 @@ import dalvik.system.VMRuntime;
import libcore.util.EmptyArray;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
@@ -1223,6 +1228,33 @@ public class ApplicationPackageManager extends PackageManager {
}
}
+ @Override
+ @NonNull
+ public PersistableBundle getAppMetadata(@NonNull String packageName)
+ throws NameNotFoundException {
+ PersistableBundle appMetadata = null;
+ String path = null;
+ try {
+ path = mPM.getAppMetadataPath(packageName, getUserId());
+ } catch (ParcelableException e) {
+ e.maybeRethrow(NameNotFoundException.class);
+ throw new RuntimeException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (path != null) {
+ File file = new File(path);
+ try (InputStream inputStream = new FileInputStream(file)) {
+ appMetadata = PersistableBundle.readFromStream(inputStream);
+ } catch (FileNotFoundException e) {
+ // ignore and return empty bundle if app metadata does not exist
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return appMetadata != null ? appMetadata : new PersistableBundle();
+ }
+
@SuppressWarnings("unchecked")
@Override
public List<PackageInfo> getPackagesHoldingPermissions(String[] permissions, int flags) {
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 7d9c64add492..60a7b13ff6e9 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -63,4 +63,7 @@ interface IPackageInstallerSession {
void requestUserPreapproval(in PackageInstaller.PreapprovalDetails details, in IntentSender statusReceiver);
boolean isKeepApplicationEnabledSetting();
+
+ ParcelFileDescriptor getAppMetadataFd();
+ ParcelFileDescriptor openWriteAppMetadata();
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 81bea2e3573f..54ca1e59aafe 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -159,6 +159,8 @@ interface IPackageManager {
*/
ParceledListSlice getInstalledPackages(long flags, in int userId);
+ String getAppMetadataPath(String packageName, int userId);
+
/**
* This implements getPackagesHoldingPermissions via a "last returned row"
* mechanism that is not exposed in the API. This is to get around the IPC
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index f17d8fac9ede..8deecd7f0b1b 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -59,6 +59,7 @@ import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.ParcelableException;
+import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -1760,6 +1761,65 @@ public class PackageInstaller {
}
/**
+ * @return A PersistableBundle containing the app metadata set with
+ * {@link Session#setAppMetadata(PersistableBundle)}. In the case where this data does not
+ * exist, an empty PersistableBundle is returned.
+ */
+ @NonNull
+ public PersistableBundle getAppMetadata() {
+ PersistableBundle data = null;
+ try {
+ ParcelFileDescriptor pfd = mSession.getAppMetadataFd();
+ if (pfd != null) {
+ try (InputStream inputStream =
+ new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+ data = PersistableBundle.readFromStream(inputStream);
+ }
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return data != null ? data : new PersistableBundle();
+ }
+
+ private OutputStream openWriteAppMetadata() throws IOException {
+ try {
+ if (ENABLE_REVOCABLE_FD) {
+ return new ParcelFileDescriptor.AutoCloseOutputStream(
+ mSession.openWriteAppMetadata());
+ } else {
+ final ParcelFileDescriptor clientSocket = mSession.openWriteAppMetadata();
+ return new FileBridge.FileBridgeOutputStream(clientSocket);
+ }
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Optionally set the app metadata. The size of this data cannot exceed the maximum allowed.
+ * If no data is provided, then any existing app metadata from the previous install will be
+ * removed for the package.
+ *
+ * @param data a PersistableBundle containing the app metadata. If this is set to null then
+ * any existing app metadata will be removed.
+ * @throws IOException if writing the data fails.
+ */
+ public void setAppMetadata(@Nullable PersistableBundle data) throws IOException {
+ if (data == null) {
+ return;
+ }
+ try (OutputStream outputStream = openWriteAppMetadata()) {
+ data.writeToStream(outputStream);
+ }
+ }
+
+ /**
* Attempt to request the approval before committing this session.
*
* For installers that have been granted the
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f3ccfb0ac4f2..b6ca15932a90 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5753,6 +5753,24 @@ public abstract class PackageManager {
}
/**
+ * Returns the app metadata for a package.
+ *
+ * @param packageName
+ * @return A PersistableBundle containing the app metadata that was provided by the installer.
+ * In the case where a package does not have any metadata, an empty PersistableBundle is
+ * returned.
+ * @throws NameNotFoundException if no such package is available to the caller.
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @RequiresPermission(Manifest.permission.GET_APP_METADATA)
+ public PersistableBundle getAppMetadata(@NonNull String packageName)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException("getAppMetadata not implemented in subclass");
+ }
+
+ /**
* Return a List of all installed packages that are currently holding any of
* the given permissions.
*
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index aa8edf954187..2a93156f474f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6849,6 +6849,11 @@
<permission android:name="android.permission.RUN_LONG_JOBS"
android:protectionLevel="normal|appop"/>
+ <!-- Allows an app access to the installer provided app metadata.
+ @hide -->
+ <permission android:name="android.permission.GET_APP_METADATA"
+ android:protectionLevel="signature" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 682774e76505..c7db275a80e5 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -787,6 +787,9 @@
<!-- Permission required for CTS test - ApplicationExemptionsTests -->
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS" />
+ <!-- Permission required for CTS test - CtsPackageInstallTestCases-->
+ <uses-permission android:name="android.permission.GET_APP_METADATA" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 9aaf685e9817..f65a65d6762e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -34,6 +34,7 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFIC
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.os.Process.INVALID_UID;
+import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.O_WRONLY;
@@ -48,6 +49,7 @@ import static com.android.internal.util.XmlUtils.writeByteArrayAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static com.android.internal.util.XmlUtils.writeUriAttribute;
import static com.android.server.pm.PackageInstallerService.prepareStageDir;
+import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
import android.Manifest;
import android.annotation.AnyThread;
@@ -106,6 +108,7 @@ import android.icu.util.ULocale;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.os.FileBridge;
import android.os.FileUtils;
import android.os.Handler;
@@ -125,6 +128,7 @@ import android.os.incremental.IncrementalManager;
import android.os.incremental.PerUidReadTimeouts;
import android.os.incremental.StorageHealthCheckParams;
import android.os.storage.StorageManager;
+import android.provider.DeviceConfig;
import android.provider.Settings.Global;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.system.ErrnoException;
@@ -178,6 +182,7 @@ import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.file.Files;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.cert.Certificate;
@@ -292,6 +297,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
*/
private static final int INVALID_TARGET_SDK_VERSION = Integer.MAX_VALUE;
+ /**
+ * Byte size limit for app metadata.
+ *
+ * Flag type: {@code long}
+ * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+ */
+ private static final String PROPERTY_APP_METADATA_BYTE_SIZE_LIMIT =
+ "app_metadata_byte_size_limit";
+
+ /** Default byte size limit for app metadata */
+ private static final long DEFAULT_APP_METADATA_BYTE_SIZE_LIMIT = 32000;
+
// TODO: enforce INSTALL_ALLOW_TEST
// TODO: enforce INSTALL_ALLOW_DOWNGRADE
@@ -708,6 +725,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// entries like "lost+found".
if (file.isDirectory()) return false;
if (file.getName().endsWith(REMOVE_MARKER_EXTENSION)) return false;
+ if (isAppMetadata(file)) return false;
if (DexMetadataHelper.isDexMetadataFile(file)) return false;
if (VerityUtils.isFsveritySignatureFile(file)) return false;
if (ApkChecksums.isDigestOrDigestSignatureFile(file)) return false;
@@ -1434,6 +1452,61 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
+ private File getTmpAppMetadataFile() {
+ return new File(Environment.getDataAppDirectory(params.volumeUuid),
+ sessionId + "-" + APP_METADATA_FILE_NAME);
+ }
+
+ private File getStagedAppMetadataFile() {
+ File file = new File(stageDir, APP_METADATA_FILE_NAME);
+ return file.exists() ? file : null;
+ }
+
+ private static boolean isAppMetadata(String name) {
+ return name.endsWith(APP_METADATA_FILE_NAME);
+ }
+
+ private static boolean isAppMetadata(File file) {
+ return isAppMetadata(file.getName());
+ }
+
+ @Override
+ public ParcelFileDescriptor getAppMetadataFd() {
+ assertCallerIsOwnerOrRoot();
+ synchronized (mLock) {
+ assertPreparedAndNotCommittedOrDestroyedLocked("openRead");
+ try {
+ return openReadInternalLocked(APP_METADATA_FILE_NAME);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
+ }
+ }
+
+ private static long getAppMetadataSizeLimit() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ PROPERTY_APP_METADATA_BYTE_SIZE_LIMIT, DEFAULT_APP_METADATA_BYTE_SIZE_LIMIT);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor openWriteAppMetadata() {
+ assertCallerIsOwnerOrRoot();
+ synchronized (mLock) {
+ assertPreparedAndNotSealedLocked("openWriteAppMetadata");
+ }
+ try {
+ return doWriteInternal(APP_METADATA_FILE_NAME, /* offsetBytes= */ 0,
+ /* lengthBytes= */ -1, null);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
+ }
+
@Override
public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
assertCanWrite(false);
@@ -1705,6 +1778,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
+ File appMetadataFile = getStagedAppMetadataFile();
+ if (appMetadataFile != null) {
+ long sizeLimit = getAppMetadataSizeLimit();
+ if (appMetadataFile.length() > sizeLimit) {
+ appMetadataFile.delete();
+ throw new IllegalArgumentException(
+ "App metadata size exceeds the maximum allowed limit of " + sizeLimit);
+ }
+ if (isIncrementalInstallation()) {
+ // Incremental requires stageDir to be empty so move the app metadata file to a
+ // temporary location and move back after commit.
+ appMetadataFile.renameTo(getTmpAppMetadataFile());
+ }
+ }
+
dispatchSessionSealed();
}
@@ -2802,7 +2890,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
final List<File> addedFiles = getAddedApksLocked();
- if (addedFiles.isEmpty() && removeSplitList.size() == 0) {
+ if (addedFiles.isEmpty()
+ && (removeSplitList.size() == 0 || getStagedAppMetadataFile() != null)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
TextUtils.formatSimple("Session: %d. No packages staged in %s", sessionId,
stageDir.getAbsolutePath()));
@@ -2899,10 +2988,27 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
- if (isIncrementalInstallation() && !isIncrementalInstallationAllowed(mPackageName)) {
- throw new PackageManagerException(
- PackageManager.INSTALL_FAILED_SESSION_INVALID,
- "Incremental installation of this package is not allowed.");
+ if (isIncrementalInstallation()) {
+ if (!isIncrementalInstallationAllowed(mPackageName)) {
+ throw new PackageManagerException(
+ PackageManager.INSTALL_FAILED_SESSION_INVALID,
+ "Incremental installation of this package is not allowed.");
+ }
+ // Since we moved the staged app metadata file so that incfs can be initialized, lets
+ // now move it back.
+ File appMetadataFile = getTmpAppMetadataFile();
+ if (appMetadataFile.exists()) {
+ final IncrementalFileStorages incrementalFileStorages =
+ getIncrementalFileStorages();
+ try {
+ incrementalFileStorages.makeFile(APP_METADATA_FILE_NAME,
+ Files.readAllBytes(appMetadataFile.toPath()));
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write app metadata to incremental storage", e);
+ } finally {
+ appMetadataFile.delete();
+ }
+ }
}
if (mInstallerUid != mOriginalInstallerUid) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 759ec67cfba8..edc6b4a085f6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -15,6 +15,7 @@
package com.android.server.pm;
+import static android.Manifest.permission.GET_APP_METADATA;
import static android.Manifest.permission.MANAGE_DEVICE_ADMINS;
import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
import static android.app.AppOpsManager.MODE_IGNORED;
@@ -559,6 +560,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
static final String RANDOM_DIR_PREFIX = "~~";
static final char RANDOM_CODEPATH_PREFIX = '-';
+ static final String APP_METADATA_FILE_NAME = "app.metadata";
+
final Handler mHandler;
final Handler mBackgroundHandler;
@@ -5058,6 +5061,20 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
@Override
+ public String getAppMetadataPath(String packageName, int userId) {
+ mContext.enforceCallingOrSelfPermission(GET_APP_METADATA, "getAppMetadataPath");
+ final int callingUid = Binder.getCallingUid();
+ final Computer snapshot = snapshotComputer();
+ final PackageStateInternal ps = snapshot.getPackageStateForInstalledAndFiltered(
+ packageName, callingUid, userId);
+ if (ps == null) {
+ throw new ParcelableException(
+ new PackageManager.NameNotFoundException(packageName));
+ }
+ return new File(ps.getPathString(), APP_METADATA_FILE_NAME).getAbsolutePath();
+ }
+
+ @Override
public String getPermissionControllerPackageName() {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);