| /* |
| * Copyright (C) 2021 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 com.android.server.pm; |
| |
| import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; |
| import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_PERMISSION_GROUP; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION_GROUP; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_SESSION_INVALID; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED; |
| import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; |
| import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE; |
| import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP; |
| import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; |
| import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN; |
| import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4; |
| import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile; |
| import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED; |
| import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; |
| import static android.os.incremental.IncrementalManager.isIncrementalPath; |
| import static android.os.storage.StorageManager.FLAG_STORAGE_CE; |
| import static android.os.storage.StorageManager.FLAG_STORAGE_DE; |
| import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL; |
| |
| import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; |
| import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet; |
| import static com.android.server.pm.InstructionSets.getPreferredInstructionSet; |
| import static com.android.server.pm.PackageManagerService.DEBUG_BACKUP; |
| import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION; |
| import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL; |
| import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING; |
| import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE; |
| import static com.android.server.pm.PackageManagerService.DEBUG_UPGRADE; |
| import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY; |
| import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY; |
| import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; |
| import static com.android.server.pm.PackageManagerService.POST_INSTALL; |
| import static com.android.server.pm.PackageManagerService.PRECOMPILE_LAYOUTS; |
| import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX; |
| import static com.android.server.pm.PackageManagerService.SCAN_AS_FULL_APP; |
| import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP; |
| import static com.android.server.pm.PackageManagerService.SCAN_AS_ODM; |
| import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM; |
| import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED; |
| import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT; |
| import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM; |
| import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT; |
| import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR; |
| import static com.android.server.pm.PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD; |
| import static com.android.server.pm.PackageManagerService.SCAN_BOOTING; |
| import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP; |
| import static com.android.server.pm.PackageManagerService.SCAN_DROP_CACHE; |
| import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE; |
| import static com.android.server.pm.PackageManagerService.SCAN_IGNORE_FROZEN; |
| import static com.android.server.pm.PackageManagerService.SCAN_INITIAL; |
| import static com.android.server.pm.PackageManagerService.SCAN_MOVE; |
| import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL; |
| import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX; |
| import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN; |
| import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE; |
| import static com.android.server.pm.PackageManagerService.TAG; |
| import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures; |
| import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures; |
| import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists; |
| import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride; |
| import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; |
| import static com.android.server.pm.SharedUidMigration.BEST_EFFORT; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.UserIdInt; |
| import android.app.AppOpsManager; |
| import android.app.ApplicationPackageManager; |
| import android.app.backup.IBackupManager; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentSender; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.DataLoaderType; |
| import android.content.pm.IPackageInstallObserver2; |
| import android.content.pm.PackageChangeEvent; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageInfoLite; |
| import android.content.pm.PackageInstaller; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PermissionGroupInfo; |
| import android.content.pm.PermissionInfo; |
| import android.content.pm.SharedLibraryInfo; |
| import android.content.pm.Signature; |
| import android.content.pm.SigningDetails; |
| import android.content.pm.VerifierInfo; |
| import android.content.pm.dex.DexMetadataHelper; |
| import android.content.pm.parsing.result.ParseResult; |
| import android.content.pm.parsing.result.ParseTypeImpl; |
| import android.net.Uri; |
| import android.os.Binder; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.Environment; |
| import android.os.Message; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.SystemProperties; |
| import android.os.Trace; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.os.incremental.IncrementalManager; |
| import android.os.incremental.IncrementalStorage; |
| import android.os.storage.StorageManager; |
| import android.os.storage.VolumeInfo; |
| import android.stats.storage.StorageEnums; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.EventLog; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.content.F2fsUtils; |
| import com.android.internal.content.InstallLocationUtils; |
| import com.android.internal.security.VerityUtils; |
| import com.android.internal.util.ArrayUtils; |
| import com.android.internal.util.CollectionUtils; |
| import com.android.internal.util.FrameworkStatsLog; |
| import com.android.server.EventLogTags; |
| import com.android.server.pm.dex.ArtManagerService; |
| import com.android.server.pm.dex.DexManager; |
| import com.android.server.pm.dex.DexoptOptions; |
| import com.android.server.pm.dex.ViewCompiler; |
| import com.android.server.pm.parsing.PackageCacher; |
| import com.android.server.pm.parsing.PackageParser2; |
| import com.android.server.pm.parsing.pkg.AndroidPackage; |
| import com.android.server.pm.parsing.pkg.AndroidPackageUtils; |
| import com.android.server.pm.parsing.pkg.ParsedPackage; |
| import com.android.server.pm.permission.Permission; |
| import com.android.server.pm.permission.PermissionManagerServiceInternal; |
| import com.android.server.pm.pkg.PackageStateInternal; |
| import com.android.server.pm.pkg.component.ComponentMutateUtils; |
| import com.android.server.pm.pkg.component.ParsedInstrumentation; |
| import com.android.server.pm.pkg.component.ParsedPermission; |
| import com.android.server.pm.pkg.component.ParsedPermissionGroup; |
| import com.android.server.pm.pkg.parsing.ParsingPackageUtils; |
| import com.android.server.rollback.RollbackManagerInternal; |
| import com.android.server.utils.WatchedArrayMap; |
| import com.android.server.utils.WatchedLongSparseArray; |
| |
| import dalvik.system.VMRuntime; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.security.DigestException; |
| import java.security.DigestInputStream; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.PublicKey; |
| import java.security.cert.CertificateException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ExecutorService; |
| |
| final class InstallPackageHelper { |
| private final PackageManagerService mPm; |
| private final AppDataHelper mAppDataHelper; |
| private final BroadcastHelper mBroadcastHelper; |
| private final RemovePackageHelper mRemovePackageHelper; |
| private final IncrementalManager mIncrementalManager; |
| private final ApexManager mApexManager; |
| private final DexManager mDexManager; |
| private final ArtManagerService mArtManagerService; |
| private final Context mContext; |
| private final PackageDexOptimizer mPackageDexOptimizer; |
| private final PackageAbiHelper mPackageAbiHelper; |
| private final ViewCompiler mViewCompiler; |
| private final SharedLibrariesImpl mSharedLibraries; |
| private final PackageManagerServiceInjector mInjector; |
| |
| // TODO(b/198166813): remove PMS dependency |
| InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) { |
| mPm = pm; |
| mInjector = pm.mInjector; |
| mAppDataHelper = appDataHelper; |
| mBroadcastHelper = new BroadcastHelper(pm.mInjector); |
| mRemovePackageHelper = new RemovePackageHelper(pm); |
| mIncrementalManager = pm.mInjector.getIncrementalManager(); |
| mApexManager = pm.mInjector.getApexManager(); |
| mDexManager = pm.mInjector.getDexManager(); |
| mArtManagerService = pm.mInjector.getArtManagerService(); |
| mContext = pm.mInjector.getContext(); |
| mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer(); |
| mPackageAbiHelper = pm.mInjector.getAbiHelper(); |
| mViewCompiler = pm.mInjector.getViewCompiler(); |
| mSharedLibraries = pm.mInjector.getSharedLibrariesImpl(); |
| } |
| |
| InstallPackageHelper(PackageManagerService pm) { |
| this(pm, new AppDataHelper(pm)); |
| } |
| |
| /** |
| * Commits the package scan and modifies system state. |
| * <p><em>WARNING:</em> The method may throw an exception in the middle |
| * of committing the package, leaving the system in an inconsistent state. |
| * This needs to be fixed so, once we get to this point, no errors are |
| * possible and the system is not left in an inconsistent state. |
| */ |
| @GuardedBy({"mPm.mLock", "mPm.mInstallLock"}) |
| public AndroidPackage commitReconciledScanResultLocked( |
| @NonNull ReconciledPackage reconciledPkg, int[] allUsers) { |
| final ScanResult result = reconciledPkg.mScanResult; |
| final ScanRequest request = result.mRequest; |
| // TODO(b/135203078): Move this even further away |
| ParsedPackage parsedPackage = request.mParsedPackage; |
| if ("android".equals(parsedPackage.getPackageName())) { |
| // TODO(b/135203078): Move this to initial parse |
| parsedPackage.setVersionCode(mPm.getSdkVersion()) |
| .setVersionCodeMajor(0); |
| } |
| |
| final AndroidPackage oldPkg = request.mOldPkg; |
| final @ParsingPackageUtils.ParseFlags int parseFlags = request.mParseFlags; |
| final @PackageManagerService.ScanFlags int scanFlags = request.mScanFlags; |
| final PackageSetting oldPkgSetting = request.mOldPkgSetting; |
| final PackageSetting originalPkgSetting = request.mOriginalPkgSetting; |
| final UserHandle user = request.mUser; |
| final String realPkgName = request.mRealPkgName; |
| final List<String> changedAbiCodePath = result.mChangedAbiCodePath; |
| final PackageSetting pkgSetting; |
| if (request.mPkgSetting != null) { |
| SharedUserSetting requestSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr( |
| request.mPkgSetting); |
| SharedUserSetting resultSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr( |
| result.mPkgSetting); |
| if (requestSharedUserSetting != null |
| && requestSharedUserSetting != resultSharedUserSetting) { |
| // shared user changed, remove from old shared user |
| requestSharedUserSetting.removePackage(request.mPkgSetting); |
| // Prune unused SharedUserSetting |
| if (mPm.mSettings.checkAndPruneSharedUserLPw(requestSharedUserSetting, false)) { |
| // Set the app ID in removed info for UID_REMOVED broadcasts |
| if (reconciledPkg.mInstallResult != null |
| && reconciledPkg.mInstallResult.mRemovedInfo != null) { |
| reconciledPkg.mInstallResult.mRemovedInfo.mRemovedAppId = |
| requestSharedUserSetting.mAppId; |
| } |
| } |
| } |
| } |
| if (result.mExistingSettingCopied) { |
| pkgSetting = request.mPkgSetting; |
| pkgSetting.updateFrom(result.mPkgSetting); |
| } else { |
| pkgSetting = result.mPkgSetting; |
| if (originalPkgSetting != null) { |
| mPm.mSettings.addRenamedPackageLPw( |
| AndroidPackageUtils.getRealPackageOrNull(parsedPackage), |
| originalPkgSetting.getPackageName()); |
| mPm.mTransferredPackages.add(originalPkgSetting.getPackageName()); |
| } else { |
| mPm.mSettings.removeRenamedPackageLPw(parsedPackage.getPackageName()); |
| } |
| } |
| SharedUserSetting sharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(pkgSetting); |
| if (sharedUserSetting != null) { |
| sharedUserSetting.addPackage(pkgSetting); |
| if (parsedPackage.isLeavingSharedUid() |
| && SharedUidMigration.applyStrategy(BEST_EFFORT) |
| && sharedUserSetting.isSingleUser()) { |
| // Attempt the transparent shared UID migration |
| mPm.mSettings.convertSharedUserSettingsLPw(sharedUserSetting); |
| } |
| } |
| if (reconciledPkg.mInstallArgs != null |
| && reconciledPkg.mInstallArgs.mForceQueryableOverride) { |
| pkgSetting.setForceQueryableOverride(true); |
| } |
| |
| // If this is part of a standard install, set the initiating package name, else rely on |
| // previous device state. |
| if (reconciledPkg.mInstallArgs != null) { |
| InstallSource installSource = reconciledPkg.mInstallArgs.mInstallSource; |
| if (installSource.initiatingPackageName != null) { |
| final PackageSetting ips = mPm.mSettings.getPackageLPr( |
| installSource.initiatingPackageName); |
| if (ips != null) { |
| installSource = installSource.setInitiatingPackageSignatures( |
| ips.getSignatures()); |
| } |
| } |
| pkgSetting.setInstallSource(installSource); |
| } |
| |
| // TODO(toddke): Consider a method specifically for modifying the Package object |
| // post scan; or, moving this stuff out of the Package object since it has nothing |
| // to do with the package on disk. |
| // We need to have this here because addUserToSettingLPw() is sometimes responsible |
| // for creating the application ID. If we did this earlier, we would be saving the |
| // correct ID. |
| parsedPackage.setUid(pkgSetting.getAppId()); |
| final AndroidPackage pkg = parsedPackage.hideAsFinal(); |
| |
| mPm.mSettings.writeUserRestrictionsLPw(pkgSetting, oldPkgSetting); |
| |
| if (realPkgName != null) { |
| mPm.mTransferredPackages.add(pkg.getPackageName()); |
| } |
| |
| if (reconciledPkg.mCollectedSharedLibraryInfos != null |
| || (oldPkgSetting != null && oldPkgSetting.getUsesLibraryInfos() != null)) { |
| // Reconcile if the new package or the old package uses shared libraries. |
| // It is possible that the old package uses shared libraries but the new one doesn't. |
| mSharedLibraries.executeSharedLibrariesUpdateLPw(pkg, pkgSetting, null, null, |
| reconciledPkg.mCollectedSharedLibraryInfos, allUsers); |
| } |
| |
| final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService(); |
| if (reconciledPkg.mRemoveAppKeySetData) { |
| ksms.removeAppKeySetDataLPw(pkg.getPackageName()); |
| } |
| if (reconciledPkg.mSharedUserSignaturesChanged) { |
| sharedUserSetting.signaturesChanged = Boolean.TRUE; |
| sharedUserSetting.signatures.mSigningDetails = reconciledPkg.mSigningDetails; |
| } |
| pkgSetting.setSigningDetails(reconciledPkg.mSigningDetails); |
| |
| if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) { |
| for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) { |
| final String codePathString = changedAbiCodePath.get(i); |
| try { |
| mPm.mInstaller.rmdex(codePathString, |
| getDexCodeInstructionSet(getPreferredInstructionSet())); |
| } catch (Installer.InstallerException ignored) { |
| } |
| } |
| } |
| |
| final int userId = user == null ? 0 : user.getIdentifier(); |
| // Modify state for the given package setting |
| commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags, |
| (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg); |
| if (pkgSetting.getInstantApp(userId)) { |
| mPm.mInstantAppRegistry.addInstantApp(userId, pkgSetting.getAppId()); |
| } |
| |
| if (!IncrementalManager.isIncrementalPath(pkgSetting.getPathString())) { |
| pkgSetting.setLoadingProgress(1f); |
| } |
| |
| return pkg; |
| } |
| |
| /** |
| * Adds a scanned package to the system. When this method is finished, the package will |
| * be available for query, resolution, etc... |
| */ |
| private void commitPackageSettings(@NonNull AndroidPackage pkg, @Nullable AndroidPackage oldPkg, |
| @NonNull PackageSetting pkgSetting, @Nullable PackageSetting oldPkgSetting, |
| final @PackageManagerService.ScanFlags int scanFlags, boolean chatty, |
| ReconciledPackage reconciledPkg) { |
| final String pkgName = pkg.getPackageName(); |
| if (mPm.mCustomResolverComponentName != null |
| && mPm.mCustomResolverComponentName.getPackageName().equals(pkg.getPackageName())) { |
| mPm.setUpCustomResolverActivity(pkg, pkgSetting); |
| } |
| |
| if (pkg.getPackageName().equals("android")) { |
| mPm.setPlatformPackage(pkg, pkgSetting); |
| } |
| |
| ArrayList<AndroidPackage> clientLibPkgs = null; |
| // writer |
| synchronized (mPm.mLock) { |
| if (!ArrayUtils.isEmpty(reconciledPkg.mAllowedSharedLibraryInfos)) { |
| for (SharedLibraryInfo info : reconciledPkg.mAllowedSharedLibraryInfos) { |
| mSharedLibraries.commitSharedLibraryInfoLPw(info); |
| } |
| final Map<String, AndroidPackage> combinedSigningDetails = |
| reconciledPkg.getCombinedAvailablePackages(); |
| try { |
| // Shared libraries for the package need to be updated. |
| mSharedLibraries.updateSharedLibrariesLPw(pkg, pkgSetting, null, null, |
| combinedSigningDetails); |
| } catch (PackageManagerException e) { |
| Slog.e(TAG, "updateSharedLibrariesLPr failed: ", e); |
| } |
| // Update all applications that use this library. Skip when booting |
| // since this will be done after all packages are scaned. |
| if ((scanFlags & SCAN_BOOTING) == 0) { |
| clientLibPkgs = mSharedLibraries.updateAllSharedLibrariesLPw(pkg, pkgSetting, |
| combinedSigningDetails); |
| } |
| } |
| } |
| if (reconciledPkg.mInstallResult != null) { |
| reconciledPkg.mInstallResult.mLibraryConsumers = clientLibPkgs; |
| } |
| |
| if ((scanFlags & SCAN_BOOTING) != 0) { |
| // No apps can run during boot scan, so they don't need to be frozen |
| } else if ((scanFlags & SCAN_DONT_KILL_APP) != 0) { |
| // Caller asked to not kill app, so it's probably not frozen |
| } else if ((scanFlags & SCAN_IGNORE_FROZEN) != 0) { |
| // Caller asked us to ignore frozen check for some reason; they |
| // probably didn't know the package name |
| } else { |
| // We're doing major surgery on this package, so it better be frozen |
| // right now to keep it from launching |
| mPm.snapshotComputer().checkPackageFrozen(pkgName); |
| } |
| |
| final boolean isReplace = |
| reconciledPkg.mPrepareResult != null && reconciledPkg.mPrepareResult.mReplace; |
| // Also need to kill any apps that are dependent on the library, except the case of |
| // installation of new version static shared library. |
| if (clientLibPkgs != null) { |
| if (pkg.getStaticSharedLibName() == null || isReplace) { |
| for (int i = 0; i < clientLibPkgs.size(); i++) { |
| AndroidPackage clientPkg = clientLibPkgs.get(i); |
| mPm.killApplication(clientPkg.getPackageName(), |
| clientPkg.getUid(), "update lib"); |
| } |
| } |
| } |
| |
| // writer |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings"); |
| |
| synchronized (mPm.mLock) { |
| // We don't expect installation to fail beyond this point |
| // Add the new setting to mSettings |
| mPm.mSettings.insertPackageSettingLPw(pkgSetting, pkg); |
| // Add the new setting to mPackages |
| mPm.mPackages.put(pkg.getPackageName(), pkg); |
| if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) { |
| mApexManager.registerApkInApex(pkg); |
| } |
| |
| // Add the package's KeySets to the global KeySetManagerService |
| KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService(); |
| ksms.addScannedPackageLPw(pkg); |
| |
| final Computer snapshot = mPm.snapshotComputer(); |
| mPm.mComponentResolver.addAllComponents(pkg, chatty, mPm.mSetupWizardPackage, snapshot); |
| mPm.mAppsFilter.addPackage(snapshot, pkgSetting, isReplace); |
| mPm.addAllPackageProperties(pkg); |
| |
| if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) { |
| mPm.mDomainVerificationManager.addPackage(pkgSetting); |
| } else { |
| mPm.mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting); |
| } |
| |
| int collectionSize = ArrayUtils.size(pkg.getInstrumentations()); |
| StringBuilder r = null; |
| int i; |
| for (i = 0; i < collectionSize; i++) { |
| ParsedInstrumentation a = pkg.getInstrumentations().get(i); |
| ComponentMutateUtils.setPackageName(a, pkg.getPackageName()); |
| mPm.addInstrumentation(a.getComponentName(), a); |
| if (chatty) { |
| if (r == null) { |
| r = new StringBuilder(256); |
| } else { |
| r.append(' '); |
| } |
| r.append(a.getName()); |
| } |
| } |
| if (r != null) { |
| if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Instrumentation: " + r); |
| } |
| |
| final List<String> protectedBroadcasts = pkg.getProtectedBroadcasts(); |
| if (!protectedBroadcasts.isEmpty()) { |
| synchronized (mPm.mProtectedBroadcasts) { |
| mPm.mProtectedBroadcasts.addAll(protectedBroadcasts); |
| } |
| } |
| |
| mPm.mPermissionManager.onPackageAdded(pkg, |
| (scanFlags & SCAN_AS_INSTANT_APP) != 0, oldPkg); |
| } |
| |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| |
| public int installExistingPackageAsUser(@Nullable String packageName, @UserIdInt int userId, |
| @PackageManager.InstallFlags int installFlags, |
| @PackageManager.InstallReason int installReason, |
| @Nullable List<String> allowlistedRestrictedPermissions, |
| @Nullable IntentSender intentSender) { |
| if (DEBUG_INSTALL) { |
| Log.v(TAG, "installExistingPackageAsUser package=" + packageName + " userId=" + userId |
| + " installFlags=" + installFlags + " installReason=" + installReason |
| + " allowlistedRestrictedPermissions=" + allowlistedRestrictedPermissions); |
| } |
| |
| final int callingUid = Binder.getCallingUid(); |
| if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES) |
| != PackageManager.PERMISSION_GRANTED |
| && mContext.checkCallingOrSelfPermission( |
| android.Manifest.permission.INSTALL_EXISTING_PACKAGES) |
| != PackageManager.PERMISSION_GRANTED) { |
| throw new SecurityException("Neither user " + callingUid + " nor current process has " |
| + android.Manifest.permission.INSTALL_PACKAGES + "."); |
| } |
| PackageSetting pkgSetting; |
| final Computer preLockSnapshot = mPm.snapshotComputer(); |
| preLockSnapshot.enforceCrossUserPermission(callingUid, userId, |
| true /* requireFullPermission */, true /* checkShell */, |
| "installExistingPackage for user " + userId); |
| if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) { |
| return PackageManager.INSTALL_FAILED_USER_RESTRICTED; |
| } |
| |
| final long callingId = Binder.clearCallingIdentity(); |
| try { |
| boolean installed = false; |
| final boolean instantApp = |
| (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0; |
| final boolean fullApp = |
| (installFlags & PackageManager.INSTALL_FULL_APP) != 0; |
| |
| // writer |
| synchronized (mPm.mLock) { |
| final Computer snapshot = mPm.snapshotComputer(); |
| pkgSetting = mPm.mSettings.getPackageLPr(packageName); |
| if (pkgSetting == null) { |
| return PackageManager.INSTALL_FAILED_INVALID_URI; |
| } |
| if (!snapshot.canViewInstantApps(callingUid, UserHandle.getUserId(callingUid))) { |
| // only allow the existing package to be used if it's installed as a full |
| // application for at least one user |
| boolean installAllowed = false; |
| for (int checkUserId : mPm.mUserManager.getUserIds()) { |
| installAllowed = !pkgSetting.getInstantApp(checkUserId); |
| if (installAllowed) { |
| break; |
| } |
| } |
| if (!installAllowed) { |
| return PackageManager.INSTALL_FAILED_INVALID_URI; |
| } |
| } |
| if (!pkgSetting.getInstalled(userId)) { |
| pkgSetting.setInstalled(true, userId); |
| pkgSetting.setHidden(false, userId); |
| pkgSetting.setInstallReason(installReason, userId); |
| pkgSetting.setUninstallReason(PackageManager.UNINSTALL_REASON_UNKNOWN, userId); |
| pkgSetting.setFirstInstallTime(System.currentTimeMillis(), userId); |
| mPm.mSettings.writePackageRestrictionsLPr(userId); |
| mPm.mSettings.writeKernelMappingLPr(pkgSetting); |
| installed = true; |
| } else if (fullApp && pkgSetting.getInstantApp(userId)) { |
| // upgrade app from instant to full; we don't allow app downgrade |
| installed = true; |
| } |
| ScanPackageUtils.setInstantAppForUser(mPm.mInjector, pkgSetting, userId, instantApp, |
| fullApp); |
| } |
| |
| if (installed) { |
| if (pkgSetting.getPkg() != null) { |
| final PermissionManagerServiceInternal.PackageInstalledParams.Builder |
| permissionParamsBuilder = |
| new PermissionManagerServiceInternal.PackageInstalledParams.Builder(); |
| if ((installFlags & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) |
| != 0) { |
| permissionParamsBuilder.setAllowlistedRestrictedPermissions( |
| pkgSetting.getPkg().getRequestedPermissions()); |
| } |
| mPm.mPermissionManager.onPackageInstalled(pkgSetting.getPkg(), |
| Process.INVALID_UID /* previousAppId */, |
| permissionParamsBuilder.build(), userId); |
| |
| synchronized (mPm.mInstallLock) { |
| // We don't need to freeze for a brand new install |
| mAppDataHelper.prepareAppDataAfterInstallLIF(pkgSetting.getPkg()); |
| } |
| } |
| mPm.sendPackageAddedForUser(mPm.snapshotComputer(), packageName, pkgSetting, userId, |
| DataLoaderType.NONE); |
| synchronized (mPm.mLock) { |
| mPm.updateSequenceNumberLP(pkgSetting, new int[]{ userId }); |
| } |
| // start async restore with no post-install since we finish install here |
| PackageInstalledInfo res = new PackageInstalledInfo( |
| PackageManager.INSTALL_SUCCEEDED); |
| res.mPkg = pkgSetting.getPkg(); |
| res.mNewUsers = new int[]{ userId }; |
| |
| PostInstallData postInstallData = |
| new PostInstallData(null, res, () -> { |
| mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName, |
| userId); |
| if (intentSender != null) { |
| onRestoreComplete(res.mReturnCode, mContext, intentSender); |
| } |
| }); |
| restoreAndPostInstall(userId, res, postInstallData); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(callingId); |
| } |
| |
| return PackageManager.INSTALL_SUCCEEDED; |
| } |
| |
| private static void onRestoreComplete(int returnCode, Context context, IntentSender target) { |
| Intent fillIn = new Intent(); |
| fillIn.putExtra(PackageInstaller.EXTRA_STATUS, |
| PackageManager.installStatusToPublicStatus(returnCode)); |
| try { |
| target.sendIntent(context, 0, fillIn, null, null); |
| } catch (IntentSender.SendIntentException ignored) { |
| } |
| } |
| |
| /** @param data Post-install is performed only if this is non-null. */ |
| public void restoreAndPostInstall( |
| int userId, PackageInstalledInfo res, @Nullable PostInstallData data) { |
| if (DEBUG_INSTALL) { |
| Log.v(TAG, "restoreAndPostInstall userId=" + userId + " package=" + res.mPkg); |
| } |
| |
| // A restore should be requested at this point if (a) the install |
| // succeeded, (b) the operation is not an update. |
| final boolean update = res.mRemovedInfo != null |
| && res.mRemovedInfo.mRemovedPackage != null; |
| boolean doRestore = !update && res.mPkg != null; |
| |
| // Set up the post-install work request bookkeeping. This will be used |
| // and cleaned up by the post-install event handling regardless of whether |
| // there's a restore pass performed. Token values are >= 1. |
| int token; |
| if (mPm.mNextInstallToken < 0) mPm.mNextInstallToken = 1; |
| token = mPm.mNextInstallToken++; |
| if (data != null) { |
| mPm.mRunningInstalls.put(token, data); |
| } else if (DEBUG_INSTALL) { |
| Log.v(TAG, "No post-install required for " + token); |
| } |
| |
| if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token); |
| |
| if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) { |
| // Pass responsibility to the Backup Manager. It will perform a |
| // restore if appropriate, then pass responsibility back to the |
| // Package Manager to run the post-install observer callbacks |
| // and broadcasts. |
| if (res.mFreezer != null) { |
| res.mFreezer.close(); |
| } |
| doRestore = performBackupManagerRestore(userId, token, res); |
| } |
| |
| // If this is an update to a package that might be potentially downgraded, then we |
| // need to check with the rollback manager whether there's any userdata that might |
| // need to be snapshotted or restored for the package. |
| // |
| // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL. |
| if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) { |
| doRestore = performRollbackManagerRestore(userId, token, res, data); |
| } |
| |
| if (!doRestore) { |
| // No restore possible, or the Backup Manager was mysteriously not |
| // available -- just fire the post-install work request directly. |
| if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token); |
| |
| Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token); |
| |
| Message msg = mPm.mHandler.obtainMessage(POST_INSTALL, token, 0); |
| mPm.mHandler.sendMessage(msg); |
| } |
| } |
| |
| /** |
| * Perform Backup Manager restore for a given {@link PackageInstalledInfo}. |
| * Returns whether the restore successfully completed. |
| */ |
| private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) { |
| IBackupManager iBackupManager = mInjector.getIBackupManager(); |
| if (iBackupManager != null) { |
| // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM |
| // in the BackupManager. USER_ALL is used in compatibility tests. |
| if (userId == UserHandle.USER_ALL) { |
| userId = UserHandle.USER_SYSTEM; |
| } |
| if (DEBUG_INSTALL) { |
| Log.v(TAG, "token " + token + " to BM for possible restore for user " + userId); |
| } |
| Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token); |
| try { |
| if (iBackupManager.isUserReadyForBackup(userId)) { |
| iBackupManager.restoreAtInstallForUser( |
| userId, res.mPkg.getPackageName(), token); |
| } else { |
| Slog.w(TAG, "User " + userId + " is not ready. Restore at install " |
| + "didn't take place."); |
| return false; |
| } |
| } catch (RemoteException e) { |
| // can't happen; the backup manager is local |
| } catch (Exception e) { |
| Slog.e(TAG, "Exception trying to enqueue restore", e); |
| return false; |
| } |
| } else { |
| Slog.e(TAG, "Backup Manager not found!"); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Perform Rollback Manager restore for a given {@link PackageInstalledInfo}. |
| * Returns whether the restore successfully completed. |
| */ |
| private boolean performRollbackManagerRestore(int userId, int token, PackageInstalledInfo res, |
| PostInstallData data) { |
| final String packageName = res.mPkg.getPackageName(); |
| final int[] allUsers = mPm.mUserManager.getUserIds(); |
| final int[] installedUsers; |
| |
| final PackageSetting ps; |
| int appId = -1; |
| long ceDataInode = -1; |
| synchronized (mPm.mLock) { |
| ps = mPm.mSettings.getPackageLPr(packageName); |
| if (ps != null) { |
| appId = ps.getAppId(); |
| ceDataInode = ps.getCeDataInode(userId); |
| } |
| |
| // NOTE: We ignore the user specified in the InstallParam because we know this is |
| // an update, and hence need to restore data for all installed users. |
| installedUsers = ps.queryInstalledUsers(allUsers, true); |
| } |
| |
| boolean doSnapshotOrRestore = data != null && data.args != null |
| && ((data.args.mInstallFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 |
| || (data.args.mInstallFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0); |
| |
| if (ps != null && doSnapshotOrRestore) { |
| final String seInfo = AndroidPackageUtils.getSeInfo(res.mPkg, ps); |
| final RollbackManagerInternal rollbackManager = |
| mInjector.getLocalService(RollbackManagerInternal.class); |
| rollbackManager.snapshotAndRestoreUserData(packageName, |
| UserHandle.toUserHandles(installedUsers), appId, ceDataInode, seInfo, token); |
| return true; |
| } |
| return false; |
| } |
| |
| public void processInstallRequests(boolean success, List<InstallRequest> installRequests) { |
| List<InstallRequest> apexInstallRequests = new ArrayList<>(); |
| List<InstallRequest> apkInstallRequests = new ArrayList<>(); |
| for (InstallRequest request : installRequests) { |
| if ((request.mArgs.mInstallFlags & PackageManager.INSTALL_APEX) != 0) { |
| apexInstallRequests.add(request); |
| } else { |
| apkInstallRequests.add(request); |
| } |
| } |
| // Note: supporting multi package install of both APEXes and APKs might requir some |
| // thinking to ensure atomicity of the install. |
| if (!apexInstallRequests.isEmpty() && !apkInstallRequests.isEmpty()) { |
| // This should've been caught at the validation step, but for some reason wasn't. |
| throw new IllegalStateException( |
| "Attempted to do a multi package install of both APEXes and APKs"); |
| } |
| if (!apexInstallRequests.isEmpty()) { |
| if (success) { |
| // Since installApexPackages requires talking to external service (apexd), we |
| // schedule to run it async. Once it finishes, it will resume the install. |
| Thread t = new Thread(() -> installApexPackagesTraced(apexInstallRequests), |
| "installApexPackages"); |
| t.start(); |
| } else { |
| // Non-staged APEX installation failed somewhere before |
| // processInstallRequestAsync. In that case just notify the observer about the |
| // failure. |
| InstallRequest request = apexInstallRequests.get(0); |
| mPm.notifyInstallObserver(request.mInstallResult, |
| request.mArgs.mObserver); |
| } |
| return; |
| } |
| if (success) { |
| for (InstallRequest request : apkInstallRequests) { |
| request.mArgs.doPreInstall(request.mInstallResult.mReturnCode); |
| } |
| synchronized (mPm.mInstallLock) { |
| installPackagesTracedLI(apkInstallRequests); |
| } |
| for (InstallRequest request : apkInstallRequests) { |
| request.mArgs.doPostInstall( |
| request.mInstallResult.mReturnCode, request.mInstallResult.mUid); |
| } |
| } |
| for (InstallRequest request : apkInstallRequests) { |
| restoreAndPostInstall(request.mArgs.mUser.getIdentifier(), |
| request.mInstallResult, |
| new PostInstallData(request.mArgs, |
| request.mInstallResult, null)); |
| } |
| } |
| |
| private void installApexPackagesTraced(List<InstallRequest> requests) { |
| try { |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installApexPackages"); |
| installApexPackages(requests); |
| } finally { |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| } |
| |
| private void installApexPackages(List<InstallRequest> requests) { |
| if (requests.isEmpty()) { |
| return; |
| } |
| if (requests.size() != 1) { |
| throw new IllegalStateException( |
| "Only a non-staged install of a single APEX is supported"); |
| } |
| InstallRequest request = requests.get(0); |
| try { |
| // Should directory scanning logic be moved to ApexManager for better test coverage? |
| final File dir = request.mArgs.mOriginInfo.mResolvedFile; |
| final File[] apexes = dir.listFiles(); |
| if (apexes == null) { |
| throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, |
| dir.getAbsolutePath() + " is not a directory"); |
| } |
| if (apexes.length != 1) { |
| throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, |
| "Expected exactly one .apex file under " + dir.getAbsolutePath() |
| + " got: " + apexes.length); |
| } |
| try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) { |
| mApexManager.installPackage(apexes[0], packageParser); |
| } |
| } catch (PackageManagerException e) { |
| request.mInstallResult.setError("APEX installation failed", e); |
| } |
| PackageManagerService.invalidatePackageInfoCache(); |
| mPm.notifyInstallObserver(request.mInstallResult, request.mArgs.mObserver); |
| } |
| |
| @GuardedBy("mPm.mInstallLock") |
| private void installPackagesTracedLI(List<InstallRequest> requests) { |
| try { |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages"); |
| installPackagesLI(requests); |
| } finally { |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| } |
| |
| /** |
| * Installs one or more packages atomically. This operation is broken up into four phases: |
| * <ul> |
| * <li><b>Prepare</b> |
| * <br/>Analyzes any current install state, parses the package and does initial |
| * validation on it.</li> |
| * <li><b>Scan</b> |
| * <br/>Interrogates the parsed packages given the context collected in prepare.</li> |
| * <li><b>Reconcile</b> |
| * <br/>Validates scanned packages in the context of each other and the current system |
| * state to ensure that the install will be successful. |
| * <li><b>Commit</b> |
| * <br/>Commits all scanned packages and updates system state. This is the only place |
| * that system state may be modified in the install flow and all predictable errors |
| * must be determined before this phase.</li> |
| * </ul> |
| * |
| * Failure at any phase will result in a full failure to install all packages. |
| */ |
| @GuardedBy("mPm.mInstallLock") |
| private void installPackagesLI(List<InstallRequest> requests) { |
| final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size()); |
| final Map<String, InstallArgs> installArgs = new ArrayMap<>(requests.size()); |
| final Map<String, PackageInstalledInfo> installResults = new ArrayMap<>(requests.size()); |
| final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size()); |
| final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size()); |
| final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size()); |
| boolean success = false; |
| try { |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI"); |
| for (InstallRequest request : requests) { |
| // TODO(b/109941548): remove this once we've pulled everything from it and into |
| // scan, reconcile or commit. |
| final PrepareResult prepareResult; |
| try { |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage"); |
| prepareResult = |
| preparePackageLI(request.mArgs, request.mInstallResult); |
| } catch (PrepareFailure prepareFailure) { |
| request.mInstallResult.setError(prepareFailure.error, |
| prepareFailure.getMessage()); |
| request.mInstallResult.mOrigPackage = prepareFailure.mConflictingPackage; |
| request.mInstallResult.mOrigPermission = prepareFailure.mConflictingPermission; |
| return; |
| } finally { |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| request.mInstallResult.setReturnCode(PackageManager.INSTALL_SUCCEEDED); |
| request.mInstallResult.mInstallerPackageName = |
| request.mArgs.mInstallSource.installerPackageName; |
| |
| final String packageName = prepareResult.mPackageToScan.getPackageName(); |
| prepareResults.put(packageName, prepareResult); |
| installResults.put(packageName, request.mInstallResult); |
| installArgs.put(packageName, request.mArgs); |
| try { |
| final ScanResult result = scanPackageTracedLI( |
| prepareResult.mPackageToScan, prepareResult.mParseFlags, |
| prepareResult.mScanFlags, System.currentTimeMillis(), |
| request.mArgs.mUser, request.mArgs.mAbiOverride); |
| if (null != preparedScans.put(result.mPkgSetting.getPkg().getPackageName(), |
| result)) { |
| request.mInstallResult.setError( |
| PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE, |
| "Duplicate package " |
| + result.mPkgSetting.getPkg().getPackageName() |
| + " in multi-package install request."); |
| return; |
| } |
| if (!checkNoAppStorageIsConsistent( |
| result.mRequest.mOldPkg, result.mPkgSetting.getPkg())) { |
| // TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible |
| // signatures. Is there a better error code? |
| request.mInstallResult.setError( |
| INSTALL_FAILED_UPDATE_INCOMPATIBLE, |
| "Update attempted to change value of " |
| + PackageManager.PROPERTY_NO_APP_DATA_STORAGE); |
| return; |
| } |
| createdAppId.put(packageName, optimisticallyRegisterAppId(result)); |
| versionInfos.put(result.mPkgSetting.getPkg().getPackageName(), |
| mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg())); |
| } catch (PackageManagerException e) { |
| request.mInstallResult.setError("Scanning Failed.", e); |
| return; |
| } |
| } |
| ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs, |
| installResults, prepareResults, |
| Collections.unmodifiableMap(mPm.mPackages), versionInfos); |
| CommitRequest commitRequest = null; |
| synchronized (mPm.mLock) { |
| Map<String, ReconciledPackage> reconciledPackages; |
| try { |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages"); |
| reconciledPackages = ReconcilePackageUtils.reconcilePackages( |
| reconcileRequest, mSharedLibraries, |
| mPm.mSettings.getKeySetManagerService(), mPm.mSettings); |
| } catch (ReconcileFailure e) { |
| for (InstallRequest request : requests) { |
| request.mInstallResult.setError("Reconciliation failed...", e); |
| } |
| return; |
| } finally { |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| try { |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "commitPackages"); |
| commitRequest = new CommitRequest(reconciledPackages, |
| mPm.mUserManager.getUserIds()); |
| commitPackagesLocked(commitRequest); |
| success = true; |
| } finally { |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| } |
| executePostCommitSteps(commitRequest); |
| } finally { |
| if (success) { |
| for (InstallRequest request : requests) { |
| final InstallArgs args = request.mArgs; |
| if (args.mDataLoaderType != DataLoaderType.INCREMENTAL) { |
| continue; |
| } |
| if (args.mSigningDetails.getSignatureSchemeVersion() != SIGNING_BLOCK_V4) { |
| continue; |
| } |
| // For incremental installs, we bypass the verifier prior to install. Now |
| // that we know the package is valid, send a notice to the verifier with |
| // the root hash of the base.apk. |
| final String baseCodePath = request.mInstallResult.mPkg.getBaseApkPath(); |
| final String[] splitCodePaths = request.mInstallResult.mPkg.getSplitCodePaths(); |
| final Uri originUri = Uri.fromFile(args.mOriginInfo.mResolvedFile); |
| final int verificationId = mPm.mPendingVerificationToken++; |
| final String rootHashString = PackageManagerServiceUtils |
| .buildVerificationRootHashString(baseCodePath, splitCodePaths); |
| VerificationUtils.broadcastPackageVerified(verificationId, originUri, |
| PackageManager.VERIFICATION_ALLOW, rootHashString, |
| args.mDataLoaderType, args.getUser(), mContext); |
| } |
| } else { |
| for (ScanResult result : preparedScans.values()) { |
| if (createdAppId.getOrDefault(result.mRequest.mParsedPackage.getPackageName(), |
| false)) { |
| cleanUpAppIdCreation(result); |
| } |
| } |
| // TODO(b/194319951): create a more descriptive reason than unknown |
| // mark all non-failure installs as UNKNOWN so we do not treat them as success |
| for (InstallRequest request : requests) { |
| if (request.mInstallResult.mFreezer != null) { |
| request.mInstallResult.mFreezer.close(); |
| } |
| if (request.mInstallResult.mReturnCode == PackageManager.INSTALL_SUCCEEDED) { |
| request.mInstallResult.mReturnCode = PackageManager.INSTALL_UNKNOWN; |
| } |
| } |
| } |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| } |
| |
| @GuardedBy("mPm.mInstallLock") |
| private boolean checkNoAppStorageIsConsistent(AndroidPackage oldPkg, AndroidPackage newPkg) { |
| if (oldPkg == null) { |
| // New install, nothing to check against. |
| return true; |
| } |
| final PackageManager.Property curProp = |
| oldPkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE); |
| final PackageManager.Property newProp = |
| newPkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE); |
| if (curProp == null || !curProp.getBoolean()) { |
| return newProp == null || !newProp.getBoolean(); |
| } |
| return newProp != null && newProp.getBoolean(); |
| } |
| |
| @GuardedBy("mPm.mInstallLock") |
| private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res) |
| throws PrepareFailure { |
| final int installFlags = args.mInstallFlags; |
| final File tmpPackageFile = new File(args.getCodePath()); |
| final boolean onExternal = args.mVolumeUuid != null; |
| final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0); |
| final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0); |
| final boolean virtualPreload = |
| ((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0); |
| final boolean isRollback = args.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK; |
| @PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE; |
| if (args.mMoveInfo != null) { |
| // moving a complete application; perform an initial scan on the new install location |
| scanFlags |= SCAN_INITIAL; |
| } |
| if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) { |
| scanFlags |= SCAN_DONT_KILL_APP; |
| } |
| if (instantApp) { |
| scanFlags |= SCAN_AS_INSTANT_APP; |
| } |
| if (fullApp) { |
| scanFlags |= SCAN_AS_FULL_APP; |
| } |
| if (virtualPreload) { |
| scanFlags |= SCAN_AS_VIRTUAL_PRELOAD; |
| } |
| |
| if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile); |
| |
| // Validity check |
| if (instantApp && onExternal) { |
| Slog.i(TAG, "Incompatible ephemeral install; external=" + onExternal); |
| throw new PrepareFailure(PackageManager.INSTALL_FAILED_SESSION_INVALID); |
| } |
| |
| // Retrieve PackageSettings and parse package |
| @ParsingPackageUtils.ParseFlags final int parseFlags = |
| mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY |
| | ParsingPackageUtils.PARSE_ENFORCE_CODE |
| | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0); |
| |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage"); |
| final ParsedPackage parsedPackage; |
| try (PackageParser2 pp = mPm.mInjector.getPreparingPackageParser()) { |
| parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false); |
| AndroidPackageUtils.validatePackageDexMetadata(parsedPackage); |
| } catch (PackageManagerException e) { |
| throw new PrepareFailure("Failed parse during installPackageLI", e); |
| } finally { |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| |
| // Instant apps have several additional install-time checks. |
| if (instantApp) { |
| if (parsedPackage.getTargetSdkVersion() < Build.VERSION_CODES.O) { |
| Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName() |
| + " does not target at least O"); |
| throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID, |
| "Instant app package must target at least O"); |
| } |
| if (parsedPackage.getSharedUserId() != null) { |
| Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName() |
| + " may not declare sharedUserId."); |
| throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID, |
| "Instant app package may not declare a sharedUserId"); |
| } |
| } |
| |
| if (parsedPackage.isStaticSharedLibrary()) { |
| // Static shared libraries have synthetic package names |
| PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage); |
| |
| // No static shared libs on external storage |
| if (onExternal) { |
| Slog.i(TAG, "Static shared libs can only be installed on internal storage."); |
| throw new PrepareFailure(INSTALL_FAILED_INVALID_INSTALL_LOCATION, |
| "Packages declaring static-shared libs cannot be updated"); |
| } |
| } |
| |
| String pkgName = res.mName = parsedPackage.getPackageName(); |
| if (parsedPackage.isTestOnly()) { |
| if ((installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) { |
| throw new PrepareFailure(INSTALL_FAILED_TEST_ONLY, "installPackageLI"); |
| } |
| } |
| |
| // either use what we've been given or parse directly from the APK |
| if (args.mSigningDetails != SigningDetails.UNKNOWN) { |
| parsedPackage.setSigningDetails(args.mSigningDetails); |
| } else { |
| final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); |
| final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails( |
| input, parsedPackage, false /*skipVerify*/); |
| if (result.isError()) { |
| throw new PrepareFailure("Failed collect during installPackageLI", |
| result.getException()); |
| } |
| parsedPackage.setSigningDetails(result.getResult()); |
| } |
| |
| if (instantApp && parsedPackage.getSigningDetails().getSignatureSchemeVersion() |
| < SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2) { |
| Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName() |
| + " is not signed with at least APK Signature Scheme v2"); |
| throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID, |
| "Instant app package must be signed with APK Signature Scheme v2 or greater"); |
| } |
| |
| boolean systemApp = false; |
| boolean replace = false; |
| synchronized (mPm.mLock) { |
| // Check if installing already existing package |
| if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { |
| String oldName = mPm.mSettings.getRenamedPackageLPr(pkgName); |
| if (parsedPackage.getOriginalPackages().contains(oldName) |
| && mPm.mPackages.containsKey(oldName)) { |
| // This package is derived from an original package, |
| // and this device has been updating from that original |
| // name. We must continue using the original name, so |
| // rename the new package here. |
| parsedPackage.setPackageName(oldName); |
| pkgName = parsedPackage.getPackageName(); |
| replace = true; |
| if (DEBUG_INSTALL) { |
| Slog.d(TAG, "Replacing existing renamed package: oldName=" |
| + oldName + " pkgName=" + pkgName); |
| } |
| } else if (mPm.mPackages.containsKey(pkgName)) { |
| // This package, under its official name, already exists |
| // on the device; we should replace it. |
| replace = true; |
| if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing package: " + pkgName); |
| } |
| |
| if (replace) { |
| // Prevent apps opting out from runtime permissions |
| AndroidPackage oldPackage = mPm.mPackages.get(pkgName); |
| final int oldTargetSdk = oldPackage.getTargetSdkVersion(); |
| final int newTargetSdk = parsedPackage.getTargetSdkVersion(); |
| if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1 |
| && newTargetSdk <= Build.VERSION_CODES.LOLLIPOP_MR1) { |
| throw new PrepareFailure( |
| PackageManager.INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE, |
| "Package " + parsedPackage.getPackageName() |
| + " new target SDK " + newTargetSdk |
| + " doesn't support runtime permissions but the old" |
| + " target SDK " + oldTargetSdk + " does."); |
| } |
| // Prevent persistent apps from being updated |
| if (oldPackage.isPersistent() |
| && ((installFlags & PackageManager.INSTALL_STAGED) == 0)) { |
| throw new PrepareFailure(PackageManager.INSTALL_FAILED_INVALID_APK, |
| "Package " + oldPackage.getPackageName() + " is a persistent app. " |
| + "Persistent apps are not updateable."); |
| } |
| } |
| } |
| |
| PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName); |
| PackageSetting signatureCheckPs = ps; |
| |
| // SDK libs can have other major versions with different package names. |
| if (signatureCheckPs == null && parsedPackage.isSdkLibrary()) { |
| WatchedLongSparseArray<SharedLibraryInfo> libraryInfos = |
| mSharedLibraries.getSharedLibraryInfos( |
| parsedPackage.getSdkLibName()); |
| if (libraryInfos != null && libraryInfos.size() > 0) { |
| // Any existing version would do. |
| SharedLibraryInfo libraryInfo = libraryInfos.valueAt(0); |
| signatureCheckPs = mPm.mSettings.getPackageLPr(libraryInfo.getPackageName()); |
| } |
| } |
| |
| // Static shared libs have same package with different versions where |
| // we internally use a synthetic package name to allow multiple versions |
| // of the same package, therefore we need to compare signatures against |
| // the package setting for the latest library version. |
| if (parsedPackage.isStaticSharedLibrary()) { |
| SharedLibraryInfo libraryInfo = |
| mSharedLibraries.getLatestStaticSharedLibraVersionLPr(parsedPackage); |
| if (libraryInfo != null) { |
| signatureCheckPs = mPm.mSettings.getPackageLPr(libraryInfo.getPackageName()); |
| } |
| } |
| |
| if (signatureCheckPs != null) { |
| if (DEBUG_INSTALL) { |
| Slog.d(TAG, |
| "Existing package for signature checking: " + signatureCheckPs); |
| } |
| |
| // Quick validity check that we're signed correctly if updating; |
| // we'll check this again later when scanning, but we want to |
| // bail early here before tripping over redefined permissions. |
| final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService(); |
| final SharedUserSetting signatureCheckSus = mPm.mSettings.getSharedUserSettingLPr( |
| signatureCheckPs); |
| if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, signatureCheckSus, |
| scanFlags)) { |
| if (!ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) { |
| throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " |
| + parsedPackage.getPackageName() + " upgrade keys do not match the " |
| + "previously installed version"); |
| } |
| } else { |
| try { |
| final boolean compareCompat = |
| ReconcilePackageUtils.isCompatSignatureUpdateNeeded( |
| mPm.getSettingsVersionForPackage(parsedPackage)); |
| final boolean compareRecover = |
| ReconcilePackageUtils.isRecoverSignatureUpdateNeeded( |
| mPm.getSettingsVersionForPackage(parsedPackage)); |
| // We don't care about disabledPkgSetting on install for now. |
| final boolean compatMatch = |
| PackageManagerServiceUtils.verifySignatures(signatureCheckPs, |
| signatureCheckSus, null, |
| parsedPackage.getSigningDetails(), compareCompat, compareRecover, |
| isRollback); |
| // The new KeySets will be re-added later in the scanning process. |
| if (compatMatch) { |
| synchronized (mPm.mLock) { |
| ksms.removeAppKeySetDataLPw(parsedPackage.getPackageName()); |
| } |
| } |
| } catch (PackageManagerException e) { |
| throw new PrepareFailure(e.error, e.getMessage()); |
| } |
| } |
| } |
| |
| if (ps != null) { |
| if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps); |
| |
| if (ps.getPkg() != null) { |
| systemApp = ps.getPkg().isSystem(); |
| } |
| res.mOrigUsers = ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true); |
| } |
| |
| final int numGroups = ArrayUtils.size(parsedPackage.getPermissionGroups()); |
| for (int groupNum = 0; groupNum < numGroups; groupNum++) { |
| final ParsedPermissionGroup group = |
| parsedPackage.getPermissionGroups().get(groupNum); |
| final PermissionGroupInfo sourceGroup = mPm.getPermissionGroupInfo(group.getName(), |
| 0); |
| |
| if (sourceGroup != null && cannotInstallWithBadPermissionGroups(parsedPackage)) { |
| final String sourcePackageName = sourceGroup.packageName; |
| |
| if ((replace || !parsedPackage.getPackageName().equals(sourcePackageName)) |
| && !doesSignatureMatchForPermissions(sourcePackageName, parsedPackage, |
| scanFlags)) { |
| EventLog.writeEvent(0x534e4554, "146211400", -1, |
| parsedPackage.getPackageName()); |
| |
| throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PERMISSION_GROUP, |
| "Package " |
| + parsedPackage.getPackageName() |
| + " attempting to redeclare permission group " |
| + group.getName() + " already owned by " |
| + sourcePackageName); |
| } |
| } |
| } |
| |
| // TODO: Move logic for checking permission compatibility into PermissionManagerService |
| final int n = ArrayUtils.size(parsedPackage.getPermissions()); |
| for (int i = n - 1; i >= 0; i--) { |
| final ParsedPermission perm = parsedPackage.getPermissions().get(i); |
| final Permission bp = mPm.mPermissionManager.getPermissionTEMP(perm.getName()); |
| |
| // Don't allow anyone but the system to define ephemeral permissions. |
| if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0 |
| && !systemApp) { |
| Slog.w(TAG, "Non-System package " + parsedPackage.getPackageName() |
| + " attempting to delcare ephemeral permission " |
| + perm.getName() + "; Removing ephemeral."); |
| ComponentMutateUtils.setProtectionLevel(perm, |
| perm.getProtectionLevel() & ~PermissionInfo.PROTECTION_FLAG_INSTANT); |
| } |
| |
| // Check whether the newly-scanned package wants to define an already-defined perm |
| if (bp != null) { |
| final String sourcePackageName = bp.getPackageName(); |
| |
| if (!doesSignatureMatchForPermissions(sourcePackageName, parsedPackage, |
| scanFlags)) { |
| // If the owning package is the system itself, we log but allow |
| // install to proceed; we fail the install on all other permission |
| // redefinitions. |
| if (!sourcePackageName.equals("android")) { |
| throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PERMISSION, |
| "Package " |
| + parsedPackage.getPackageName() |
| + " attempting to redeclare permission " |
| + perm.getName() + " already owned by " |
| + sourcePackageName) |
| .conflictsWithExistingPermission(perm.getName(), |
| sourcePackageName); |
| } else { |
| Slog.w(TAG, "Package " + parsedPackage.getPackageName() |
| + " attempting to redeclare system permission " |
| + perm.getName() + "; ignoring new declaration"); |
| parsedPackage.removePermission(i); |
| } |
| } else if (!PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())) { |
| // Prevent apps to change protection level to dangerous from any other |
| // type as this would allow a privilege escalation where an app adds a |
| // normal/signature permission in other app's group and later redefines |
| // it as dangerous leading to the group auto-grant. |
| if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_MASK_BASE) |
| == PermissionInfo.PROTECTION_DANGEROUS) { |
| if (bp != null && !bp.isRuntime()) { |
| Slog.w(TAG, "Package " + parsedPackage.getPackageName() |
| + " trying to change a non-runtime permission " |
| + perm.getName() |
| + " to runtime; keeping old protection level"); |
| ComponentMutateUtils.setProtectionLevel(perm, |
| bp.getProtectionLevel()); |
| } |
| } |
| } |
| } |
| |
| if (perm.getGroup() != null |
| && cannotInstallWithBadPermissionGroups(parsedPackage)) { |
| boolean isPermGroupDefinedByPackage = false; |
| for (int groupNum = 0; groupNum < numGroups; groupNum++) { |
| if (parsedPackage.getPermissionGroups().get(groupNum).getName() |
| .equals(perm.getGroup())) { |
| isPermGroupDefinedByPackage = true; |
| break; |
| } |
| } |
| |
| if (!isPermGroupDefinedByPackage) { |
| final PermissionGroupInfo sourceGroup = |
| mPm.getPermissionGroupInfo(perm.getGroup(), 0); |
| |
| if (sourceGroup == null) { |
| EventLog.writeEvent(0x534e4554, "146211400", -1, |
| parsedPackage.getPackageName()); |
| |
| throw new PrepareFailure(INSTALL_FAILED_BAD_PERMISSION_GROUP, |
| "Package " |
| + parsedPackage.getPackageName() |
| + " attempting to declare permission " |
| + perm.getName() + " in non-existing group " |
| + perm.getGroup()); |
| } else { |
| String groupSourcePackageName = sourceGroup.packageName; |
| |
| if (!PLATFORM_PACKAGE_NAME.equals(groupSourcePackageName) |
| && !doesSignatureMatchForPermissions(groupSourcePackageName, |
| parsedPackage, scanFlags)) { |
| EventLog.writeEvent(0x534e4554, "146211400", -1, |
| parsedPackage.getPackageName()); |
| |
| throw new PrepareFailure(INSTALL_FAILED_BAD_PERMISSION_GROUP, |
| "Package " |
| + parsedPackage.getPackageName() |
| + " attempting to declare permission " |
| + perm.getName() + " in group " |
| + perm.getGroup() + " owned by package " |
| + groupSourcePackageName |
| + " with incompatible certificate"); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if (systemApp) { |
| if (onExternal) { |
| // Abort update; system app can't be replaced with app on sdcard |
| throw new PrepareFailure(INSTALL_FAILED_INVALID_INSTALL_LOCATION, |
| "Cannot install updates to system apps on sdcard"); |
| } else if (instantApp) { |
| // Abort update; system app can't be replaced with an instant app |
| throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID, |
| "Cannot update a system app with an instant app"); |
| } |
| } |
| |
| if (args.mMoveInfo != null) { |
| // We did an in-place move, so dex is ready to roll |
| scanFlags |= SCAN_NO_DEX; |
| scanFlags |= SCAN_MOVE; |
| |
| synchronized (mPm.mLock) { |
| final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName); |
| if (ps == null) { |
| res.setError(INSTALL_FAILED_INTERNAL_ERROR, |
| "Missing settings for moved package " + pkgName); |
| } |
| |
| // We moved the entire application as-is, so bring over the |
| // previously derived ABI information. |
| parsedPackage.setPrimaryCpuAbi(ps.getPrimaryCpuAbi()) |
| .setSecondaryCpuAbi(ps.getSecondaryCpuAbi()); |
| } |
| |
| } else { |
| // Enable SCAN_NO_DEX flag to skip dexopt at a later stage |
| scanFlags |= SCAN_NO_DEX; |
| |
| try { |
| PackageSetting pkgSetting; |
| AndroidPackage oldPackage; |
| synchronized (mPm.mLock) { |
| pkgSetting = mPm.mSettings.getPackageLPr(pkgName); |
| oldPackage = mPm.mPackages.get(pkgName); |
| } |
| boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null |
| && pkgSetting.getPkgState().isUpdatedSystemApp(); |
| final String abiOverride = deriveAbiOverride(args.mAbiOverride); |
| boolean isUpdatedSystemAppInferred = oldPackage != null && oldPackage.isSystem(); |
| final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths> |
| derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage, |
| isUpdatedSystemAppFromExistingSetting || isUpdatedSystemAppInferred, |
| abiOverride, ScanPackageUtils.getAppLib32InstallDir()); |
| derivedAbi.first.applyTo(parsedPackage); |
| derivedAbi.second.applyTo(parsedPackage); |
| } catch (PackageManagerException pme) { |
| Slog.e(TAG, "Error deriving application ABI", pme); |
| throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR, |
| "Error deriving application ABI: " + pme.getMessage()); |
| } |
| } |
| |
| if (!args.doRename(res.mReturnCode, parsedPackage)) { |
| throw new PrepareFailure(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename"); |
| } |
| |
| try { |
| setUpFsVerityIfPossible(parsedPackage); |
| } catch (Installer.InstallerException | IOException | DigestException |
| | NoSuchAlgorithmException e) { |
| throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR, |
| "Failed to set up verity: " + e); |
| } |
| |
| final PackageFreezer freezer = |
| freezePackageForInstall(pkgName, installFlags, "installPackageLI"); |
| boolean shouldCloseFreezerBeforeReturn = true; |
| try { |
| final AndroidPackage oldPackage; |
| String renamedPackage; |
| boolean sysPkg = false; |
| int targetScanFlags = scanFlags; |
| int targetParseFlags = parseFlags; |
| final PackageSetting ps; |
| final PackageSetting disabledPs; |
| final SharedUserSetting sharedUserSetting; |
| if (replace) { |
| final String pkgName11 = parsedPackage.getPackageName(); |
| synchronized (mPm.mLock) { |
| oldPackage = mPm.mPackages.get(pkgName11); |
| } |
| if (parsedPackage.isStaticSharedLibrary()) { |
| // Static libs have a synthetic package name containing the version |
| // and cannot be updated as an update would get a new package name, |
| // unless this is installed from adb which is useful for development. |
| if (oldPackage != null |
| && (installFlags & PackageManager.INSTALL_FROM_ADB) == 0) { |
| throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PACKAGE, |
| "Packages declaring " |
| + "static-shared libs cannot be updated"); |
| } |
| } |
| |
| final boolean isInstantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0; |
| |
| final int[] allUsers; |
| final int[] installedUsers; |
| final int[] uninstalledUsers; |
| |
| synchronized (mPm.mLock) { |
| if (DEBUG_INSTALL) { |
| Slog.d(TAG, |
| "replacePackageLI: new=" + parsedPackage + ", old=" + oldPackage); |
| } |
| |
| ps = mPm.mSettings.getPackageLPr(pkgName11); |
| disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(ps); |
| sharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(ps); |
| |
| // verify signatures are valid |
| final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService(); |
| if (ksms.shouldCheckUpgradeKeySetLocked(ps, sharedUserSetting, scanFlags)) { |
| if (!ksms.checkUpgradeKeySetLocked(ps, parsedPackage)) { |
| throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, |
| "New package not signed by keys specified by upgrade-keysets: " |
| + pkgName11); |
| } |
| } else { |
| SigningDetails parsedPkgSigningDetails = parsedPackage.getSigningDetails(); |
| SigningDetails oldPkgSigningDetails = oldPackage.getSigningDetails(); |
| // default to original signature matching |
| if (!parsedPkgSigningDetails.checkCapability(oldPkgSigningDetails, |
| SigningDetails.CertCapabilities.INSTALLED_DATA) |
| && !oldPkgSigningDetails.checkCapability(parsedPkgSigningDetails, |
| SigningDetails.CertCapabilities.ROLLBACK)) { |
| // Allow the update to proceed if this is a rollback and the parsed |
| // package's current signing key is the current signer or in the lineage |
| // of the old package; this allows a rollback to a previously installed |
| // version after an app's signing key has been rotated without requiring |
| // the rollback capability on the previous signing key. |
| if (!isRollback || !oldPkgSigningDetails.hasAncestorOrSelf( |
| parsedPkgSigningDetails)) { |
| throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, |
| "New package has a different signature: " + pkgName11); |
| } |
| } |
| } |
| |
| // don't allow a system upgrade unless the upgrade hash matches |
| if (oldPackage.getRestrictUpdateHash() != null && oldPackage.isSystem()) { |
| final byte[] digestBytes; |
| try { |
| final MessageDigest digest = MessageDigest.getInstance("SHA-512"); |
| updateDigest(digest, new File(parsedPackage.getBaseApkPath())); |
| if (!ArrayUtils.isEmpty(parsedPackage.getSplitCodePaths())) { |
| for (String path : parsedPackage.getSplitCodePaths()) { |
| updateDigest(digest, new File(path)); |
| } |
| } |
| digestBytes = digest.digest(); |
| } catch (NoSuchAlgorithmException | IOException e) { |
| throw new PrepareFailure(INSTALL_FAILED_INVALID_APK, |
| "Could not compute hash: " + pkgName11); |
| } |
| if (!Arrays.equals(oldPackage.getRestrictUpdateHash(), digestBytes)) { |
| throw new PrepareFailure(INSTALL_FAILED_INVALID_APK, |
| "New package fails restrict-update check: " + pkgName11); |
| } |
| // retain upgrade restriction |
| parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash()); |
| } |
| |
| // APK should not change its sharedUserId declarations |
| final var oldSharedUid = oldPackage.getSharedUserId() != null |
| ? oldPackage.getSharedUserId() : "<nothing>"; |
| final var newSharedUid = parsedPackage.getSharedUserId() != null |
| ? parsedPackage.getSharedUserId() : "<nothing>"; |
| if (!oldSharedUid.equals(newSharedUid)) { |
| throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED, |
| "Package " + parsedPackage.getPackageName() |
| + " shared user changed from " |
| + oldSharedUid + " to " + newSharedUid); |
| } |
| |
| // APK should not re-join shared UID |
| if (oldPackage.isLeavingSharedUid() && !parsedPackage.isLeavingSharedUid()) { |
| throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED, |
| "Package " + parsedPackage.getPackageName() |
| + " attempting to rejoin " + newSharedUid); |
| } |
| |
| // In case of rollback, remember per-user/profile install state |
| allUsers = mPm.mUserManager.getUserIds(); |
| installedUsers = ps.queryInstalledUsers(allUsers, true); |
| uninstalledUsers = ps.queryInstalledUsers(allUsers, false); |
| |
| |
| // don't allow an upgrade from full to ephemeral |
| if (isInstantApp) { |
| if (args.mUser == null |
| || args.mUser.getIdentifier() == UserHandle.USER_ALL) { |
| for (int currentUser : allUsers) { |
| if (!ps.getInstantApp(currentUser)) { |
| // can't downgrade from full to instant |
| Slog.w(TAG, |
| "Can't replace full app with instant app: " + pkgName11 |
| + " for user: " + currentUser); |
| throw new PrepareFailure( |
| PackageManager.INSTALL_FAILED_SESSION_INVALID); |
| } |
| } |
| } else if (!ps.getInstantApp(args.mUser.getIdentifier())) { |
| // can't downgrade from full to instant |
| Slog.w(TAG, "Can't replace full app with instant app: " + pkgName11 |
| + " for user: " + args.mUser.getIdentifier()); |
| throw new PrepareFailure( |
| PackageManager.INSTALL_FAILED_SESSION_INVALID); |
| } |
| } |
| } |
| |
| // Update what is removed |
| res.mRemovedInfo = new PackageRemovedInfo(mPm); |
| res.mRemovedInfo.mUid = oldPackage.getUid(); |
| res.mRemovedInfo.mRemovedPackage = oldPackage.getPackageName(); |
| res.mRemovedInfo.mInstallerPackageName = ps.getInstallSource().installerPackageName; |
| res.mRemovedInfo.mIsStaticSharedLib = |
| parsedPackage.getStaticSharedLibName() != null; |
| res.mRemovedInfo.mIsUpdate = true; |
| res.mRemovedInfo.mOrigUsers = installedUsers; |
| res.mRemovedInfo.mInstallReasons = new SparseArray<>(installedUsers.length); |
| for (int i = 0; i < installedUsers.length; i++) { |
| final int userId = installedUsers[i]; |
| res.mRemovedInfo.mInstallReasons.put(userId, ps.getInstallReason(userId)); |
| } |
| res.mRemovedInfo.mUninstallReasons = new SparseArray<>(uninstalledUsers.length); |
| for (int i = 0; i < uninstalledUsers.length; i++) { |
| final int userId = uninstalledUsers[i]; |
| res.mRemovedInfo.mUninstallReasons.put(userId, ps.getUninstallReason(userId)); |
| } |
| res.mRemovedInfo.mIsExternal = oldPackage.isExternalStorage(); |
| |
| sysPkg = oldPackage.isSystem(); |
| if (sysPkg) { |
| // Set the system/privileged/oem/vendor/product flags as needed |
| final boolean privileged = oldPackage.isPrivileged(); |
| final boolean oem = oldPackage.isOem(); |
| final boolean vendor = oldPackage.isVendor(); |
| final boolean product = oldPackage.isProduct(); |
| final boolean odm = oldPackage.isOdm(); |
| final boolean systemExt = oldPackage.isSystemExt(); |
| final @ParsingPackageUtils.ParseFlags int systemParseFlags = parseFlags; |
| final @PackageManagerService.ScanFlags int systemScanFlags = scanFlags |
| | SCAN_AS_SYSTEM |
| | (privileged ? SCAN_AS_PRIVILEGED : 0) |
| | (oem ? SCAN_AS_OEM : 0) |
| | (vendor ? SCAN_AS_VENDOR : 0) |
| | (product ? SCAN_AS_PRODUCT : 0) |
| | (odm ? SCAN_AS_ODM : 0) |
| | (systemExt ? SCAN_AS_SYSTEM_EXT : 0); |
| |
| if (DEBUG_INSTALL) { |
| Slog.d(TAG, "replaceSystemPackageLI: new=" + parsedPackage |
| + ", old=" + oldPackage); |
| } |
| res.setReturnCode(PackageManager.INSTALL_SUCCEEDED); |
| targetParseFlags = systemParseFlags; |
| targetScanFlags = systemScanFlags; |
| } else { // non system replace |
| replace = true; |
| if (DEBUG_INSTALL) { |
| Slog.d(TAG, |
| "replaceNonSystemPackageLI: new=" + parsedPackage + ", old=" |
| + oldPackage); |
| } |
| } |
| } else { // new package install |
| ps = null; |
| disabledPs = null; |
| replace = false; |
| oldPackage = null; |
| // Remember this for later, in case we need to rollback this install |
| String pkgName1 = parsedPackage.getPackageName(); |
| |
| if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + parsedPackage); |
| |
| // TODO(b/194319951): MOVE TO RECONCILE |
| synchronized (mPm.mLock) { |
| renamedPackage = mPm.mSettings.getRenamedPackageLPr(pkgName1); |
| if (renamedPackage != null) { |
| // A package with the same name is already installed, though |
| // it has been renamed to an older name. The package we |
| // are trying to install should be installed as an update to |
| // the existing one, but that has not been requested, so bail. |
| throw new PrepareFailure(INSTALL_FAILED_ALREADY_EXISTS, |
| "Attempt to re-install " + pkgName1 |
| + " without first uninstalling package running as " |
| + renamedPackage); |
| } |
| if (mPm.mPackages.containsKey(pkgName1)) { |
| // Don't allow installation over an existing package with the same name. |
| throw new PrepareFailure(INSTALL_FAILED_ALREADY_EXISTS, |
| "Attempt to re-install " + pkgName1 |
| + " without first uninstalling."); |
| } |
| } |
| } |
| // we're passing the freezer back to be closed in a later phase of install |
| shouldCloseFreezerBeforeReturn = false; |
| |
| return new PrepareResult(replace, targetScanFlags, targetParseFlags, |
| oldPackage, parsedPackage, replace /* clearCodeCache */, sysPkg, |
| ps, disabledPs); |
| } finally { |
| res.mFreezer = freezer; |
| if (shouldCloseFreezerBeforeReturn) { |
| freezer.close(); |
| } |
| } |
| } |
| |
| /* |
| * Cannot properly check CANNOT_INSTALL_WITH_BAD_PERMISSION_GROUPS using CompatChanges |
| * as this only works for packages that are installed |
| * |
| * TODO: Move logic for permission group compatibility into PermissionManagerService |
| */ |
| @SuppressWarnings("AndroidFrameworkCompatChange") |
| private static boolean cannotInstallWithBadPermissionGroups(ParsedPackage parsedPackage) { |
| return parsedPackage.getTargetSdkVersion() >= Build.VERSION_CODES.S; |
| } |
| |
| private boolean doesSignatureMatchForPermissions(@NonNull String sourcePackageName, |
| @NonNull ParsedPackage parsedPackage, int scanFlags) { |
| // If the defining package is signed with our cert, it's okay. This |
| // also includes the "updating the same package" case, of course. |
| // "updating same package" could also involve key-rotation. |
| |
| final PackageSetting sourcePackageSetting; |
| final KeySetManagerService ksms; |
| final SharedUserSetting sharedUserSetting; |
| synchronized (mPm.mLock) { |
| sourcePackageSetting = mPm.mSettings.getPackageLPr(sourcePackageName); |
| ksms = mPm.mSettings.getKeySetManagerService(); |
| sharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(sourcePackageSetting); |
| } |
| |
| final SigningDetails sourceSigningDetails = (sourcePackageSetting == null |
| ? SigningDetails.UNKNOWN : sourcePackageSetting.getSigningDetails()); |
| if (sourcePackageName.equals(parsedPackage.getPackageName()) |
| && (ksms.shouldCheckUpgradeKeySetLocked( |
| sourcePackageSetting, sharedUserSetting, scanFlags))) { |
| return ksms.checkUpgradeKeySetLocked(sourcePackageSetting, parsedPackage); |
| } else { |
| |
| // in the event of signing certificate rotation, we need to see if the |
| // package's certificate has rotated from the current one, or if it is an |
| // older certificate with which the current is ok with sharing permissions |
| if (sourceSigningDetails.checkCapability( |
| parsedPackage.getSigningDetails(), |
| SigningDetails.CertCapabilities.PERMISSION)) { |
| return true; |
| } else if (parsedPackage.getSigningDetails().checkCapability( |
| sourceSigningDetails, |
| SigningDetails.CertCapabilities.PERMISSION)) { |
| // the scanned package checks out, has signing certificate rotation |
| // history, and is newer; bring it over |
| synchronized (mPm.mLock) { |
| sourcePackageSetting.setSigningDetails(parsedPackage.getSigningDetails()); |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| } |
| |
| /** |
| * Set up fs-verity for the given package if possible. This requires a feature flag of system |
| * property to be enabled only if the kernel supports fs-verity. |
| * |
| * <p>When the feature flag is set to legacy mode, only APK is supported (with some experimental |
| * kernel patches). In normal mode, all file format can be supported. |
| */ |
| private void setUpFsVerityIfPossible(AndroidPackage pkg) throws Installer.InstallerException, |
| PrepareFailure, IOException, DigestException, NoSuchAlgorithmException { |
| if (!PackageManagerServiceUtils.isApkVerityEnabled()) { |
| return; |
| } |
| |
| if (isIncrementalPath(pkg.getPath()) && IncrementalManager.getVersion() |
| < IncrementalManager.MIN_VERSION_TO_SUPPORT_FSVERITY) { |
| return; |
| } |
| |
| // Collect files we care for fs-verity setup. |
| ArrayMap<String, String> fsverityCandidates = new ArrayMap<>(); |
| // NB: These files will become only accessible if the signing key is loaded in kernel's |
| // .fs-verity keyring. |
| fsverityCandidates.put(pkg.getBaseApkPath(), |
| VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath())); |
| |
| final String dmPath = DexMetadataHelper.buildDexMetadataPathForApk( |
| pkg.getBaseApkPath()); |
| if (new File(dmPath).exists()) { |
| fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath)); |
| } |
| |
| for (String path : pkg.getSplitCodePaths()) { |
| fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path)); |
| |
| final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path); |
| if (new File(splitDmPath).exists()) { |
| fsverityCandidates.put(splitDmPath, |
| VerityUtils.getFsveritySignatureFilePath(splitDmPath)); |
| } |
| } |
| |
| final String packageName = pkg.getPackageName(); |
| for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) { |
| final String filePath = entry.getKey(); |
| final String signaturePath = entry.getValue(); |
| |
| // fs-verity is optional for now. Only set up if signature is provided. |
| if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) { |
| try { |
| VerityUtils.setUpFsverity(filePath, signaturePath); |
| } catch (IOException e) { |
| throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE, |
| "Failed to enable fs-verity: " + e); |
| } |
| } |
| } |
| } |
| |
| private PackageFreezer freezePackageForInstall(String packageName, int installFlags, |
| String killReason) { |
| return freezePackageForInstall(packageName, UserHandle.USER_ALL, installFlags, killReason); |
| } |
| |
| private PackageFreezer freezePackageForInstall(String packageName, int userId, int installFlags, |
| String killReason) { |
| if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) { |
| return new PackageFreezer(mPm); |
| } else { |
| return mPm.freezePackage(packageName, userId, killReason); |
| } |
| } |
| |
| private static void updateDigest(MessageDigest digest, File file) throws IOException { |
| try (DigestInputStream digestStream = |
| new DigestInputStream(new FileInputStream(file), digest)) { |
| int length, total = 0; |
| while ((length = digestStream.read()) != -1) { |
| total += length; |
| } // just plow through the file |
| } |
| } |
| |
| @GuardedBy("mPm.mLock") |
| private void commitPackagesLocked(final CommitRequest request) { |
| // TODO: remove any expected failures from this method; this should only be able to fail due |
| // to unavoidable errors (I/O, etc.) |
| for (ReconciledPackage reconciledPkg : request.mReconciledPackages.values()) { |
| final ScanResult scanResult = reconciledPkg.mScanResult; |
| final ScanRequest scanRequest = scanResult.mRequest; |
| final ParsedPackage parsedPackage = scanRequest.mParsedPackage; |
| final String packageName = parsedPackage.getPackageName(); |
| final PackageInstalledInfo res = reconciledPkg.mInstallResult; |
| final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm); |
| final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm); |
| |
| if (reconciledPkg.mPrepareResult.mReplace) { |
| AndroidPackage oldPackage = mPm.mPackages.get(packageName); |
| |
| // Set the update and install times |
| PackageStateInternal deletedPkgSetting = mPm.snapshotComputer() |
| .getPackageStateInternal(oldPackage.getPackageName()); |
| reconciledPkg.mPkgSetting |
| .setFirstInstallTimeFromReplaced(deletedPkgSetting, request.mAllUsers) |
| .setLastUpdateTime(System.currentTimeMillis()); |
| |
| res.mRemovedInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList( |
| mPm.snapshotComputer(), reconciledPkg.mPkgSetting, request.mAllUsers, |
| mPm.mSettings.getPackagesLocked()); |
| if (reconciledPkg.mPrepareResult.mSystem) { |
| // Remove existing system package |
| removePackageHelper.removePackageLI(oldPackage, true); |
| if (!disableSystemPackageLPw(oldPackage)) { |
| // We didn't need to disable the .apk as a current system package, |
| // which means we are replacing another update that is already |
| // installed. We need to make sure to delete the older one's .apk. |
| res.mRemovedInfo.mArgs = new FileInstallArgs( |
| oldPackage.getPath(), |
| getAppDexInstructionSets( |
| AndroidPackageUtils.getPrimaryCpuAbi(oldPackage, |
| deletedPkgSetting), |
| AndroidPackageUtils.getSecondaryCpuAbi(oldPackage, |
| deletedPkgSetting)), mPm); |
| } else { |
| res.mRemovedInfo.mArgs = null; |
| } |
| } else { |
| try { |
| // Settings will be written during the call to updateSettingsLI(). |
| deletePackageHelper.executeDeletePackageLIF( |
| reconciledPkg.mDeletePackageAction, packageName, |
| true, request.mAllUsers, false); |
| } catch (SystemDeleteException e) { |
| if (mPm.mIsEngBuild) { |
| throw new RuntimeException("Unexpected failure", e); |
| // ignore; not possible for non-system app |
| } |
| } |
| // Successfully deleted the old package; proceed with replace. |
| // Update the in-memory copy of the previous code paths. |
| PackageSetting ps1 = mPm.mSettings.getPackageLPr( |
| reconciledPkg.mPrepareResult.mExistingPackage.getPackageName()); |
| if ((reconciledPkg.mInstallArgs.mInstallFlags & PackageManager.DONT_KILL_APP) |
| == 0) { |
| Set<String> oldCodePaths = ps1.getOldCodePaths(); |
| if (oldCodePaths == null) { |
| oldCodePaths = new ArraySet<>(); |
| } |
| Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath()); |
| Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths()); |
| ps1.setOldCodePaths(oldCodePaths); |
| } else { |
| ps1.setOldCodePaths(null); |
| } |
| |
| if (reconciledPkg.mInstallResult.mReturnCode |
| == PackageManager.INSTALL_SUCCEEDED) { |
| PackageSetting ps2 = mPm.mSettings.getPackageLPr( |
| parsedPackage.getPackageName()); |
| if (ps2 != null) { |
| res.mRemovedInfo.mRemovedForAllUsers = |
| mPm.mPackages.get(ps2.getPackageName()) == null; |
| } |
| } |
| } |
| } |
| |
| AndroidPackage pkg = commitReconciledScanResultLocked( |
| reconciledPkg, request.mAllUsers); |
| updateSettingsLI(pkg, reconciledPkg, request.mAllUsers, res); |
| |
| final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); |
| if (ps != null) { |
| res.mNewUsers = ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true); |
| ps.setUpdateAvailable(false /*updateAvailable*/); |
| } |
| if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED) { |
| mPm.updateSequenceNumberLP(ps, res.mNewUsers); |
| mPm.updateInstantAppInstallerLocked(packageName); |
| } |
| } |
| ApplicationPackageManager.invalidateGetPackagesForUidCache(); |
| } |
| |
| @GuardedBy("mPm.mLock") |
| private boolean disableSystemPackageLPw(AndroidPackage oldPkg) { |
| return mPm.mSettings.disableSystemPackageLPw(oldPkg.getPackageName(), true); |
| } |
| |
| private void updateSettingsLI(AndroidPackage newPackage, ReconciledPackage reconciledPkg, |
| int[] allUsers, PackageInstalledInfo res) { |
| updateSettingsInternalLI(newPackage, reconciledPkg, allUsers, res); |
| } |
| |
| private void updateSettingsInternalLI(AndroidPackage pkg, ReconciledPackage reconciledPkg, |
| int[] allUsers, PackageInstalledInfo res) { |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings"); |
| |
| final String pkgName = pkg.getPackageName(); |
| final int[] installedForUsers = res.mOrigUsers; |
| final InstallArgs installArgs = reconciledPkg.mInstallArgs; |
| final int installReason = installArgs.mInstallReason; |
| InstallSource installSource = installArgs.mInstallSource; |
| final String installerPackageName = installSource.installerPackageName; |
| |
| if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath()); |
| synchronized (mPm.mLock) { |
| // For system-bundled packages, we assume that installing an upgraded version |
| // of the package implies that the user actually wants to run that new code, |
| // so we enable the package. |
| final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName); |
| final int userId = installArgs.mUser.getIdentifier(); |
| if (ps != null) { |
| if (pkg.isSystem()) { |
| if (DEBUG_INSTALL) { |
| Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName); |
| } |
| // Enable system package for requested users |
| if (res.mOrigUsers != null) { |
| for (int origUserId : res.mOrigUsers) { |
| if (userId == UserHandle.USER_ALL || userId == origUserId) { |
| ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, |
| origUserId, installerPackageName); |
| } |
| } |
| } |
| // Also convey the prior install/uninstall state |
| if (allUsers != null && installedForUsers != null) { |
| for (int currentUserId : allUsers) { |
| final boolean installed = ArrayUtils.contains( |
| installedForUsers, currentUserId); |
| if (DEBUG_INSTALL) { |
| Slog.d(TAG, " user " + currentUserId + " => " + installed); |
| } |
| ps.setInstalled(installed, currentUserId); |
| } |
| // these install state changes will be persisted in the |
| // upcoming call to mSettings.writeLPr(). |
| } |
| |
| if (allUsers != null) { |
| for (int currentUserId : allUsers) { |
| ps.resetOverrideComponentLabelIcon(currentUserId); |
| } |
| } |
| } |
| |
| // Retrieve the overlays for shared libraries of the package. |
| if (!ps.getPkgState().getUsesLibraryInfos().isEmpty()) { |
| for (SharedLibraryInfo sharedLib : ps.getPkgState().getUsesLibraryInfos()) { |
| for (int currentUserId : UserManagerService.getInstance().getUserIds()) { |
| if (!sharedLib.isDynamic()) { |
| // TODO(146804378): Support overlaying static shared libraries |
| continue; |
| } |
| final PackageSetting libPs = mPm.mSettings.getPackageLPr( |
| sharedLib.getPackageName()); |
| if (libPs == null) { |
| continue; |
| } |
| ps.setOverlayPathsForLibrary(sharedLib.getName(), |
| libPs.getOverlayPaths(currentUserId), currentUserId); |
| } |
| } |
| } |
| |
| if (userId != UserHandle.USER_ALL) { |
| // It's implied that when a user requests installation, they want the app to |
| // be installed and enabled. |
| ps.setInstalled(true, userId); |
| ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName); |
| } else if (allUsers != null) { |
| // The caller explicitly specified INSTALL_ALL_USERS flag. |
| // Thus, updating the settings to install the app for all users. |
| for (int currentUserId : allUsers) { |
| ps.setInstalled(true, currentUserId); |
| ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, |
| installerPackageName); |
| } |
| } |
| |
| mPm.mSettings.addInstallerPackageNames(ps.getInstallSource()); |
| |
| // When replacing an existing package, preserve the original install reason for all |
| // users that had the package installed before. Similarly for uninstall reasons. |
| final Set<Integer> previousUserIds = new ArraySet<>(); |
| if (res.mRemovedInfo != null && res.mRemovedInfo.mInstallReasons != null) { |
| final int installReasonCount = res.mRemovedInfo.mInstallReasons.size(); |
| for (int i = 0; i < installReasonCount; i++) { |
| final int previousUserId = res.mRemovedInfo.mInstallReasons.keyAt(i); |
| final int previousInstallReason = |
| res.mRemovedInfo.mInstallReasons.valueAt(i); |
| ps.setInstallReason(previousInstallReason, previousUserId); |
| previousUserIds.add(previousUserId); |
| } |
| } |
| if (res.mRemovedInfo != null && res.mRemovedInfo.mUninstallReasons != null) { |
| for (int i = 0; i < res.mRemovedInfo.mUninstallReasons.size(); i++) { |
| final int previousUserId = res.mRemovedInfo.mUninstallReasons.keyAt(i); |
| final int previousReason = res.mRemovedInfo.mUninstallReasons.valueAt(i); |
| ps.setUninstallReason(previousReason, previousUserId); |
| } |
| } |
| |
| // Set install reason for users that are having the package newly installed. |
| final int[] allUsersList = mPm.mUserManager.getUserIds(); |
| if (userId == UserHandle.USER_ALL) { |
| for (int currentUserId : allUsersList) { |
| if (!previousUserIds.contains(currentUserId) |
| && ps.getInstalled(currentUserId)) { |
| ps.setInstallReason(installReason, currentUserId); |
| } |
| } |
| } else if (!previousUserIds.contains(userId)) { |
| ps.setInstallReason(installReason, userId); |
| } |
| |
| // TODO(b/169721400): generalize Incremental States and create a Callback object |
| // that can be used for all the packages. |
| final String codePath = ps.getPathString(); |
| if (IncrementalManager.isIncrementalPath(codePath) |
| && mIncrementalManager != null) { |
| mIncrementalManager.registerLoadingProgressCallback(codePath, |
| new IncrementalProgressListener(ps.getPackageName(), mPm)); |
| } |
| |
| // Ensure that the uninstall reason is UNKNOWN for users with the package installed. |
| for (int currentUserId : allUsersList) { |
| if (ps.getInstalled(currentUserId)) { |
| ps.setUninstallReason(UNINSTALL_REASON_UNKNOWN, currentUserId); |
| } |
| } |
| |
| mPm.mSettings.writeKernelMappingLPr(ps); |
| |
| final PermissionManagerServiceInternal.PackageInstalledParams.Builder |
| permissionParamsBuilder = |
| new PermissionManagerServiceInternal.PackageInstalledParams.Builder(); |
| final boolean grantPermissions = (installArgs.mInstallFlags |
| & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0; |
| if (grantPermissions) { |
| final List<String> grantedPermissions = |
| installArgs.mInstallGrantPermissions != null |
| ? Arrays.asList(installArgs.mInstallGrantPermissions) |
| : pkg.getRequestedPermissions(); |
| permissionParamsBuilder.setGrantedPermissions(grantedPermissions); |
| } |
| final boolean allowlistAllRestrictedPermissions = |
| (installArgs.mInstallFlags |
| & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0; |
| final List<String> allowlistedRestrictedPermissions = |
| allowlistAllRestrictedPermissions ? pkg.getRequestedPermissions() |
| : installArgs.mAllowlistedRestrictedPermissions; |
| if (allowlistedRestrictedPermissions != null) { |
| permissionParamsBuilder.setAllowlistedRestrictedPermissions( |
| allowlistedRestrictedPermissions); |
| } |
| final int autoRevokePermissionsMode = installArgs.mAutoRevokePermissionsMode; |
| permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode); |
| final ScanResult scanResult = reconciledPkg.mScanResult; |
| mPm.mPermissionManager.onPackageInstalled(pkg, scanResult.mPreviousAppId, |
| permissionParamsBuilder.build(), userId); |
| // Apply restricted settings on potentially dangerous packages. |
| if (installArgs.mPackageSource == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE |
| || installArgs.mPackageSource |
| == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) { |
| enableRestrictedSettings(pkgName, pkg.getUid()); |
| } |
| } |
| res.mName = pkgName; |
| res.mUid = pkg.getUid(); |
| res.mPkg = pkg; |
| res.setReturnCode(PackageManager.INSTALL_SUCCEEDED); |
| //to update install status |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings"); |
| mPm.writeSettingsLPrTEMP(); |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| |
| private void enableRestrictedSettings(String pkgName, int appId) { |
| final AppOpsManager appOpsManager = mPm.mContext.getSystemService(AppOpsManager.class); |
| final int[] allUsersList = mPm.mUserManager.getUserIds(); |
| for (int userId : allUsersList) { |
| final int uid = UserHandle.getUid(userId, appId); |
| appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, |
| uid, |
| pkgName, |
| AppOpsManager.MODE_ERRORED); |
| } |
| } |
| |
| /** |
| * On successful install, executes remaining steps after commit completes and the package lock |
| * is released. These are typically more expensive or require calls to installd, which often |
| * locks on {@link com.android.server.pm.PackageManagerService.mLock}. |
| */ |
| private void executePostCommitSteps(CommitRequest commitRequest) { |
| final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>(); |
| for (ReconciledPackage reconciledPkg : commitRequest.mReconciledPackages.values()) { |
| final boolean instantApp = ((reconciledPkg.mScanResult.mRequest.mScanFlags |
| & SCAN_AS_INSTANT_APP) != 0); |
| final AndroidPackage pkg = reconciledPkg.mPkgSetting.getPkg(); |
| final String packageName = pkg.getPackageName(); |
| final String codePath = pkg.getPath(); |
| final boolean onIncremental = mIncrementalManager != null |
| && isIncrementalPath(codePath); |
| if (onIncremental) { |
| IncrementalStorage storage = mIncrementalManager.openStorage(codePath); |
| if (storage == null) { |
| throw new IllegalArgumentException( |
| "Install: null storage for incremental package " + packageName); |
| } |
| incrementalStorages.add(storage); |
| } |
| // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088) |
| mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0); |
| if (reconciledPkg.mPrepareResult.mClearCodeCache) { |
| mAppDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL, |
| FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL |
| | Installer.FLAG_CLEAR_CODE_CACHE_ONLY); |
| } |
| if (reconciledPkg.mPrepareResult.mReplace) { |
| mDexManager.notifyPackageUpdated(pkg.getPackageName(), |
| pkg.getBaseApkPath(), pkg.getSplitCodePaths()); |
| } |
| |
| // Prepare the application profiles for the new code paths. |
| // This needs to be done before invoking dexopt so that any install-time profile |
| // can be used for optimizations. |
| mArtManagerService.prepareAppProfiles( |
| pkg, |
| mPm.resolveUserIds(reconciledPkg.mInstallArgs.mUser.getIdentifier()), |
| /* updateReferenceProfileContent= */ true); |
| |
| // Compute the compilation reason from the installation scenario. |
| final int compilationReason = |
| mDexManager.getCompilationReasonForInstallScenario( |
| reconciledPkg.mInstallArgs.mInstallScenario); |
| |
| // Construct the DexoptOptions early to see if we should skip running dexopt. |
| // |
| // Do not run PackageDexOptimizer through the local performDexOpt |
| // method because `pkg` may not be in `mPackages` yet. |
| // |
| // Also, don't fail application installs if the dexopt step fails. |
| final boolean isBackupOrRestore = |
| reconciledPkg.mInstallArgs.mInstallReason == INSTALL_REASON_DEVICE_RESTORE |
| || reconciledPkg.mInstallArgs.mInstallReason |
| == INSTALL_REASON_DEVICE_SETUP; |
| |
| final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE |
| | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE |
| | (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0); |
| DexoptOptions dexoptOptions = |
| new DexoptOptions(packageName, compilationReason, dexoptFlags); |
| |
| // Check whether we need to dexopt the app. |
| // |
| // NOTE: it is IMPORTANT to call dexopt: |
| // - after doRename which will sync the package data from AndroidPackage and |
| // its corresponding ApplicationInfo. |
| // - after installNewPackageLIF or replacePackageLIF which will update result with the |
| // uid of the application (pkg.applicationInfo.uid). |
| // This update happens in place! |
| // |
| // We only need to dexopt if the package meets ALL of the following conditions: |
| // 1) it is not an instant app or if it is then dexopt is enabled via gservices. |
| // 2) it is not debuggable. |
| // 3) it is not on Incremental File System. |
| // |
| // Note that we do not dexopt instant apps by default. dexopt can take some time to |
| // complete, so we skip this step during installation. Instead, we'll take extra time |
| // the first time the instant app starts. It's preferred to do it this way to provide |
| // continuous progress to the useur instead of mysteriously blocking somewhere in the |
| // middle of running an instant app. The default behaviour can be overridden |
| // via gservices. |
| // |
| // Furthermore, dexopt may be skipped, depending on the install scenario and current |
| // state of the device. |
| // |
| // TODO(b/174695087): instantApp and onIncremental should be removed and their install |
| // path moved to SCENARIO_FAST. |
| final boolean performDexopt = |
| (!instantApp || android.provider.Settings.Global.getInt( |
| mContext.getContentResolver(), |
| android.provider.Settings.Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0) |
| && !pkg.isDebuggable() |
| && (!onIncremental) |
| && dexoptOptions.isCompilationEnabled(); |
| |
| if (performDexopt) { |
| // Compile the layout resources. |
| if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) { |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts"); |
| mViewCompiler.compileLayouts(pkg); |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); |
| ScanResult result = reconciledPkg.mScanResult; |
| |
| // This mirrors logic from commitReconciledScanResultLocked, where the library files |
| // needed for dexopt are assigned. |
| // TODO: Fix this to have 1 mutable PackageSetting for scan/install. If the previous |
| // setting needs to be passed to have a comparison, hide it behind an immutable |
| // interface. There's no good reason to have 3 different ways to access the real |
| // PackageSetting object, only one of which is actually correct. |
| PackageSetting realPkgSetting = result.mExistingSettingCopied |
| ? result.mRequest.mPkgSetting : result.mPkgSetting; |
| if (realPkgSetting == null) { |
| realPkgSetting = reconciledPkg.mPkgSetting; |
| } |
| |
| // Unfortunately, the updated system app flag is only tracked on this PackageSetting |
| boolean isUpdatedSystemApp = reconciledPkg.mPkgSetting.getPkgState() |
| .isUpdatedSystemApp(); |
| |
| realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp); |
| |
| mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting, |
| null /* instructionSets */, |
| mPm.getOrCreateCompilerPackageStats(pkg), |
| mDexManager.getPackageUseInfoOrDefault(packageName), |
| dexoptOptions); |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| |
| // Notify BackgroundDexOptService that the package has been changed. |
| // If this is an update of a package which used to fail to compile, |
| // BackgroundDexOptService will remove it from its denylist. |
| // TODO: Layering violation |
| BackgroundDexOptService.getService().notifyPackageChanged(packageName); |
| |
| notifyPackageChangeObserversOnUpdate(reconciledPkg); |
| } |
| PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental( |
| incrementalStorages); |
| } |
| |
| private void notifyPackageChangeObserversOnUpdate(ReconciledPackage reconciledPkg) { |
| final PackageSetting pkgSetting = reconciledPkg.mPkgSetting; |
| final PackageInstalledInfo pkgInstalledInfo = reconciledPkg.mInstallResult; |
| final PackageRemovedInfo pkgRemovedInfo = pkgInstalledInfo.mRemovedInfo; |
| |
| PackageChangeEvent pkgChangeEvent = new PackageChangeEvent(); |
| pkgChangeEvent.packageName = pkgSetting.getPkg().getPackageName(); |
| pkgChangeEvent.version = pkgSetting.getVersionCode(); |
| pkgChangeEvent.lastUpdateTimeMillis = pkgSetting.getLastUpdateTime(); |
| pkgChangeEvent.newInstalled = (pkgRemovedInfo == null || !pkgRemovedInfo.mIsUpdate); |
| pkgChangeEvent.dataRemoved = (pkgRemovedInfo != null && pkgRemovedInfo.mDataRemoved); |
| pkgChangeEvent.isDeleted = false; |
| |
| mPm.notifyPackageChangeObservers(pkgChangeEvent); |
| } |
| |
| public int installLocationPolicy(PackageInfoLite pkgLite, int installFlags) { |
| String packageName = pkgLite.packageName; |
| int installLocation = pkgLite.installLocation; |
| // reader |
| synchronized (mPm.mLock) { |
| // Currently installed package which the new package is attempting to replace or |
| // null if no such package is installed. |
| AndroidPackage installedPkg = mPm.mPackages.get(packageName); |
| |
| if (installedPkg != null) { |
| if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { |
| // Check for updated system application. |
| if (installedPkg.isSystem()) { |
| return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL; |
| } else { |
| // If current upgrade specifies particular preference |
| if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { |
| // Application explicitly specified internal. |
| return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL; |
| } else if ( |
| installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { |
| // App explicitly prefers external. Let policy decide |
| } else { |
| // Prefer previous location |
| if (installedPkg.isExternalStorage()) { |
| return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL; |
| } |
| return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL; |
| } |
| } |
| } else { |
| // Invalid install. Return error code |
| return InstallLocationUtils.RECOMMEND_FAILED_ALREADY_EXISTS; |
| } |
| } |
| } |
| return pkgLite.recommendedInstallLocation; |
| } |
| |
| Pair<Integer, String> verifyReplacingVersionCode(PackageInfoLite pkgLite, |
| long requiredInstalledVersionCode, int installFlags) { |
| if ((installFlags & PackageManager.INSTALL_APEX) != 0) { |
| return verifyReplacingVersionCodeForApex( |
| pkgLite, requiredInstalledVersionCode, installFlags); |
| } |
| |
| String packageName = pkgLite.packageName; |
| synchronized (mPm.mLock) { |
| // Package which currently owns the data that the new package will own if installed. |
| // If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg |
| // will be null whereas dataOwnerPkg will contain information about the package |
| // which was uninstalled while keeping its data. |
| AndroidPackage dataOwnerPkg = mPm.mPackages.get(packageName); |
| if (dataOwnerPkg == null) { |
| PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); |
| if (ps != null) { |
| dataOwnerPkg = ps.getPkg(); |
| } |
| } |
| |
| if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) { |
| if (dataOwnerPkg == null) { |
| String errorMsg = "Required installed version code was " |
| + requiredInstalledVersionCode |
| + " but package is not installed"; |
| Slog.w(TAG, errorMsg); |
| return Pair.create( |
| PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg); |
| } |
| |
| if (dataOwnerPkg.getLongVersionCode() != requiredInstalledVersionCode) { |
| String errorMsg = "Required installed version code was " |
| + requiredInstalledVersionCode |
| + " but actual installed version is " |
| + dataOwnerPkg.getLongVersionCode(); |
| Slog.w(TAG, errorMsg); |
| return Pair.create( |
| PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg); |
| } |
| } |
| |
| if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) { |
| if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags, |
| dataOwnerPkg.isDebuggable())) { |
| try { |
| PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite); |
| } catch (PackageManagerException e) { |
| String errorMsg = "Downgrade detected: " + e.getMessage(); |
| Slog.w(TAG, errorMsg); |
| return Pair.create( |
| PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg); |
| } |
| } |
| } |
| } |
| return Pair.create(PackageManager.INSTALL_SUCCEEDED, null); |
| } |
| |
| private Pair<Integer, String> verifyReplacingVersionCodeForApex(PackageInfoLite pkgLite, |
| long requiredInstalledVersionCode, int installFlags) { |
| String packageName = pkgLite.packageName; |
| |
| final PackageInfo activePackage = mApexManager.getPackageInfo(packageName, |
| ApexManager.MATCH_ACTIVE_PACKAGE); |
| if (activePackage == null) { |
| String errorMsg = "Attempting to install new APEX package " + packageName; |
| Slog.w(TAG, errorMsg); |
| return Pair.create(PackageManager.INSTALL_FAILED_PACKAGE_CHANGED, errorMsg); |
| } |
| |
| final long activeVersion = activePackage.getLongVersionCode(); |
| if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST |
| && activeVersion != requiredInstalledVersionCode) { |
| String errorMsg = "Installed version of APEX package " + packageName |
| + " does not match required. Active version: " + activeVersion |
| + " required: " + requiredInstalledVersionCode; |
| Slog.w(TAG, errorMsg); |
| return Pair.create(PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg); |
| } |
| |
| final boolean isAppDebuggable = (activePackage.applicationInfo.flags |
| & ApplicationInfo.FLAG_DEBUGGABLE) != 0; |
| final long newVersionCode = pkgLite.getLongVersionCode(); |
| if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags, isAppDebuggable) |
| && newVersionCode < activeVersion) { |
| String errorMsg = "Downgrade of APEX package " + packageName |
| + " is not allowed. Active version: " + activeVersion |
| + " attempted: " + newVersionCode; |
| Slog.w(TAG, errorMsg); |
| return Pair.create(PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg); |
| } |
| |
| return Pair.create(PackageManager.INSTALL_SUCCEEDED, null); |
| } |
| |
| int getUidForVerifier(VerifierInfo verifierInfo) { |
| synchronized (mPm.mLock) { |
| final AndroidPackage pkg = mPm.mPackages.get(verifierInfo.packageName); |
| if (pkg == null) { |
| return -1; |
| } else if (pkg.getSigningDetails().getSignatures().length != 1) { |
| Slog.i(TAG, "Verifier package " + verifierInfo.packageName |
| + " has more than one signature; ignoring"); |
| return -1; |
| } |
| |
| /* |
| * If the public key of the package's signature does not match |
| * our expected public key, then this is a different package and |
| * we should skip. |
| */ |
| |
| final byte[] expectedPublicKey; |
| try { |
| final Signature verifierSig = pkg.getSigningDetails().getSignatures()[0]; |
| final PublicKey publicKey = verifierSig.getPublicKey(); |
| expectedPublicKey = publicKey.getEncoded(); |
| } catch (CertificateException e) { |
| return -1; |
| } |
| |
| final byte[] actualPublicKey = verifierInfo.publicKey.getEncoded(); |
| |
| if (!Arrays.equals(actualPublicKey, expectedPublicKey)) { |
| Slog.i(TAG, "Verifier package " + verifierInfo.packageName |
| + " does not have the expected public key; ignoring"); |
| return -1; |
| } |
| |
| return pkg.getUid(); |
| } |
| } |
| |
| public void sendPendingBroadcasts() { |
| String[] packages; |
| ArrayList<String>[] components; |
| int numBroadcasts = 0, numUsers; |
| int[] uids; |
| |
| synchronized (mPm.mLock) { |
| final SparseArray<ArrayMap<String, ArrayList<String>>> userIdToPackagesToComponents = |
| mPm.mPendingBroadcasts.copiedMap(); |
| numUsers = userIdToPackagesToComponents.size(); |
| for (int n = 0; n < numUsers; n++) { |
| numBroadcasts += userIdToPackagesToComponents.valueAt(n).size(); |
| } |
| if (numBroadcasts == 0) { |
| // Nothing to be done. Just return |
| return; |
| } |
| packages = new String[numBroadcasts]; |
| components = new ArrayList[numBroadcasts]; |
| uids = new int[numBroadcasts]; |
| int i = 0; // filling out the above arrays |
| |
| for (int n = 0; n < numUsers; n++) { |
| final int packageUserId = userIdToPackagesToComponents.keyAt(n); |
| final ArrayMap<String, ArrayList<String>> componentsToBroadcast = |
| userIdToPackagesToComponents.valueAt(n); |
| final int numComponents = CollectionUtils.size(componentsToBroadcast); |
| for (int index = 0; index < numComponents; index++) { |
| packages[i] = componentsToBroadcast.keyAt(index); |
| components[i] = componentsToBroadcast.valueAt(index); |
| final PackageSetting ps = mPm.mSettings.getPackageLPr(packages[i]); |
| uids[i] = (ps != null) |
| ? UserHandle.getUid(packageUserId, ps.getAppId()) |
| : -1; |
| i++; |
| } |
| } |
| numBroadcasts = i; |
| mPm.mPendingBroadcasts.clear(); |
| } |
| final Computer snapshot = mPm.snapshotComputer(); |
| // Send broadcasts |
| for (int i = 0; i < numBroadcasts; i++) { |
| mPm.sendPackageChangedBroadcast(snapshot, packages[i], true /* dontKillApp */, |
| components[i], uids[i], null /* reason */); |
| } |
| } |
| |
| void handlePackagePostInstall(PackageInstalledInfo res, InstallArgs installArgs, |
| boolean launchedForRestore) { |
| final boolean killApp = |
| (installArgs.mInstallFlags & PackageManager.INSTALL_DONT_KILL_APP) == 0; |
| final boolean virtualPreload = |
| ((installArgs.mInstallFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0); |
| final String installerPackage = installArgs.mInstallSource.installerPackageName; |
| final IPackageInstallObserver2 installObserver = installArgs.mObserver; |
| final int dataLoaderType = installArgs.mDataLoaderType; |
| final boolean succeeded = res.mReturnCode == PackageManager.INSTALL_SUCCEEDED; |
| final boolean update = res.mRemovedInfo != null && res.mRemovedInfo.mRemovedPackage != null; |
| final String packageName = res.mName; |
| final PackageStateInternal pkgSetting = |
| succeeded ? mPm.snapshotComputer().getPackageStateInternal(packageName) : null; |
| final boolean removedBeforeUpdate = (pkgSetting == null) |
| || (pkgSetting.isSystem() && !pkgSetting.getPath().getPath().equals( |
| res.mPkg.getPath())); |
| if (succeeded && removedBeforeUpdate) { |
| Slog.e(TAG, packageName + " was removed before handlePackagePostInstall " |
| + "could be executed"); |
| res.mReturnCode = INSTALL_FAILED_PACKAGE_CHANGED; |
| res.mReturnMsg = "Package was removed before install could complete."; |
| |
| // Remove the update failed package's older resources safely now |
| InstallArgs args = res.mRemovedInfo != null ? res.mRemovedInfo.mArgs : null; |
| if (args != null) { |
| synchronized (mPm.mInstallLock) { |
| args.doPostDeleteLI(true); |
| } |
| } |
| mPm.notifyInstallObserver(res, installObserver); |
| return; |
| } |
| |
| if (succeeded) { |
| // Clear the uid cache after we installed a new package. |
| mPm.mPerUidReadTimeoutsCache = null; |
| |
| // Send the removed broadcasts |
| if (res.mRemovedInfo != null) { |
| if (res.mRemovedInfo.mIsExternal) { |
| if (DEBUG_INSTALL) { |
| Slog.i(TAG, "upgrading pkg " + res.mRemovedInfo.mRemovedPackage |
| + " is ASEC-hosted -> UNAVAILABLE"); |
| } |
| final int[] uidArray = new int[]{res.mRemovedInfo.mUid}; |
| final ArrayList<String> pkgList = new ArrayList<>(1); |
| pkgList.add(res.mRemovedInfo.mRemovedPackage); |
| mBroadcastHelper.sendResourcesChangedBroadcast( |
| false, true, pkgList, uidArray, null); |
| } |
| res.mRemovedInfo.sendPackageRemovedBroadcasts(killApp, false /*removedBySystem*/); |
| } |
| |
| final String installerPackageName = |
| res.mInstallerPackageName != null |
| ? res.mInstallerPackageName |
| : res.mRemovedInfo != null |
| ? res.mRemovedInfo.mInstallerPackageName |
| : null; |
| |
| mPm.notifyInstantAppPackageInstalled(res.mPkg.getPackageName(), res.mNewUsers); |
| |
| // Determine the set of users who are adding this package for |
| // the first time vs. those who are seeing an update. |
| int[] firstUserIds = EMPTY_INT_ARRAY; |
| int[] firstInstantUserIds = EMPTY_INT_ARRAY; |
| int[] updateUserIds = EMPTY_INT_ARRAY; |
| int[] instantUserIds = EMPTY_INT_ARRAY; |
| final boolean allNewUsers = res.mOrigUsers == null || res.mOrigUsers.length == 0; |
| for (int newUser : res.mNewUsers) { |
| final boolean isInstantApp = pkgSetting.getUserStateOrDefault(newUser) |
| .isInstantApp(); |
| if (allNewUsers) { |
| if (isInstantApp) { |
| firstInstantUserIds = ArrayUtils.appendInt(firstInstantUserIds, newUser); |
| } else { |
| firstUserIds = ArrayUtils.appendInt(firstUserIds, newUser); |
| } |
| continue; |
| } |
| boolean isNew = true; |
| for (int origUser : res.mOrigUsers) { |
| if (origUser == newUser) { |
| isNew = false; |
| break; |
| } |
| } |
| if (isNew) { |
| if (isInstantApp) { |
| firstInstantUserIds = ArrayUtils.appendInt(firstInstantUserIds, newUser); |
| } else { |
| firstUserIds = ArrayUtils.appendInt(firstUserIds, newUser); |
| } |
| } else { |
| if (isInstantApp) { |
| instantUserIds = ArrayUtils.appendInt(instantUserIds, newUser); |
| } else { |
| updateUserIds = ArrayUtils.appendInt(updateUserIds, newUser); |
| } |
| } |
| } |
| |
| // Send installed broadcasts if the package is not a static shared lib. |
| if (res.mPkg.getStaticSharedLibName() == null) { |
| mPm.mProcessLoggingHandler.invalidateBaseApkHash(res.mPkg.getBaseApkPath()); |
| |
| // Send added for users that see the package for the first time |
| // sendPackageAddedForNewUsers also deals with system apps |
| int appId = UserHandle.getAppId(res.mUid); |
| boolean isSystem = res.mPkg.isSystem(); |
| mPm.sendPackageAddedForNewUsers(mPm.snapshotComputer(), packageName, |
| isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId, |
| firstUserIds, firstInstantUserIds, dataLoaderType); |
| |
| // Send added for users that don't see the package for the first time |
| Bundle extras = new Bundle(); |
| extras.putInt(Intent.EXTRA_UID, res.mUid); |
| if (update) { |
| extras.putBoolean(Intent.EXTRA_REPLACING, true); |
| } |
| extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType); |
| // Send to all running apps. |
| final SparseArray<int[]> newBroadcastAllowList; |
| synchronized (mPm.mLock) { |
| final Computer snapshot = mPm.snapshotComputer(); |
| newBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(snapshot, |
| snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID), |
| updateUserIds, mPm.mSettings.getPackagesLocked()); |
| } |
| mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, |
| extras, 0 /*flags*/, |
| null /*targetPackage*/, null /*finishedReceiver*/, |
| updateUserIds, instantUserIds, newBroadcastAllowList, null); |
| if (installerPackageName != null) { |
| // Send to the installer, even if it's not running. |
| mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, |
| extras, 0 /*flags*/, |
| installerPackageName, null /*finishedReceiver*/, |
| updateUserIds, instantUserIds, null /* broadcastAllowList */, null); |
| } |
| // if the required verifier is defined, but, is not the installer of record |
| // for the package, it gets notified |
| final boolean notifyVerifier = mPm.mRequiredVerifierPackage != null |
| && !mPm.mRequiredVerifierPackage.equals(installerPackageName); |
| if (notifyVerifier) { |
| mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, |
| extras, 0 /*flags*/, |
| mPm.mRequiredVerifierPackage, null /*finishedReceiver*/, |
| updateUserIds, instantUserIds, null /* broadcastAllowList */, null); |
| } |
| // If package installer is defined, notify package installer about new |
| // app installed |
| if (mPm.mRequiredInstallerPackage != null) { |
| mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, |
| extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/, |
| mPm.mRequiredInstallerPackage, null /*finishedReceiver*/, |
| firstUserIds, instantUserIds, null /* broadcastAllowList */, null); |
| } |
| |
| // Send replaced for users that don't see the package for the first time |
| if (update) { |
| mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, |
| packageName, extras, 0 /*flags*/, |
| null /*targetPackage*/, null /*finishedReceiver*/, |
| updateUserIds, instantUserIds, res.mRemovedInfo.mBroadcastAllowList, |
| null); |
| if (installerPackageName != null) { |
| mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, |
| extras, 0 /*flags*/, |
| installerPackageName, null /*finishedReceiver*/, |
| updateUserIds, instantUserIds, null /*broadcastAllowList*/, |
| null); |
| } |
| if (notifyVerifier) { |
| mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, |
| extras, 0 /*flags*/, |
| mPm.mRequiredVerifierPackage, null /*finishedReceiver*/, |
| updateUserIds, instantUserIds, null /*broadcastAllowList*/, |
| null); |
| } |
| mPm.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, |
| null /*package*/, null /*extras*/, 0 /*flags*/, |
| packageName /*targetPackage*/, |
| null /*finishedReceiver*/, updateUserIds, instantUserIds, |
| null /*broadcastAllowList*/, |
| mBroadcastHelper.getTemporaryAppAllowlistBroadcastOptions( |
| REASON_PACKAGE_REPLACED).toBundle()); |
| } else if (launchedForRestore && !res.mPkg.isSystem()) { |
| // First-install and we did a restore, so we're responsible for the |
| // first-launch broadcast. |
| if (DEBUG_BACKUP) { |
| Slog.i(TAG, "Post-restore of " + packageName |
| + " sending FIRST_LAUNCH in " + Arrays.toString(firstUserIds)); |
| } |
| mBroadcastHelper.sendFirstLaunchBroadcast(packageName, installerPackage, |
| firstUserIds, firstInstantUserIds); |
| } |
| |
| // Send broadcast package appeared if external for all users |
| if (res.mPkg.isExternalStorage()) { |
| if (!update) { |
| final StorageManager storageManager = |
| mInjector.getSystemService(StorageManager.class); |
| VolumeInfo volume = |
| storageManager.findVolumeByUuid( |
| StorageManager.convert( |
| res.mPkg.getVolumeUuid()).toString()); |
| int packageExternalStorageType = |
| PackageManagerServiceUtils.getPackageExternalStorageType(volume, |
| res.mPkg.isExternalStorage()); |
| // If the package was installed externally, log it. |
| if (packageExternalStorageType != StorageEnums.UNKNOWN) { |
| FrameworkStatsLog.write( |
| FrameworkStatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED, |
| packageExternalStorageType, packageName); |
| } |
| } |
| if (DEBUG_INSTALL) { |
| Slog.i(TAG, "upgrading pkg " + res.mPkg + " is external"); |
| } |
| final int[] uidArray = new int[]{res.mPkg.getUid()}; |
| ArrayList<String> pkgList = new ArrayList<>(1); |
| pkgList.add(packageName); |
| mBroadcastHelper.sendResourcesChangedBroadcast( |
| true, true, pkgList, uidArray, null); |
| } |
| } else if (!ArrayUtils.isEmpty(res.mLibraryConsumers)) { // if static shared lib |
| // No need to kill consumers if it's installation of new version static shared lib. |
| final Computer snapshot = mPm.snapshotComputer(); |
| final boolean dontKillApp = !update && res.mPkg.getStaticSharedLibName() != null; |
| for (int i = 0; i < res.mLibraryConsumers.size(); i++) { |
| AndroidPackage pkg = res.mLibraryConsumers.get(i); |
| // send broadcast that all consumers of the static shared library have changed |
| mPm.sendPackageChangedBroadcast(snapshot, pkg.getPackageName(), dontKillApp, |
| new ArrayList<>(Collections.singletonList(pkg.getPackageName())), |
| pkg.getUid(), null); |
| } |
| } |
| |
| // Work that needs to happen on first install within each user |
| if (firstUserIds.length > 0) { |
| for (int userId : firstUserIds) { |
| mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName, |
| userId); |
| } |
| } |
| |
| if (allNewUsers && !update) { |
| mPm.notifyPackageAdded(packageName, res.mUid); |
| } else { |
| mPm.notifyPackageChanged(packageName, res.mUid); |
| } |
| |
| // Log current value of "unknown sources" setting |
| EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED, |
| getUnknownSourcesSettings()); |
| |
| // Remove the replaced package's older resources safely now |
| InstallArgs args = res.mRemovedInfo != null ? res.mRemovedInfo.mArgs : null; |
| if (args != null) { |
| if (!killApp) { |
| // If we didn't kill the app, defer the deletion of code/resource files, since |
| // they may still be in use by the running application. This mitigates problems |
| // in cases where resources or code is loaded by a new Activity before |
| // ApplicationInfo changes have propagated to all application threads. |
| mPm.scheduleDeferredNoKillPostDelete(args); |
| } else { |
| synchronized (mPm.mInstallLock) { |
| args.doPostDeleteLI(true); |
| } |
| } |
| } else { |
| // Force a gc to clear up things. Ask for a background one, it's fine to go on |
| // and not block here. |
| VMRuntime.getRuntime().requestConcurrentGC(); |
| } |
| |
| final Computer snapshot = mPm.snapshotComputer(); |
| // Notify DexManager that the package was installed for new users. |
| // The updated users should already be indexed and the package code paths |
| // should not change. |
| // Don't notify the manager for ephemeral apps as they are not expected to |
| // survive long enough to benefit of background optimizations. |
| for (int userId : firstUserIds) { |
| PackageInfo info = snapshot.getPackageInfo(packageName, /*flags*/ 0, userId); |
| // There's a race currently where some install events may interleave with an |
| // uninstall. This can lead to package info being null (b/36642664). |
| if (info != null) { |
| mDexManager.notifyPackageInstalled(info, userId); |
| } |
| } |
| } |
| |
| final boolean deferInstallObserver = succeeded && update; |
| if (deferInstallObserver) { |
| if (killApp) { |
| mPm.scheduleDeferredPendingKillInstallObserver(res, installObserver); |
| } else { |
| mPm.scheduleDeferredNoKillInstallObserver(res, installObserver); |
| } |
| } else { |
| mPm.notifyInstallObserver(res, installObserver); |
| } |
| |
| // Prune unused static shared libraries which have been cached a period of time |
| mPm.schedulePruneUnusedStaticSharedLibraries(true /* delay */); |
| |
| // Log tracing if needed |
| if (installArgs.mTraceMethod != null) { |
| Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, installArgs.mTraceMethod, |
| installArgs.mTraceCookie); |
| } |
| } |
| |
| /** |
| * Get the "allow unknown sources" setting. |
| * |
| * @return the current "allow unknown sources" setting |
| */ |
| private int getUnknownSourcesSettings() { |
| return android.provider.Settings.Secure.getIntForUser(mContext.getContentResolver(), |
| android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS, |
| -1, UserHandle.USER_SYSTEM); |
| } |
| |
| /** |
| * Uncompress and install stub applications. |
| * <p>In order to save space on the system partition, some applications are shipped in a |
| * compressed form. In addition the compressed bits for the full application, the |
| * system image contains a tiny stub comprised of only the Android manifest. |
| * <p>During the first boot, attempt to uncompress and install the full application. If |
| * the application can't be installed for any reason, disable the stub and prevent |
| * uncompressing the full application during future boots. |
| * <p>In order to forcefully attempt an installation of a full application, go to app |
| * settings and enable the application. |
| */ |
| @GuardedBy({"mPm.mLock", "mPm.mInstallLock"}) |
| void installSystemStubPackages(@NonNull List<String> systemStubPackageNames, |
| @PackageManagerService.ScanFlags int scanFlags) { |
| for (int i = systemStubPackageNames.size() - 1; i >= 0; --i) { |
| final String packageName = systemStubPackageNames.get(i); |
| // skip if the system package is already disabled |
| if (mPm.mSettings.isDisabledSystemPackageLPr(packageName)) { |
| systemStubPackageNames.remove(i); |
| continue; |
| } |
| // skip if the package isn't installed (?!); this should never happen |
| final AndroidPackage pkg = mPm.mPackages.get(packageName); |
| if (pkg == null) { |
| systemStubPackageNames.remove(i); |
| continue; |
| } |
| // skip if the package has been disabled by the user |
| final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); |
| if (ps != null) { |
| final int enabledState = ps.getEnabled(UserHandle.USER_SYSTEM); |
| if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { |
| systemStubPackageNames.remove(i); |
| continue; |
| } |
| } |
| |
| // install the package to replace the stub on /system |
| try { |
| installStubPackageLI(pkg, 0, scanFlags); |
| ps.setEnabled(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, |
| UserHandle.USER_SYSTEM, "android"); |
| systemStubPackageNames.remove(i); |
| } catch (PackageManagerException e) { |
| Slog.e(TAG, "Failed to parse uncompressed system package: " + e.getMessage()); |
| } |
| |
| // any failed attempt to install the package will be cleaned up later |
| } |
| |
| // disable any stub still left; these failed to install the full application |
| for (int i = systemStubPackageNames.size() - 1; i >= 0; --i) { |
| final String pkgName = systemStubPackageNames.get(i); |
| final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName); |
| ps.setEnabled(PackageManager.COMPONENT_ENABLED_STATE_DISABLED, |
| UserHandle.USER_SYSTEM, "android"); |
| logCriticalInfo(Log.ERROR, "Stub disabled; pkg: " + pkgName); |
| } |
| } |
| |
| /** |
| * Extract, install and enable a stub package. |
| * <p>If the compressed file can not be extracted / installed for any reason, the stub |
| * APK will be installed and the package will be disabled. To recover from this situation, |
| * the user will need to go into system settings and re-enable the package. |
| */ |
| boolean enableCompressedPackage(AndroidPackage stubPkg, |
| @NonNull PackageSetting stubPkgSetting) { |
| final int parseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY |
| | ParsingPackageUtils.PARSE_ENFORCE_CODE; |
| synchronized (mPm.mInstallLock) { |
| final AndroidPackage pkg; |
| try (PackageFreezer freezer = |
| mPm.freezePackage(stubPkg.getPackageName(), "setEnabledSetting")) { |
| pkg = installStubPackageLI(stubPkg, parseFlags, 0 /*scanFlags*/); |
| mAppDataHelper.prepareAppDataAfterInstallLIF(pkg); |
| synchronized (mPm.mLock) { |
| try { |
| mSharedLibraries.updateSharedLibrariesLPw( |
| pkg, stubPkgSetting, null, null, |
| Collections.unmodifiableMap(mPm.mPackages)); |
| } catch (PackageManagerException e) { |
| Slog.w(TAG, "updateAllSharedLibrariesLPw failed: ", e); |
| } |
| mPm.mPermissionManager.onPackageInstalled(pkg, |
| Process.INVALID_UID /* previousAppId */, |
| PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT, |
| UserHandle.USER_ALL); |
| mPm.writeSettingsLPrTEMP(); |
| // Since compressed package can be system app only, we do not need to |
| // set restricted settings on it. |
| } |
| } catch (PackageManagerException e) { |
| // Whoops! Something went very wrong; roll back to the stub and disable the package |
| try (PackageFreezer freezer = |
| mPm.freezePackage(stubPkg.getPackageName(), "setEnabledSetting")) { |
| synchronized (mPm.mLock) { |
| // NOTE: Ensure the system package is enabled; even for a compressed stub. |
| // If we don't, installing the system package fails during scan |
| mPm.mSettings.enableSystemPackageLPw(stubPkg.getPackageName()); |
| } |
| installPackageFromSystemLIF(stubPkg.getPath(), |
| mPm.mUserManager.getUserIds() /*allUserHandles*/, |
| null /*origUserHandles*/, |
| true /*writeSettings*/); |
| } catch (PackageManagerException pme) { |
| // Serious WTF; we have to be able to install the stub |
| Slog.wtf(TAG, "Failed to restore system package:" + stubPkg.getPackageName(), |
| pme); |
| } finally { |
| // Disable the package; the stub by itself is not runnable |
| synchronized (mPm.mLock) { |
| final PackageSetting stubPs = mPm.mSettings.getPackageLPr( |
| stubPkg.getPackageName()); |
| if (stubPs != null) { |
| stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, |
| UserHandle.USER_SYSTEM, "android"); |
| } |
| mPm.writeSettingsLPrTEMP(); |
| } |
| } |
| return false; |
| } |
| mAppDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL, |
| FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL |
| | Installer.FLAG_CLEAR_CODE_CACHE_ONLY); |
| mDexManager.notifyPackageUpdated(pkg.getPackageName(), |
| pkg.getBaseApkPath(), pkg.getSplitCodePaths()); |
| } |
| return true; |
| } |
| |
| @GuardedBy("mPm.mInstallLock") |
| private AndroidPackage installStubPackageLI(AndroidPackage stubPkg, |
| @ParsingPackageUtils.ParseFlags int parseFlags, |
| @PackageManagerService.ScanFlags int scanFlags) |
| throws PackageManagerException { |
| if (DEBUG_COMPRESSION) { |
| Slog.i(TAG, "Uncompressing system stub; pkg: " + stubPkg.getPackageName()); |
| } |
| // uncompress the binary to its eventual destination on /data |
| final File scanFile = decompressPackage(stubPkg.getPackageName(), stubPkg.getPath()); |
| if (scanFile == null) { |
| throw new PackageManagerException( |
| "Unable to decompress stub at " + stubPkg.getPath()); |
| } |
| synchronized (mPm.mLock) { |
| mPm.mSettings.disableSystemPackageLPw(stubPkg.getPackageName(), true /*replaced*/); |
| } |
| final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm); |
| removePackageHelper.removePackageLI(stubPkg, true /*chatty*/); |
| try { |
| return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, null); |
| } catch (PackageManagerException e) { |
| Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(), |
| e); |
| // Remove the failed install |
| removePackageHelper.removeCodePathLI(scanFile); |
| throw e; |
| } |
| } |
| |
| /** |
| * Decompresses the given package on the system image onto |
| * the /data partition. |
| * @return The directory the package was decompressed into. Otherwise, {@code null}. |
| */ |
| @GuardedBy("mPm.mInstallLock") |
| private File decompressPackage(String packageName, String codePath) { |
| if (!compressedFileExists(codePath)) { |
| if (DEBUG_COMPRESSION) { |
| Slog.i(TAG, "No files to decompress at: " + codePath); |
| } |
| return null; |
| } |
| final File dstCodePath = |
| PackageManagerServiceUtils.getNextCodePath(Environment.getDataAppDirectory(null), |
| packageName); |
| int ret = PackageManagerServiceUtils.decompressFiles(codePath, dstCodePath, packageName); |
| if (ret == PackageManager.INSTALL_SUCCEEDED) { |
| ret = PackageManagerServiceUtils.extractNativeBinaries(dstCodePath, packageName); |
| } |
| if (ret == PackageManager.INSTALL_SUCCEEDED) { |
| // NOTE: During boot, we have to delay releasing cblocks for no other reason than |
| // we cannot retrieve the setting {@link Secure#RELEASE_COMPRESS_BLOCKS_ON_INSTALL}. |
| // When we no longer need to read that setting, cblock release can occur always |
| // occur here directly |
| if (!mPm.isSystemReady()) { |
| if (mPm.mReleaseOnSystemReady == null) { |
| mPm.mReleaseOnSystemReady = new ArrayList<>(); |
| } |
| mPm.mReleaseOnSystemReady.add(dstCodePath); |
| } else { |
| final ContentResolver resolver = mContext.getContentResolver(); |
| F2fsUtils.releaseCompressedBlocks(resolver, dstCodePath); |
| } |
| } else { |
| if (!dstCodePath.exists()) { |
| return null; |
| } |
| new RemovePackageHelper(mPm).removeCodePathLI(dstCodePath); |
| return null; |
| } |
| |
| return dstCodePath; |
| } |
| |
| /** |
| * Tries to restore the disabled system package after an update has been deleted. |
| */ |
| public void restoreDisabledSystemPackageLIF(DeletePackageAction action, |
| @NonNull int[] allUserHandles, boolean writeSettings) throws SystemDeleteException { |
| final PackageSetting deletedPs = action.mDeletingPs; |
| final PackageRemovedInfo outInfo = action.mRemovedInfo; |
| final PackageSetting disabledPs = action.mDisabledPs; |
| |
| synchronized (mPm.mLock) { |
| // NOTE: The system package always needs to be enabled; even if it's for |
| // a compressed stub. If we don't, installing the system package fails |
| // during scan [scanning checks the disabled packages]. We will reverse |
| // this later, after we've "installed" the stub. |
| // Reinstate the old system package |
| mPm.mSettings.enableSystemPackageLPw(disabledPs.getPkg().getPackageName()); |
| // Remove any native libraries from the upgraded package. |
| PackageManagerServiceUtils.removeNativeBinariesLI(deletedPs); |
| } |
| // Install the system package |
| if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs); |
| try { |
| synchronized (mPm.mInstallLock) { |
| final int[] origUsers = outInfo == null ? null : outInfo.mOrigUsers; |
| installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles, |
| origUsers, writeSettings); |
| } |
| } catch (PackageManagerException e) { |
| Slog.w(TAG, "Failed to restore system package:" + deletedPs.getPackageName() + ": " |
| + e.getMessage()); |
| // TODO(b/194319951): can we avoid this; throw would come from scan... |
| throw new SystemDeleteException(e); |
| } finally { |
| if (disabledPs.getPkg().isStub()) { |
| // We've re-installed the stub; make sure it's disabled here. If package was |
| // originally enabled, we'll install the compressed version of the application |
| // and re-enable it afterward. |
| synchronized (mPm.mLock) { |
| disableStubPackage(action, deletedPs, allUserHandles); |
| } |
| } |
| } |
| } |
| |
| @GuardedBy("mPm.mLock") |
| private void disableStubPackage(DeletePackageAction action, PackageSetting deletedPs, |
| @NonNull int[] allUserHandles) { |
| final PackageSetting stubPs = mPm.mSettings.getPackageLPr( |
| deletedPs.getPackageName()); |
| if (stubPs != null) { |
| int userId = action.mUser == null |
| ? UserHandle.USER_ALL : action.mUser.getIdentifier(); |
| if (userId == UserHandle.USER_ALL) { |
| for (int aUserId : allUserHandles) { |
| stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, aUserId, "android"); |
| } |
| } else if (userId >= UserHandle.USER_SYSTEM) { |
| stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, userId, "android"); |
| } |
| } |
| } |
| |
| /** |
| * Installs a package that's already on the system partition. |
| */ |
| @GuardedBy("mPm.mInstallLock") |
| private void installPackageFromSystemLIF(@NonNull String codePathString, |
| @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, |
| boolean writeSettings) |
| throws PackageManagerException { |
| final File codePath = new File(codePathString); |
| @ParsingPackageUtils.ParseFlags int parseFlags = |
| mPm.getDefParseFlags() |
| | ParsingPackageUtils.PARSE_MUST_BE_APK |
| | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR; |
| @PackageManagerService.ScanFlags int scanFlags = mPm.getSystemPackageScanFlags(codePath); |
| final AndroidPackage pkg = scanSystemPackageTracedLI( |
| codePath, parseFlags, scanFlags, null); |
| |
| PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName()); |
| |
| try { |
| // update shared libraries for the newly re-installed system package |
| mSharedLibraries.updateSharedLibrariesLPw(pkg, pkgSetting, null, null, |
| Collections.unmodifiableMap(mPm.mPackages)); |
| } catch (PackageManagerException e) { |
| Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage()); |
| } |
| |
| mAppDataHelper.prepareAppDataAfterInstallLIF(pkg); |
| |
| setPackageInstalledForSystemPackage(pkg, allUserHandles, origUserHandles, writeSettings); |
| } |
| |
| private void setPackageInstalledForSystemPackage(@NonNull AndroidPackage pkg, |
| @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, |
| boolean writeSettings) { |
| // writer |
| synchronized (mPm.mLock) { |
| PackageSetting ps = mPm.mSettings.getPackageLPr(pkg.getPackageName()); |
| |
| final boolean applyUserRestrictions = origUserHandles != null; |
| if (applyUserRestrictions) { |
| boolean installedStateChanged = false; |
| if (DEBUG_REMOVE) { |
| Slog.d(TAG, "Propagating install state across reinstall"); |
| } |
| for (int userId : allUserHandles) { |
| final boolean installed = ArrayUtils.contains(origUserHandles, userId); |
| if (DEBUG_REMOVE) { |
| Slog.d(TAG, " user " + userId + " => " + installed); |
| } |
| if (installed != ps.getInstalled(userId)) { |
| installedStateChanged = true; |
| } |
| ps.setInstalled(installed, userId); |
| if (installed) { |
| ps.setUninstallReason(UNINSTALL_REASON_UNKNOWN, userId); |
| } |
| } |
| // Regardless of writeSettings we need to ensure that this restriction |
| // state propagation is persisted |
| mPm.mSettings.writeAllUsersPackageRestrictionsLPr(); |
| if (installedStateChanged) { |
| mPm.mSettings.writeKernelMappingLPr(ps); |
| } |
| } |
| |
| // The method below will take care of removing obsolete permissions and granting |
| // install permissions. |
| mPm.mPermissionManager.onPackageInstalled(pkg, Process.INVALID_UID, |
| PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT, |
| UserHandle.USER_ALL); |
| for (final int userId : allUserHandles) { |
| if (applyUserRestrictions) { |
| mPm.mSettings.writePermissionStateForUserLPr(userId, false); |
| } |
| } |
| |
| // can downgrade to reader here |
| if (writeSettings) { |
| mPm.writeSettingsLPrTEMP(); |
| } |
| } |
| } |
| |
| @GuardedBy("mPm.mLock") |
| public void prepareSystemPackageCleanUp( |
| WatchedArrayMap<String, PackageSetting> packageSettings, |
| List<String> possiblyDeletedUpdatedSystemApps, |
| ArrayMap<String, File> expectingBetter, int[] userIds) { |
| // Iterates PackageSettings in reversed order because the item could be removed |
| // during the iteration. |
| for (int index = packageSettings.size() - 1; index >= 0; index--) { |
| final PackageSetting ps = packageSettings.valueAt(index); |
| final String packageName = ps.getPackageName(); |
| /* |
| * If this is not a system app, it can't be a |
| * disable system app. |
| */ |
| if (!ps.isSystem()) { |
| continue; |
| } |
| |
| /* |
| * If the package is scanned, it's not erased. |
| */ |
| final AndroidPackage scannedPkg = mPm.mPackages.get(packageName); |
| final PackageSetting disabledPs = |
| mPm.mSettings.getDisabledSystemPkgLPr(packageName); |
| if (scannedPkg != null) { |
| /* |
| * If the system app is both scanned and in the |
| * disabled packages list, then it must have been |
| * added via OTA. Remove it from the currently |
| * scanned package so the previously user-installed |
| * application can be scanned. |
| */ |
| if (disabledPs != null) { |
| logCriticalInfo(Log.WARN, |
| "Expecting better updated system app for " |
| + packageName |
| + "; removing system app. Last known" |
| + " codePath=" + ps.getPathString() |
| + ", versionCode=" + ps.getVersionCode() |
| + "; scanned versionCode=" |
| + scannedPkg.getLongVersionCode()); |
| mRemovePackageHelper.removePackageLI(scannedPkg, true); |
| expectingBetter.put(ps.getPackageName(), ps.getPath()); |
| } |
| |
| continue; |
| } |
| |
| if (disabledPs == null) { |
| logCriticalInfo(Log.WARN, "System package " + packageName |
| + " no longer exists; its data will be wiped"); |
| mRemovePackageHelper.removePackageDataLIF(ps, userIds, null, 0, false); |
| } else { |
| // we still have a disabled system package, but, it still might have |
| // been removed. check the code path still exists and check there's |
| // still a package. the latter can happen if an OTA keeps the same |
| // code path, but, changes the package name. |
| if (disabledPs.getPath() == null || !disabledPs.getPath().exists() |
| || disabledPs.getPkg() == null) { |
| possiblyDeletedUpdatedSystemApps.add(packageName); |
| } else { |
| // We're expecting that the system app should remain disabled, but add |
| // it to expecting better to recover in case the data version cannot |
| // be scanned. |
| expectingBetter.put(disabledPs.getPackageName(), disabledPs.getPath()); |
| } |
| } |
| } |
| } |
| |
| @GuardedBy("mPm.mLock") |
| // Remove disable package settings for updated system apps that were |
| // removed via an OTA. If the update is no longer present, remove the |
| // app completely. Otherwise, revoke their system privileges. |
| public void cleanupDisabledPackageSettings(List<String> possiblyDeletedUpdatedSystemApps, |
| int[] userIds, int scanFlags) { |
| for (int i = possiblyDeletedUpdatedSystemApps.size() - 1; i >= 0; --i) { |
| final String packageName = possiblyDeletedUpdatedSystemApps.get(i); |
| final AndroidPackage pkg = mPm.mPackages.get(packageName); |
| final String msg; |
| |
| // remove from the disabled system list; do this first so any future |
| // scans of this package are performed without this state |
| mPm.mSettings.removeDisabledSystemPackageLPw(packageName); |
| |
| if (pkg == null) { |
| // should have found an update, but, we didn't; remove everything |
| msg = "Updated system package " + packageName |
| + " no longer exists; removing its data"; |
| // Actual deletion of code and data will be handled by later |
| // reconciliation step |
| } else { |
| // found an update; revoke system privileges |
| msg = "Updated system package " + packageName |
| + " no longer exists; rescanning package on data"; |
| |
| // NOTE: We don't do anything special if a stub is removed from the |
| // system image. But, if we were [like removing the uncompressed |
| // version from the /data partition], this is where it'd be done. |
| |
| // remove the package from the system and re-scan it without any |
| // special privileges |
| mRemovePackageHelper.removePackageLI(pkg, true); |
| try { |
| final File codePath = new File(pkg.getPath()); |
| scanSystemPackageTracedLI(codePath, 0, scanFlags, null); |
| } catch (PackageManagerException e) { |
| Slog.e(TAG, "Failed to parse updated, ex-system package: " |
| + e.getMessage()); |
| } |
| } |
| |
| // one final check. if we still have a package setting [ie. it was |
| // previously scanned and known to the system], but, we don't have |
| // a package [ie. there was an error scanning it from the /data |
| // partition], completely remove the package data. |
| final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); |
| if (ps != null && mPm.mPackages.get(packageName) == null) { |
| mRemovePackageHelper.removePackageDataLIF(ps, userIds, null, 0, false); |
| } |
| logCriticalInfo(Log.WARN, msg); |
| } |
| } |
| |
| @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) |
| public void installPackagesFromDir(File scanDir, List<File> frameworkSplits, int parseFlags, |
| int scanFlags, PackageParser2 packageParser, |
| ExecutorService executorService) { |
| final File[] files = scanDir.listFiles(); |
| if (ArrayUtils.isEmpty(files)) { |
| Log.d(TAG, "No files in app dir " + scanDir); |
| return; |
| } |
| |
| if (DEBUG_PACKAGE_SCANNING) { |
| Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags |
| + " flags=0x" + Integer.toHexString(parseFlags)); |
| } |
| ParallelPackageParser parallelPackageParser = |
| new ParallelPackageParser(packageParser, executorService, frameworkSplits); |
| |
| // Submit files for parsing in parallel |
| int fileCount = 0; |
| for (File file : files) { |
| final boolean isPackage = (isApkFile(file) || file.isDirectory()) |
| && !PackageInstallerService.isStageName(file.getName()); |
| if (!isPackage) { |
| // Ignore entries which are not packages |
| continue; |
| } |
| if ((scanFlags & SCAN_DROP_CACHE) != 0) { |
| final PackageCacher cacher = new PackageCacher(mPm.getCacheDir()); |
| Log.w(TAG, "Dropping cache of " + file.getAbsolutePath()); |
| cacher.cleanCachedResult(file); |
| } |
| parallelPackageParser.submit(file, parseFlags); |
| fileCount++; |
| } |
| |
| // Process results one by one |
| for (; fileCount > 0; fileCount--) { |
| ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take(); |
| Throwable throwable = parseResult.throwable; |
| int errorCode = PackageManager.INSTALL_SUCCEEDED; |
| String errorMsg = null; |
| |
| if (throwable == null) { |
| // TODO(b/194319951): move lower in the scan chain |
| // Static shared libraries have synthetic package names |
| if (parseResult.parsedPackage.isStaticSharedLibrary()) { |
| PackageManagerService.renameStaticSharedLibraryPackage( |
| parseResult.parsedPackage); |
| } |
| try { |
| addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags, |
| null); |
| } catch (PackageManagerException e) { |
| errorCode = e.error; |
| errorMsg = "Failed to scan " + parseResult.scanFile + ": " + e.getMessage(); |
| Slog.w(TAG, errorMsg); |
| } |
| } else if (throwable instanceof PackageManagerException) { |
| PackageManagerException e = (PackageManagerException) throwable; |
| errorCode = e.error; |
| errorMsg = "Failed to parse " + parseResult.scanFile + ": " + e.getMessage(); |
| Slog.w(TAG, errorMsg); |
| } else { |
| throw new IllegalStateException("Unexpected exception occurred while parsing " |
| + parseResult.scanFile, throwable); |
| } |
| |
| if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0 && errorCode != INSTALL_SUCCEEDED) { |
| mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath(), errorMsg); |
| } |
| |
| // Delete invalid userdata apps |
| if ((scanFlags & SCAN_AS_SYSTEM) == 0 |
| && errorCode != PackageManager.INSTALL_SUCCEEDED) { |
| logCriticalInfo(Log.WARN, |
| "Deleting invalid package at " + parseResult.scanFile); |
| mRemovePackageHelper.removeCodePathLI(parseResult.scanFile); |
| } |
| } |
| } |
| |
| /** |
| * Make sure all system apps that we expected to appear on |
| * the userdata partition actually showed up. If they never |
| * appeared, crawl back and revive the system version. |
| */ |
| @GuardedBy("mPm.mLock") |
| public void checkExistingBetterPackages(ArrayMap<String, File> expectingBetterPackages, |
| List<String> stubSystemApps, int systemScanFlags, int systemParseFlags) { |
| for (int i = 0; i < expectingBetterPackages.size(); i++) { |
| final String packageName = expectingBetterPackages.keyAt(i); |
| if (mPm.mPackages.containsKey(packageName)) { |
| continue; |
| } |
| final File scanFile = expectingBetterPackages.valueAt(i); |
| |
| logCriticalInfo(Log.WARN, "Expected better " + packageName |
| + " but never showed up; reverting to system"); |
| |
| final Pair<Integer, Integer> rescanAndReparseFlags = |
| mPm.getSystemPackageRescanFlagsAndReparseFlags(scanFile, |
| systemScanFlags, systemParseFlags); |
| @PackageManagerService.ScanFlags int rescanFlags = rescanAndReparseFlags.first; |
| @ParsingPackageUtils.ParseFlags int reparseFlags = rescanAndReparseFlags.second; |
| |
| if (rescanFlags == 0) { |
| Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile); |
| continue; |
| } |
| mPm.mSettings.enableSystemPackageLPw(packageName); |
| |
| try { |
| final AndroidPackage newPkg = scanSystemPackageTracedLI( |
| scanFile, reparseFlags, rescanFlags, null); |
| // We rescanned a stub, add it to the list of stubbed system packages |
| if (newPkg.isStub()) { |
| stubSystemApps.add(packageName); |
| } |
| } catch (PackageManagerException e) { |
| Slog.e(TAG, "Failed to parse original system package: " |
| + e.getMessage()); |
| } |
| } |
| } |
| |
| /** |
| * Traces a package scan. |
| * @see #scanSystemPackageLI(File, int, int, UserHandle) |
| */ |
| @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) |
| public AndroidPackage scanSystemPackageTracedLI(File scanFile, final int parseFlags, |
| int scanFlags, UserHandle user) throws PackageManagerException { |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage [" + scanFile.toString() + "]"); |
| try { |
| return scanSystemPackageLI(scanFile, parseFlags, scanFlags, user); |
| } finally { |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| } |
| |
| /** |
| * Scans a package and returns the newly parsed package. |
| * Returns {@code null} in case of errors and the error code is stored in mLastScanError |
| */ |
| @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) |
| private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags, |
| UserHandle user) throws PackageManagerException { |
| if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile); |
| |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage"); |
| final ParsedPackage parsedPackage; |
| try (PackageParser2 pp = mPm.mInjector.getScanningPackageParser()) { |
| parsedPackage = pp.parsePackage(scanFile, parseFlags, false); |
| } finally { |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| |
| // Static shared libraries have synthetic package names |
| if (parsedPackage.isStaticSharedLibrary()) { |
| PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage); |
| } |
| |
| return addForInitLI(parsedPackage, parseFlags, scanFlags, user); |
| } |
| |
| /** |
| * Adds a new package to the internal data structures during platform initialization. |
| * <p>After adding, the package is known to the system and available for querying. |
| * <p>For packages located on the device ROM [eg. packages located in /system, /vendor, |
| * etc...], additional checks are performed. Basic verification [such as ensuring |
| * matching signatures, checking version codes, etc...] occurs if the package is |
| * identical to a previously known package. If the package fails a signature check, |
| * the version installed on /data will be removed. If the version of the new package |
| * is less than or equal than the version on /data, it will be ignored. |
| * <p>Regardless of the package location, the results are applied to the internal |
| * structures and the package is made available to the rest of the system. |
| * <p>NOTE: The return value should be removed. It's the passed in package object. |
| */ |
| @GuardedBy({"mPm.mLock", "mPm.mInstallLock"}) |
| private AndroidPackage addForInitLI(ParsedPackage parsedPackage, |
| @ParsingPackageUtils.ParseFlags int parseFlags, |
| @PackageManagerService.ScanFlags int scanFlags, |
| @Nullable UserHandle user) throws PackageManagerException { |
| |
| final Pair<ScanResult, Boolean> scanResultPair = scanSystemPackageLI( |
| parsedPackage, parseFlags, scanFlags, user); |
| final ScanResult scanResult = scanResultPair.first; |
| boolean shouldHideSystemApp = scanResultPair.second; |
| if (scanResult.mSuccess) { |
| synchronized (mPm.mLock) { |
| boolean appIdCreated = false; |
| try { |
| final String pkgName = scanResult.mPkgSetting.getPackageName(); |
| final ReconcileRequest reconcileRequest = new ReconcileRequest( |
| Collections.singletonMap(pkgName, scanResult), |
| mPm.mPackages, |
| Collections.singletonMap(pkgName, |
| mPm.getSettingsVersionForPackage(parsedPackage))); |
| final Map<String, ReconciledPackage> reconcileResult = |
| ReconcilePackageUtils.reconcilePackages(reconcileRequest, |
| mSharedLibraries, mPm.mSettings.getKeySetManagerService(), |
| mPm.mSettings); |
| appIdCreated = optimisticallyRegisterAppId(scanResult); |
| commitReconciledScanResultLocked(reconcileResult.get(pkgName), |
| mPm.mUserManager.getUserIds()); |
| } catch (PackageManagerException e) { |
| if (appIdCreated) { |
| cleanUpAppIdCreation(scanResult); |
| } |
| throw e; |
| } |
| } |
| } |
| |
| if (shouldHideSystemApp) { |
| synchronized (mPm.mLock) { |
| mPm.mSettings.disableSystemPackageLPw(parsedPackage.getPackageName(), true); |
| } |
| } |
| if (mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) { |
| if (scanResult.mPkgSetting != null && scanResult.mPkgSetting.isLoading()) { |
| // Continue monitoring loading progress of active incremental packages |
| mIncrementalManager.registerLoadingProgressCallback(parsedPackage.getPath(), |
| new IncrementalProgressListener(parsedPackage.getPackageName(), mPm)); |
| } |
| } |
| return scanResult.mPkgSetting.getPkg(); |
| } |
| |
| /** |
| * Prepares the system to commit a {@link ScanResult} in a way that will not fail by registering |
| * the app ID required for reconcile. |
| * @return {@code true} if a new app ID was registered and will need to be cleaned up on |
| * failure. |
| */ |
| private boolean optimisticallyRegisterAppId(@NonNull ScanResult result) |
| throws PackageManagerException { |
| if (!result.mExistingSettingCopied || result.needsNewAppId()) { |
| synchronized (mPm.mLock) { |
| // THROWS: when we can't allocate a user id. add call to check if there's |
| // enough space to ensure we won't throw; otherwise, don't modify state |
| return mPm.mSettings.registerAppIdLPw(result.mPkgSetting, result.needsNewAppId()); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Reverts any app ID creation that were made by |
| * {@link #optimisticallyRegisterAppId(ScanResult)}. Note: this is only necessary if the |
| * referenced method returned true. |
| */ |
| private void cleanUpAppIdCreation(@NonNull ScanResult result) { |
| // iff we've acquired an app ID for a new package setting, remove it so that it can be |
| // acquired by another request. |
| if (result.mPkgSetting.getAppId() > 0) { |
| mPm.mSettings.removeAppIdLPw(result.mPkgSetting.getAppId()); |
| } |
| } |
| |
| @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) |
| private ScanResult scanPackageTracedLI(ParsedPackage parsedPackage, |
| final @ParsingPackageUtils.ParseFlags int parseFlags, |
| @PackageManagerService.ScanFlags int scanFlags, long currentTime, |
| @Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException { |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage"); |
| try { |
| return scanPackageNewLI(parsedPackage, parseFlags, scanFlags, currentTime, user, |
| cpuAbiOverride); |
| } finally { |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| } |
| |
| private ScanRequest prepareInitialScanRequest(@NonNull ParsedPackage parsedPackage, |
| @ParsingPackageUtils.ParseFlags int parseFlags, |
| @PackageManagerService.ScanFlags int scanFlags, |
| @Nullable UserHandle user, String cpuAbiOverride) |
| throws PackageManagerException { |
| final AndroidPackage platformPackage; |
| final String realPkgName; |
| final PackageSetting disabledPkgSetting; |
| final PackageSetting installedPkgSetting; |
| final PackageSetting originalPkgSetting; |
| final SharedUserSetting sharedUserSetting; |
| SharedUserSetting oldSharedUserSetting = null; |
| |
| synchronized (mPm.mLock) { |
| platformPackage = mPm.getPlatformPackage(); |
| final String renamedPkgName = mPm.mSettings.getRenamedPackageLPr( |
| AndroidPackageUtils.getRealPackageOrNull(parsedPackage)); |
| realPkgName = ScanPackageUtils.getRealPackageName(parsedPackage, renamedPkgName); |
| if (realPkgName != null) { |
| ScanPackageUtils.ensurePackageRenamed(parsedPackage, renamedPkgName); |
| } |
| originalPkgSetting = getOriginalPackageLocked(parsedPackage, renamedPkgName); |
| installedPkgSetting = mPm.mSettings.getPackageLPr(parsedPackage.getPackageName()); |
| if (mPm.mTransferredPackages.contains(parsedPackage.getPackageName())) { |
| Slog.w(TAG, "Package " + parsedPackage.getPackageName() |
| + " was transferred to another, but its .apk remains"); |
| } |
| disabledPkgSetting = mPm.mSettings.getDisabledSystemPkgLPr( |
| parsedPackage.getPackageName()); |
| |
| boolean ignoreSharedUserId = false; |
| if (installedPkgSetting == null || !installedPkgSetting.hasSharedUser()) { |
| // Directly ignore sharedUserSetting for new installs, or if the app has |
| // already left shared UID |
| ignoreSharedUserId = parsedPackage.isLeavingSharedUid(); |
| } |
| |
| if (!ignoreSharedUserId && parsedPackage.getSharedUserId() != null) { |
| sharedUserSetting = mPm.mSettings.getSharedUserLPw( |
| parsedPackage.getSharedUserId(), |
| 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/); |
| } else { |
| sharedUserSetting = null; |
| } |
| if (DEBUG_PACKAGE_SCANNING |
| && (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 |
| && sharedUserSetting != null) { |
| Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId() |
| + " (uid=" + sharedUserSetting.mAppId + "):" |
| + " packages=" + sharedUserSetting.getPackageStates()); |
| } |
| if (installedPkgSetting != null) { |
| oldSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(installedPkgSetting); |
| } |
| } |
| |
| final boolean isPlatformPackage = platformPackage != null |
| && platformPackage.getPackageName().equals(parsedPackage.getPackageName()); |
| |
| return new ScanRequest(parsedPackage, oldSharedUserSetting, |
| installedPkgSetting == null ? null : installedPkgSetting.getPkg() /* oldPkg */, |
| installedPkgSetting /* packageSetting */, |
| sharedUserSetting, |
| disabledPkgSetting /* disabledPackageSetting */, |
| originalPkgSetting /* originalPkgSetting */, |
| realPkgName, parseFlags, scanFlags, isPlatformPackage, user, cpuAbiOverride); |
| } |
| |
| @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) |
| private ScanResult scanPackageNewLI(@NonNull ParsedPackage parsedPackage, |
| final @ParsingPackageUtils.ParseFlags int parseFlags, |
| @PackageManagerService.ScanFlags int scanFlags, long currentTime, |
| @Nullable UserHandle user, String cpuAbiOverride) |
| throws PackageManagerException { |
| final ScanRequest initialScanRequest = prepareInitialScanRequest(parsedPackage, parseFlags, |
| scanFlags, user, cpuAbiOverride); |
| final PackageSetting installedPkgSetting = initialScanRequest.mPkgSetting; |
| final PackageSetting disabledPkgSetting = initialScanRequest.mDisabledPkgSetting; |
| |
| boolean isUpdatedSystemApp; |
| if (installedPkgSetting != null) { |
| isUpdatedSystemApp = installedPkgSetting.getPkgState().isUpdatedSystemApp(); |
| } else { |
| isUpdatedSystemApp = disabledPkgSetting != null; |
| } |
| |
| final int newScanFlags = adjustScanFlags(scanFlags, installedPkgSetting, disabledPkgSetting, |
| user, parsedPackage); |
| ScanPackageUtils.applyPolicy(parsedPackage, newScanFlags, |
| mPm.getPlatformPackage(), isUpdatedSystemApp); |
| |
| synchronized (mPm.mLock) { |
| assertPackageIsValid(parsedPackage, parseFlags, newScanFlags); |
| final ScanRequest request = new ScanRequest(parsedPackage, |
| initialScanRequest.mOldSharedUserSetting, |
| initialScanRequest.mOldPkg, installedPkgSetting, |
| initialScanRequest.mSharedUserSetting, disabledPkgSetting, |
| initialScanRequest.mOriginalPkgSetting, initialScanRequest.mRealPkgName, |
| parseFlags, scanFlags, initialScanRequest.mIsPlatformPackage, user, |
| cpuAbiOverride); |
| return ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector, mPm.mFactoryTest, |
| currentTime); |
| } |
| } |
| |
| private Pair<ScanResult, Boolean> scanSystemPackageLI(ParsedPackage parsedPackage, |
| @ParsingPackageUtils.ParseFlags int parseFlags, |
| @PackageManagerService.ScanFlags int scanFlags, |
| @Nullable UserHandle user) throws PackageManagerException { |
| final boolean scanSystemPartition = |
| (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0; |
| final ScanRequest initialScanRequest = prepareInitialScanRequest(parsedPackage, parseFlags, |
| scanFlags, user, null); |
| final PackageSetting installedPkgSetting = initialScanRequest.mPkgSetting; |
| final PackageSetting originalPkgSetting = initialScanRequest.mOriginalPkgSetting; |
| final PackageSetting pkgSetting = |
| originalPkgSetting == null ? installedPkgSetting : originalPkgSetting; |
| final boolean pkgAlreadyExists = pkgSetting != null; |
| final String disabledPkgName = pkgAlreadyExists |
| ? pkgSetting.getPackageName() : parsedPackage.getPackageName(); |
| final boolean isSystemPkgUpdated; |
| final boolean isUpgrade; |
| synchronized (mPm.mLock) { |
| isUpgrade = mPm.isDeviceUpgrading(); |
| if (scanSystemPartition && !pkgAlreadyExists |
| && mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName) != null) { |
| // The updated-package data for /system apk remains inconsistently |
| // after the package data for /data apk is lost accidentally. |
| // To recover it, enable /system apk and install it as non-updated system app. |
| Slog.w(TAG, "Inconsistent package setting of updated system app for " |
| + disabledPkgName + ". To recover it, enable the system app " |
| + "and install it as non-updated system app."); |
| mPm.mSettings.removeDisabledSystemPackageLPw(disabledPkgName); |
| } |
| final PackageSetting disabledPkgSetting = |
| mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName); |
| isSystemPkgUpdated = disabledPkgSetting != null; |
| |
| if (DEBUG_INSTALL && isSystemPkgUpdated) { |
| Slog.d(TAG, "updatedPkg = " + disabledPkgSetting); |
| } |
| |
| if (scanSystemPartition && isSystemPkgUpdated) { |
| // we're updating the disabled package, so, scan it as the package setting |
| final ScanRequest request = new ScanRequest(parsedPackage, |
| mPm.mSettings.getSharedUserSettingLPr(disabledPkgSetting), |
| null, disabledPkgSetting /* pkgSetting */, |
| initialScanRequest.mSharedUserSetting, |
| null /* disabledPkgSetting */, null /* originalPkgSetting */, |
| null, parseFlags, scanFlags, |
| initialScanRequest.mIsPlatformPackage, user, null); |
| ScanPackageUtils.applyPolicy(parsedPackage, scanFlags, |
| mPm.getPlatformPackage(), true); |
| final ScanResult scanResult = |
| ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector, |
| mPm.mFactoryTest, -1L); |
| if (scanResult.mExistingSettingCopied |
| && scanResult.mRequest.mPkgSetting != null) { |
| scanResult.mRequest.mPkgSetting.updateFrom(scanResult.mPkgSetting); |
| } |
| } |
| } // End of mLock |
| |
| final boolean newPkgChangedPaths = pkgAlreadyExists |
| && !pkgSetting.getPathString().equals(parsedPackage.getPath()); |
| final boolean newPkgVersionGreater = pkgAlreadyExists |
| && parsedPackage.getLongVersionCode() > pkgSetting.getVersionCode(); |
| final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated |
| && newPkgChangedPaths && newPkgVersionGreater; |
| if (isSystemPkgBetter) { |
| // The version of the application on /system is greater than the version on |
| // /data. Switch back to the application on /system. |
| // It's safe to assume the application on /system will correctly scan. If not, |
| // there won't be a working copy of the application. |
| synchronized (mPm.mLock) { |
| // just remove the loaded entries from package lists |
| mPm.mPackages.remove(pkgSetting.getPackageName()); |
| } |
| |
| logCriticalInfo(Log.WARN, |
| "System package updated;" |
| + " name: " + pkgSetting.getPackageName() |
| + "; " + pkgSetting.getVersionCode() + " --> " |
| + parsedPackage.getLongVersionCode() |
| + "; " + pkgSetting.getPathString() |
| + " --> " + parsedPackage.getPath()); |
| |
| final InstallArgs args = new FileInstallArgs( |
| pkgSetting.getPathString(), getAppDexInstructionSets( |
| pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()), mPm); |
| args.cleanUpResourcesLI(); |
| synchronized (mPm.mLock) { |
| mPm.mSettings.enableSystemPackageLPw(pkgSetting.getPackageName()); |
| } |
| } |
| |
| // The version of the application on the /system partition is less than or |
| // equal to the version on the /data partition. Throw an exception and use |
| // the application already installed on the /data partition. |
| if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) { |
| // In the case of a skipped package, commitReconciledScanResultLocked is not called to |
| // add the object to the "live" data structures, so this is the final mutation step |
| // for the package. Which means it needs to be finalized here to cache derived fields. |
| // This is relevant for cases where the disabled system package is used for flags or |
| // other metadata. |
| parsedPackage.hideAsFinal(); |
| throw new PackageManagerException(Log.WARN, "Package " + parsedPackage.getPackageName() |
| + " at " + parsedPackage.getPath() + " ignored: updated version " |
| + (pkgAlreadyExists ? String.valueOf(pkgSetting.getVersionCode()) : "unknown") |
| + " better than this " + parsedPackage.getLongVersionCode()); |
| } |
| |
| // Verify certificates against what was last scanned. Force re-collecting certificate in two |
| // special cases: |
| // 1) when scanning system, force re-collect only if system is upgrading. |
| // 2) when scanning /data, force re-collect only if the app is privileged (updated from |
| // preinstall, or treated as privileged, e.g. due to shared user ID). |
| final boolean forceCollect = scanSystemPartition ? isUpgrade |
| : PackageManagerServiceUtils.isApkVerificationForced(pkgSetting); |
| if (DEBUG_VERIFY && forceCollect) { |
| Slog.d(TAG, "Force collect certificate of " + parsedPackage.getPackageName()); |
| } |
| |
| // Full APK verification can be skipped during certificate collection, only if the file is |
| // in verified partition, or can be verified on access (when apk verity is enabled). In both |
| // cases, only data in Signing Block is verified instead of the whole file. |
| final boolean skipVerify = scanSystemPartition |
| || (forceCollect && canSkipForcedPackageVerification(parsedPackage)); |
| ScanPackageUtils.collectCertificatesLI(pkgSetting, parsedPackage, |
| mPm.getSettingsVersionForPackage(parsedPackage), forceCollect, skipVerify, |
| mPm.isPreNMR1Upgrade()); |
| |
| // Reset profile if the application version is changed |
| maybeClearProfilesForUpgradesLI(pkgSetting, parsedPackage); |
| |
| /* |
| * A new system app appeared, but we already had a non-system one of the |
| * same name installed earlier. |
| */ |
| boolean shouldHideSystemApp = false; |
| // A new application appeared on /system, but, we already have a copy of |
| // the application installed on /data. |
| if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists |
| && !pkgSetting.isSystem()) { |
| |
| if (!parsedPackage.getSigningDetails() |
| .checkCapability(pkgSetting.getSigningDetails(), |
| SigningDetails.CertCapabilities.INSTALLED_DATA) |
| && !pkgSetting.getSigningDetails().checkCapability( |
| parsedPackage.getSigningDetails(), |
| SigningDetails.CertCapabilities.ROLLBACK)) { |
| logCriticalInfo(Log.WARN, |
| "System package signature mismatch;" |
| + " name: " + pkgSetting.getPackageName()); |
| try (@SuppressWarnings("unused") PackageFreezer freezer = mPm.freezePackage( |
| parsedPackage.getPackageName(), |
| "scanPackageInternalLI")) { |
| DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm); |
| deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true, |
| mPm.mUserManager.getUserIds(), 0, null, false); |
| } |
| } else if (newPkgVersionGreater) { |
| // The application on /system is newer than the application on /data. |
| // Simply remove the application on /data [keeping application data] |
| // and replace it with the version on /system. |
| logCriticalInfo(Log.WARN, |
| "System package enabled;" |
| + " name: " + pkgSetting.getPackageName() |
| + "; " + pkgSetting.getVersionCode() + " --> " |
| + parsedPackage.getLongVersionCode() |
| + "; " + pkgSetting.getPathString() + " --> " |
| + parsedPackage.getPath()); |
| InstallArgs args = new FileInstallArgs( |
| pkgSetting.getPathString(), getAppDexInstructionSets( |
| pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()), |
| mPm); |
| synchronized (mPm.mInstallLock) { |
| args.cleanUpResourcesLI(); |
| } |
| } else { |
| // The application on /system is older than the application on /data. Hide |
| // the application on /system and the version on /data will be scanned later |
| // and re-added like an update. |
| shouldHideSystemApp = true; |
| logCriticalInfo(Log.INFO, |
| "System package disabled;" |
| + " name: " + pkgSetting.getPackageName() |
| + "; old: " + pkgSetting.getPathString() + " @ " |
| + pkgSetting.getVersionCode() |
| + "; new: " + parsedPackage.getPath() + " @ " |
| + parsedPackage.getPath()); |
| } |
| } |
| |
| final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags, |
| scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null); |
| return new Pair<>(scanResult, shouldHideSystemApp); |
| } |
| |
| /** |
| * Returns if forced apk verification can be skipped for the whole package, including splits. |
| */ |
| private boolean canSkipForcedPackageVerification(AndroidPackage pkg) { |
| final String packageName = pkg.getPackageName(); |
| if (!VerityUtils.hasFsverity(pkg.getBaseApkPath())) { |
| return false; |
| } |
| // TODO: Allow base and splits to be verified individually. |
| String[] splitCodePaths = pkg.getSplitCodePaths(); |
| if (!ArrayUtils.isEmpty(splitCodePaths)) { |
| for (int i = 0; i < splitCodePaths.length; i++) { |
| if (!VerityUtils.hasFsverity(splitCodePaths[i])) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Clear the package profile if this was an upgrade and the package |
| * version was updated. |
| */ |
| private void maybeClearProfilesForUpgradesLI( |
| @Nullable PackageSetting originalPkgSetting, |
| @NonNull AndroidPackage pkg) { |
| if (originalPkgSetting == null || !mPm.isDeviceUpgrading()) { |
| return; |
| } |
| if (originalPkgSetting.getVersionCode() == pkg.getLongVersionCode()) { |
| return; |
| } |
| |
| mAppDataHelper.clearAppProfilesLIF(pkg); |
| if (DEBUG_INSTALL) { |
| Slog.d(TAG, originalPkgSetting.getPackageName() |
| + " clear profile due to version change " |
| + originalPkgSetting.getVersionCode() + " != " |
| + pkg.getLongVersionCode()); |
| } |
| } |
| |
| /** |
| * Returns the original package setting. |
| * <p>A package can migrate its name during an update. In this scenario, a package |
| * designates a set of names that it considers as one of its original names. |
| * <p>An original package must be signed identically and it must have the same |
| * shared user [if any]. |
| */ |
| @GuardedBy("mPm.mLock") |
| @Nullable |
| private PackageSetting getOriginalPackageLocked(@NonNull AndroidPackage pkg, |
| @Nullable String renamedPkgName) { |
| if (ScanPackageUtils.isPackageRenamed(pkg, renamedPkgName)) { |
| return null; |
| } |
| for (int i = ArrayUtils.size(pkg.getOriginalPackages()) - 1; i >= 0; --i) { |
| final PackageSetting originalPs = |
| mPm.mSettings.getPackageLPr(pkg.getOriginalPackages().get(i)); |
| if (originalPs != null) { |
| // the package is already installed under its original name... |
| // but, should we use it? |
| if (!verifyPackageUpdateLPr(originalPs, pkg)) { |
| // the new package is incompatible with the original |
| continue; |
| } else if (mPm.mSettings.getSharedUserSettingLPr(originalPs) != null) { |
| final String sharedUserSettingsName = |
| mPm.mSettings.getSharedUserSettingLPr(originalPs).name; |
| if (!sharedUserSettingsName.equals(pkg.getSharedUserId())) { |
| // the shared user id is incompatible with the original |
| Slog.w(TAG, "Unable to migrate data from " + originalPs.getPackageName() |
| + " to " + pkg.getPackageName() + ": old shared user settings name " |
| + sharedUserSettingsName |
| + " differs from " + pkg.getSharedUserId()); |
| continue; |
| } |
| // TODO: Add case when shared user id is added [b/28144775] |
| } else { |
| if (DEBUG_UPGRADE) { |
| Log.v(TAG, "Renaming new package " |
| + pkg.getPackageName() + " to old name " |
| + originalPs.getPackageName()); |
| } |
| } |
| return originalPs; |
| } |
| } |
| return null; |
| } |
| |
| @GuardedBy("mPm.mLock") |
| private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, AndroidPackage newPkg) { |
| if ((oldPkg.getFlags() & ApplicationInfo.FLAG_SYSTEM) == 0) { |
| Slog.w(TAG, "Unable to update from " + oldPkg.getPackageName() |
| + " to " + newPkg.getPackageName() |
| + ": old package not in system partition"); |
| return false; |
| } else if (mPm.mPackages.get(oldPkg.getPackageName()) != null) { |
| Slog.w(TAG, "Unable to update from " + oldPkg.getPackageName() |
| + " to " + newPkg.getPackageName() |
| + ": old package still exists"); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Asserts the parsed package is valid according to the given policy. If the |
| * package is invalid, for whatever reason, throws {@link PackageManagerException}. |
| * <p> |
| * Implementation detail: This method must NOT have any side effects. It would |
| * ideally be static, but, it requires locks to read system state. |
| * |
| * @throws PackageManagerException If the package fails any of the validation checks |
| */ |
| private void assertPackageIsValid(AndroidPackage pkg, |
| final @ParsingPackageUtils.ParseFlags int parseFlags, |
| final @PackageManagerService.ScanFlags int scanFlags) |
| throws PackageManagerException { |
| if ((parseFlags & ParsingPackageUtils.PARSE_ENFORCE_CODE) != 0) { |
| ScanPackageUtils.assertCodePolicy(pkg); |
| } |
| |
| if (pkg.getPath() == null) { |
| // Bail out. The resource and code paths haven't been set. |
| throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, |
| "Code and resource paths haven't been set correctly"); |
| } |
| |
| // Check that there is an APEX package with the same name only during install/first boot |
| // after OTA. |
| final boolean isUserInstall = (scanFlags & SCAN_BOOTING) == 0; |
| final boolean isFirstBootOrUpgrade = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0; |
| if ((isUserInstall || isFirstBootOrUpgrade) |
| && mApexManager.isApexPackage(pkg.getPackageName())) { |
| throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE, |
| pkg.getPackageName() |
| + " is an APEX package and can't be installed as an APK."); |
| } |
| |
| // Make sure we're not adding any bogus keyset info |
| final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService(); |
| ksms.assertScannedPackageValid(pkg); |
| |
| synchronized (mPm.mLock) { |
| // The special "android" package can only be defined once |
| if (pkg.getPackageName().equals("android")) { |
| if (mPm.getCoreAndroidApplication() != null) { |
| Slog.w(TAG, "*************************************************"); |
| Slog.w(TAG, "Core android package being redefined. Skipping."); |
| Slog.w(TAG, " codePath=" + pkg.getPath()); |
| Slog.w(TAG, "*************************************************"); |
| throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE, |
| "Core android package being redefined. Skipping."); |
| } |
| } |
| |
| // A package name must be unique; don't allow duplicates |
| if ((scanFlags & SCAN_NEW_INSTALL) == 0 |
| && mPm.mPackages.containsKey(pkg.getPackageName())) { |
| throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE, |
| "Application package " + pkg.getPackageName() |
| + " already installed. Skipping duplicate."); |
| } |
| |
| if (pkg.isStaticSharedLibrary()) { |
| // Static libs have a synthetic package name containing the version |
| // but we still want the base name to be unique. |
| if ((scanFlags & SCAN_NEW_INSTALL) == 0 |
| && mPm.mPackages.containsKey(pkg.getManifestPackageName())) { |
| throw new PackageManagerException( |
| "Duplicate static shared lib provider package"); |
| } |
| ScanPackageUtils.assertStaticSharedLibraryIsValid(pkg, scanFlags); |
| assertStaticSharedLibraryVersionCodeIsValid(pkg); |
| } |
| |
| // If we're only installing presumed-existing packages, require that the |
| // scanned APK is both already known and at the path previously established |
| // for it. Previously unknown packages we pick up normally, but if we have an |
| // a priori expectation about this package's install presence, enforce it. |
| // With a singular exception for new system packages. When an OTA contains |
| // a new system package, we allow the codepath to change from a system location |
| // to the user-installed location. If we don't allow this change, any newer, |
| // user-installed version of the application will be ignored. |
| if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) { |
| if (mPm.isExpectingBetter(pkg.getPackageName())) { |
| Slog.w(TAG, "Relax SCAN_REQUIRE_KNOWN requirement for package " |
| + pkg.getPackageName()); |
| } else { |
| PackageSetting known = mPm.mSettings.getPackageLPr(pkg.getPackageName()); |
| if (known != null) { |
| if (DEBUG_PACKAGE_SCANNING) { |
| Log.d(TAG, "Examining " + pkg.getPath() |
| + " and requiring known path " + known.getPathString()); |
| } |
| if (!pkg.getPath().equals(known.getPathString())) { |
| throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED, |
| "Application package " + pkg.getPackageName() |
| + " found at " + pkg.getPath() |
| + " but expected at " + known.getPathString() |
| + "; ignoring."); |
| } |
| } else { |
| throw new PackageManagerException(INSTALL_FAILED_INVALID_INSTALL_LOCATION, |
| "Application package " + pkg.getPackageName() |
| + " not found; ignoring."); |
| } |
| } |
| } |
| |
| // Verify that this new package doesn't have any content providers |
| // that conflict with existing packages. Only do this if the |
| // package isn't already installed, since we don't want to break |
| // things that are installed. |
| if ((scanFlags & SCAN_NEW_INSTALL) != 0) { |
| mPm.mComponentResolver.assertProvidersNotDefined(pkg); |
| } |
| |
| // If this package has defined explicit processes, then ensure that these are |
| // the only processes used by its components. |
| ScanPackageUtils.assertProcessesAreValid(pkg); |
| |
| // Verify that packages sharing a user with a privileged app are marked as privileged. |
| assertPackageWithSharedUserIdIsPrivileged(pkg); |
| |
| // Apply policies specific for runtime resource overlays (RROs). |
| if (pkg.getOverlayTarget() != null) { |
| assertOverlayIsValid(pkg, parseFlags, scanFlags); |
| } |
| |
| // Ensure the package is signed with at least the minimum signature scheme version |
| // required for its target SDK. |
| ScanPackageUtils.assertMinSignatureSchemeIsValid(pkg, parseFlags); |
| } |
| } |
| |
| private void assertStaticSharedLibraryVersionCodeIsValid(AndroidPackage pkg) |
| throws PackageManagerException { |
| // The version codes must be ordered as lib versions |
| long minVersionCode = Long.MIN_VALUE; |
| long maxVersionCode = Long.MAX_VALUE; |
| |
| WatchedLongSparseArray<SharedLibraryInfo> versionedLib = |
| mSharedLibraries.getSharedLibraryInfos(pkg.getStaticSharedLibName()); |
| if (versionedLib != null) { |
| final int versionCount = versionedLib.size(); |
| for (int i = 0; i < versionCount; i++) { |
| SharedLibraryInfo libInfo = versionedLib.valueAt(i); |
| final long libVersionCode = libInfo.getDeclaringPackage() |
| .getLongVersionCode(); |
| if (libInfo.getLongVersion() < pkg.getStaticSharedLibVersion()) { |
| minVersionCode = Math.max(minVersionCode, libVersionCode + 1); |
| } else if (libInfo.getLongVersion() |
| > pkg.getStaticSharedLibVersion()) { |
| maxVersionCode = Math.min(maxVersionCode, libVersionCode - 1); |
| } else { |
| minVersionCode = maxVersionCode = libVersionCode; |
| break; |
| } |
| } |
| } |
| if (pkg.getLongVersionCode() < minVersionCode |
| || pkg.getLongVersionCode() > maxVersionCode) { |
| throw new PackageManagerException("Static shared" |
| + " lib version codes must be ordered as lib versions"); |
| } |
| } |
| |
| private void assertOverlayIsValid(AndroidPackage pkg, |
| @ParsingPackageUtils.ParseFlags int parseFlags, |
| @PackageManagerService.ScanFlags int scanFlags) throws PackageManagerException { |
| // System overlays have some restrictions on their use of the 'static' state. |
| if ((scanFlags & SCAN_AS_SYSTEM) != 0) { |
| // We are scanning a system overlay. This can be the first scan of the |
| // system/vendor/oem partition, or an update to the system overlay. |
| if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { |
| // This must be an update to a system overlay. Immutable overlays cannot be |
| // upgraded. |
| if (!mPm.isOverlayMutable(pkg.getPackageName())) { |
| throw new PackageManagerException("Overlay " |
| + pkg.getPackageName() |
| + " is static and cannot be upgraded."); |
| } |
| } else { |
| if ((scanFlags & SCAN_AS_VENDOR) != 0) { |
| if (pkg.getTargetSdkVersion() < ScanPackageUtils.getVendorPartitionVersion()) { |
| Slog.w(TAG, "System overlay " + pkg.getPackageName() |
| + " targets an SDK below the required SDK level of vendor" |
| + " overlays (" |
| + ScanPackageUtils.getVendorPartitionVersion() |
| + ")." |
| + " This will become an install error in a future release"); |
| } |
| } else if (pkg.getTargetSdkVersion() < Build.VERSION.SDK_INT) { |
| Slog.w(TAG, "System overlay " + pkg.getPackageName() |
| + " targets an SDK below the required SDK level of system" |
| + " overlays (" + Build.VERSION.SDK_INT + ")." |
| + " This will become an install error in a future release"); |
| } |
| } |
| } else { |
| // A non-preloaded overlay packages must have targetSdkVersion >= Q, or be |
| // signed with the platform certificate. Check this in increasing order of |
| // computational cost. |
| if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.Q) { |
| final PackageSetting platformPkgSetting = |
| mPm.mSettings.getPackageLPr("android"); |
| if (!comparePackageSignatures(platformPkgSetting, |
| pkg.getSigningDetails().getSignatures())) { |
| throw new PackageManagerException("Overlay " |
| + pkg.getPackageName() |
| + " must target Q or later, " |
| + "or be signed with the platform certificate"); |
| } |
| } |
| |
| // A non-preloaded overlay package, without <overlay android:targetName>, will |
| // only be used if it is signed with the same certificate as its target OR if |
| // it is signed with the same certificate as a reference package declared |
| // in 'overlay-config-signature' tag of SystemConfig. |
| // If the target is already installed or 'overlay-config-signature' tag in |
| // SystemConfig is set, check this here to augment the last line of defense |
| // which is OMS. |
| if (pkg.getOverlayTargetOverlayableName() == null) { |
| final PackageSetting targetPkgSetting = |
| mPm.mSettings.getPackageLPr(pkg.getOverlayTarget()); |
| if (targetPkgSetting != null) { |
| if (!comparePackageSignatures(targetPkgSetting, |
| pkg.getSigningDetails().getSignatures())) { |
| // check reference signature |
| if (mPm.mOverlayConfigSignaturePackage == null) { |
| throw new PackageManagerException("Overlay " |
| + pkg.getPackageName() + " and target " |
| + pkg.getOverlayTarget() + " signed with" |
| + " different certificates, and the overlay lacks" |
| + " <overlay android:targetName>"); |
| } |
| final PackageSetting refPkgSetting = |
| mPm.mSettings.getPackageLPr( |
| mPm.mOverlayConfigSignaturePackage); |
| if (!comparePackageSignatures(refPkgSetting, |
| pkg.getSigningDetails().getSignatures())) { |
| throw new PackageManagerException("Overlay " |
| + pkg.getPackageName() + " signed with a different " |
| + "certificate than both the reference package and " |
| + "target " + pkg.getOverlayTarget() + ", and the " |
| + "overlay lacks <overlay android:targetName>"); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void assertPackageWithSharedUserIdIsPrivileged(AndroidPackage pkg) |
| throws PackageManagerException { |
| if (!pkg.isPrivileged() && (pkg.getSharedUserId() != null)) { |
| SharedUserSetting sharedUserSetting = null; |
| try { |
| sharedUserSetting = mPm.mSettings.getSharedUserLPw(pkg.getSharedUserId(), |
| 0, 0, false); |
| } catch (PackageManagerException ignore) { |
| } |
| if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) { |
| // Exempt SharedUsers signed with the platform key. |
| PackageSetting platformPkgSetting = mPm.mSettings.getPackageLPr("android"); |
| if (!comparePackageSignatures(platformPkgSetting, |
| pkg.getSigningDetails().getSignatures())) { |
| throw new PackageManagerException("Apps that share a user with a " |
| + "privileged app must themselves be marked as privileged. " |
| + pkg.getPackageName() + " shares privileged user " |
| + pkg.getSharedUserId() + "."); |
| } |
| } |
| } |
| } |
| |
| private @PackageManagerService.ScanFlags int adjustScanFlags( |
| @PackageManagerService.ScanFlags int scanFlags, |
| PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user, |
| AndroidPackage pkg) { |
| scanFlags = ScanPackageUtils.adjustScanFlagsWithPackageSetting(scanFlags, pkgSetting, |
| disabledPkgSetting, user); |
| |
| // Exception for privileged apps that share a user with a priv-app. |
| final boolean skipVendorPrivilegeScan = ((scanFlags & SCAN_AS_VENDOR) != 0) |
| && ScanPackageUtils.getVendorPartitionVersion() < 28; |
| if (((scanFlags & SCAN_AS_PRIVILEGED) == 0) |
| && !pkg.isPrivileged() |
| && (pkg.getSharedUserId() != null) |
| && !skipVendorPrivilegeScan) { |
| SharedUserSetting sharedUserSetting = null; |
| synchronized (mPm.mLock) { |
| try { |
| sharedUserSetting = mPm.mSettings.getSharedUserLPw(pkg.getSharedUserId(), 0, |
| 0, false); |
| } catch (PackageManagerException ignore) { |
| } |
| if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) { |
| // Exempt SharedUsers signed with the platform key. |
| // TODO(b/72378145) Fix this exemption. Force signature apps |
| // to allowlist their privileged permissions just like other |
| // priv-apps. |
| PackageSetting platformPkgSetting = mPm.mSettings.getPackageLPr("android"); |
| if ((compareSignatures( |
| platformPkgSetting.getSigningDetails().getSignatures(), |
| pkg.getSigningDetails().getSignatures()) |
| != PackageManager.SIGNATURE_MATCH)) { |
| scanFlags |= SCAN_AS_PRIVILEGED; |
| } |
| } |
| } |
| } |
| |
| return scanFlags; |
| } |
| } |