diff options
| -rw-r--r-- | core/api/current.txt | 2 | ||||
| -rw-r--r-- | core/api/system-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/app/ApplicationPackageManager.java | 32 | ||||
| -rw-r--r-- | core/java/android/content/pm/IPackageInstallerSession.aidl | 3 | ||||
| -rw-r--r-- | core/java/android/content/pm/IPackageManager.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/content/pm/PackageInstaller.java | 60 | ||||
| -rw-r--r-- | core/java/android/content/pm/PackageManager.java | 18 | ||||
| -rw-r--r-- | core/res/AndroidManifest.xml | 5 | ||||
| -rw-r--r-- | packages/Shell/AndroidManifest.xml | 3 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/PackageInstallerSession.java | 116 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/PackageManagerService.java | 17 |
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); |