/*
 * 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.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_FAILED_PROCESS_NOT_DEFINED;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;

import static com.android.server.pm.PackageManagerService.DEBUG_ABI_SELECTION;
import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
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_FIRST_BOOT_OR_UPGRADE;
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_UPDATE_TIME;
import static com.android.server.pm.PackageManagerService.TAG;
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.getLastModifiedTime;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.Build;
import android.os.Environment;
import android.os.Process;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.apk.ApkSignatureVerifier;
import android.util.jar.StrictJarFile;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.SystemConfig;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.library.PackageBackwardCompatibility;
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.pkg.PackageStateUtils;
import com.android.server.pm.pkg.component.ComponentMutateUtils;
import com.android.server.pm.pkg.component.ParsedActivity;
import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.component.ParsedProcess;
import com.android.server.pm.pkg.component.ParsedProvider;
import com.android.server.pm.pkg.component.ParsedService;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.utils.WatchedArraySet;

import dalvik.system.VMRuntime;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * Helper class that handles package scanning logic
 */
final class ScanPackageUtils {
    /**
     * Just scans the package without any side effects.
     *
     * @param injector injector for acquiring dependencies
     * @param request Information about the package to be scanned
     * @param isUnderFactoryTest Whether or not the device is under factory test
     * @param currentTime The current time, in millis
     * @return The results of the scan
     */
    @GuardedBy("mPm.mInstallLock")
    @VisibleForTesting
    @NonNull
    public static ScanResult scanPackageOnlyLI(@NonNull ScanRequest request,
            PackageManagerServiceInjector injector,
            boolean isUnderFactoryTest, long currentTime)
            throws PackageManagerException {
        final PackageAbiHelper packageAbiHelper = injector.getAbiHelper();
        ParsedPackage parsedPackage = request.mParsedPackage;
        PackageSetting pkgSetting = request.mPkgSetting;
        final PackageSetting disabledPkgSetting = request.mDisabledPkgSetting;
        final PackageSetting originalPkgSetting = request.mOriginalPkgSetting;
        final @ParsingPackageUtils.ParseFlags int parseFlags = request.mParseFlags;
        final @PackageManagerService.ScanFlags int scanFlags = request.mScanFlags;
        final String realPkgName = request.mRealPkgName;
        final SharedUserSetting oldSharedUserSetting = request.mOldSharedUserSetting;
        final SharedUserSetting sharedUserSetting = request.mSharedUserSetting;
        final UserHandle user = request.mUser;
        final boolean isPlatformPackage = request.mIsPlatformPackage;

        List<String> changedAbiCodePath = null;

        if (DEBUG_PACKAGE_SCANNING) {
            if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) {
                Log.d(TAG, "Scanning package " + parsedPackage.getPackageName());
            }
        }

        // Initialize package source and resource directories
        final File destCodeFile = new File(parsedPackage.getPath());

        // We keep references to the derived CPU Abis from settings in oder to reuse
        // them in the case where we're not upgrading or booting for the first time.
        String primaryCpuAbiFromSettings = null;
        String secondaryCpuAbiFromSettings = null;
        boolean needToDeriveAbi = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
        if (!needToDeriveAbi) {
            if (pkgSetting != null) {
                // TODO(b/154610922): if it is not first boot or upgrade, we should directly use
                // API info from existing package setting. However, stub packages currently do not
                // preserve ABI info, thus the special condition check here. Remove the special
                // check after we fix the stub generation.
                if (pkgSetting.getPkg() != null && pkgSetting.getPkg().isStub()) {
                    needToDeriveAbi = true;
                } else {
                    primaryCpuAbiFromSettings = pkgSetting.getPrimaryCpuAbi();
                    secondaryCpuAbiFromSettings = pkgSetting.getSecondaryCpuAbi();
                }
            } else {
                // Re-scanning a system package after uninstalling updates; need to derive ABI
                needToDeriveAbi = true;
            }
        }

        if (pkgSetting != null && oldSharedUserSetting != sharedUserSetting) {
            PackageManagerService.reportSettingsProblem(Log.WARN,
                    "Package " + parsedPackage.getPackageName() + " shared user changed from "
                            + (oldSharedUserSetting != null
                            ? oldSharedUserSetting.name : "<nothing>")
                            + " to "
                            + (sharedUserSetting != null ? sharedUserSetting.name : "<nothing>")
                            + "; replacing with new");
            pkgSetting = null;
        }

        String[] usesSdkLibraries = null;
        if (!parsedPackage.getUsesSdkLibraries().isEmpty()) {
            usesSdkLibraries = new String[parsedPackage.getUsesSdkLibraries().size()];
            parsedPackage.getUsesSdkLibraries().toArray(usesSdkLibraries);
        }

        String[] usesStaticLibraries = null;
        if (!parsedPackage.getUsesStaticLibraries().isEmpty()) {
            usesStaticLibraries = new String[parsedPackage.getUsesStaticLibraries().size()];
            parsedPackage.getUsesStaticLibraries().toArray(usesStaticLibraries);
        }

        final UUID newDomainSetId = injector.getDomainVerificationManagerInternal().generateNewId();

        // TODO(b/135203078): Remove appInfoFlag usage in favor of individually assigned booleans
        //  to avoid adding something that's unsupported due to lack of state, since it's called
        //  with null.
        final boolean createNewPackage = (pkgSetting == null);
        if (createNewPackage) {
            final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
            final boolean virtualPreload = (scanFlags & SCAN_AS_VIRTUAL_PRELOAD) != 0;

            // Flags contain system values stored in the server variant of AndroidPackage,
            // and so the server-side PackageInfoUtils is still called, even without a
            // PackageSetting to pass in.
            int pkgFlags = PackageInfoUtils.appInfoFlags(parsedPackage, null);
            int pkgPrivateFlags = PackageInfoUtils.appInfoPrivateFlags(parsedPackage, null);

            // REMOVE SharedUserSetting from method; update in a separate call
            pkgSetting = Settings.createNewSetting(parsedPackage.getPackageName(),
                    originalPkgSetting, disabledPkgSetting, realPkgName, sharedUserSetting,
                    destCodeFile, parsedPackage.getNativeLibraryRootDir(),
                    AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage),
                    AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage),
                    parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
                    true /*allowInstall*/, instantApp, virtualPreload,
                    UserManagerService.getInstance(), usesSdkLibraries,
                    parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
                    parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
                    newDomainSetId);
        } else {
            // make a deep copy to avoid modifying any existing system state.
            pkgSetting = new PackageSetting(pkgSetting);
            pkgSetting.setPkg(parsedPackage);

            // REMOVE SharedUserSetting from method; update in a separate call.
            //
            // TODO(narayan): This update is bogus. nativeLibraryDir & primaryCpuAbi,
            // secondaryCpuAbi are not known at this point so we always update them
            // to null here, only to reset them at a later point.
            Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, oldSharedUserSetting,
                    sharedUserSetting, destCodeFile, parsedPackage.getNativeLibraryDir(),
                    AndroidPackageUtils.getPrimaryCpuAbi(parsedPackage, pkgSetting),
                    AndroidPackageUtils.getSecondaryCpuAbi(parsedPackage, pkgSetting),
                    PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
                    PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
                    UserManagerService.getInstance(),
                    usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
                    usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
                    parsedPackage.getMimeGroups(), newDomainSetId);
        }
        if (createNewPackage && originalPkgSetting != null) {
            // This is the initial transition from the original package, so,
            // fix up the new package's name now. We must do this after looking
            // up the package under its new name, so getPackageLP takes care of
            // fiddling things correctly.
            parsedPackage.setPackageName(originalPkgSetting.getPackageName());

            // File a report about this.
            String msg = "New package " + pkgSetting.getRealName()
                    + " renamed to replace old package " + pkgSetting.getPackageName();
            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
        }

        final int userId = (user == null ? UserHandle.USER_SYSTEM : user.getIdentifier());
        // for existing packages, change the install state; but, only if it's explicitly specified
        if (!createNewPackage) {
            final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
            final boolean fullApp = (scanFlags & SCAN_AS_FULL_APP) != 0;
            setInstantAppForUser(injector, pkgSetting, userId, instantApp, fullApp);
        }
        // TODO(patb): see if we can do away with disabled check here.
        if (disabledPkgSetting != null
                || (0 != (scanFlags & SCAN_NEW_INSTALL)
                && pkgSetting != null && pkgSetting.isSystem())) {
            pkgSetting.getPkgState().setUpdatedSystemApp(true);
        }

        parsedPackage.setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage, sharedUserSetting,
                injector.getCompatibility()));

        if (parsedPackage.isSystem()) {
            configurePackageComponents(parsedPackage);
        }

        final String cpuAbiOverride = deriveAbiOverride(request.mCpuAbiOverride);
        final boolean isUpdatedSystemApp = pkgSetting.getPkgState().isUpdatedSystemApp();

        final File appLib32InstallDir = getAppLib32InstallDir();
        if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
            if (needToDeriveAbi) {
                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
                final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths> derivedAbi =
                        packageAbiHelper.derivePackageAbi(parsedPackage, isUpdatedSystemApp,
                                cpuAbiOverride, appLib32InstallDir);
                derivedAbi.first.applyTo(parsedPackage);
                derivedAbi.second.applyTo(parsedPackage);
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);

                // Some system apps still use directory structure for native libraries
                // in which case we might end up not detecting abi solely based on apk
                // structure. Try to detect abi based on directory structure.

                String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage);
                if (parsedPackage.isSystem() && !isUpdatedSystemApp
                        && pkgRawPrimaryCpuAbi == null) {
                    final PackageAbiHelper.Abis abis = packageAbiHelper.getBundledAppAbis(
                            parsedPackage);
                    abis.applyTo(parsedPackage);
                    abis.applyTo(pkgSetting);
                    final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
                            packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
                                    isUpdatedSystemApp, appLib32InstallDir);
                    nativeLibraryPaths.applyTo(parsedPackage);
                }
            } else {
                // This is not a first boot or an upgrade, don't bother deriving the
                // ABI during the scan. Instead, trust the value that was stored in the
                // package setting.
                parsedPackage.setPrimaryCpuAbi(primaryCpuAbiFromSettings)
                        .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);

                final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
                        packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
                                isUpdatedSystemApp, appLib32InstallDir);
                nativeLibraryPaths.applyTo(parsedPackage);

                if (DEBUG_ABI_SELECTION) {
                    Slog.i(TAG, "Using ABIS and native lib paths from settings : "
                            + parsedPackage.getPackageName() + " "
                            + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage)
                            + ", "
                            + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
                }
            }
        } else {
            if ((scanFlags & SCAN_MOVE) != 0) {
                // We haven't run dex-opt for this move (since we've moved the compiled output too)
                // but we already have this packages package info in the PackageSetting. We just
                // use that and derive the native library path based on the new code path.
                parsedPackage.setPrimaryCpuAbi(pkgSetting.getPrimaryCpuAbi())
                        .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbi());
            }

            // Set native library paths again. For moves, the path will be updated based on the
            // ABIs we've determined above. For non-moves, the path will be updated based on the
            // ABIs we determined during compilation, but the path will depend on the final
            // package path (after the rename away from the stage path).
            final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
                    packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isUpdatedSystemApp,
                            appLib32InstallDir);
            nativeLibraryPaths.applyTo(parsedPackage);
        }

        // This is a special case for the "system" package, where the ABI is
        // dictated by the zygote configuration (and init.rc). We should keep track
        // of this ABI so that we can deal with "normal" applications that run under
        // the same UID correctly.
        if (isPlatformPackage) {
            parsedPackage.setPrimaryCpuAbi(VMRuntime.getRuntime().is64Bit()
                    ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]);
        }

        // If there's a mismatch between the abi-override in the package setting
        // and the abiOverride specified for the install. Warn about this because we
        // would've already compiled the app without taking the package setting into
        // account.
        if ((scanFlags & SCAN_NO_DEX) == 0 && (scanFlags & SCAN_NEW_INSTALL) != 0) {
            if (cpuAbiOverride == null) {
                Slog.w(TAG, "Ignoring persisted ABI override for package "
                        + parsedPackage.getPackageName());
            }
        }

        pkgSetting.setPrimaryCpuAbi(AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage))
                .setSecondaryCpuAbi(AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage))
                .setCpuAbiOverride(cpuAbiOverride);

        if (DEBUG_ABI_SELECTION) {
            Slog.d(TAG, "Resolved nativeLibraryRoot for " + parsedPackage.getPackageName()
                    + " to root=" + parsedPackage.getNativeLibraryRootDir()
                    + ", to dir=" + parsedPackage.getNativeLibraryDir()
                    + ", isa=" + parsedPackage.isNativeLibraryRootRequiresIsa());
        }

        // Push the derived path down into PackageSettings so we know what to
        // clean up at uninstall time.
        pkgSetting.setLegacyNativeLibraryPath(parsedPackage.getNativeLibraryRootDir());

        if (DEBUG_ABI_SELECTION) {
            Log.d(TAG, "Abis for package[" + parsedPackage.getPackageName() + "] are"
                    + " primary=" + pkgSetting.getPrimaryCpuAbi()
                    + " secondary=" + pkgSetting.getSecondaryCpuAbi()
                    + " abiOverride=" + pkgSetting.getCpuAbiOverride());
        }

        if ((scanFlags & SCAN_BOOTING) == 0 && oldSharedUserSetting != null) {
            // We don't do this here during boot because we can do it all
            // at once after scanning all existing packages.
            //
            // We also do this *before* we perform dexopt on this package, so that
            // we can avoid redundant dexopts, and also to make sure we've got the
            // code and package path correct.
            changedAbiCodePath = applyAdjustedAbiToSharedUser(oldSharedUserSetting,
                    parsedPackage, packageAbiHelper.getAdjustedAbiForSharedUser(
                            oldSharedUserSetting.getPackageStates(), parsedPackage));
        }

        parsedPackage.setFactoryTest(isUnderFactoryTest && parsedPackage.getRequestedPermissions()
                .contains(android.Manifest.permission.FACTORY_TEST));

        if (parsedPackage.isSystem()) {
            pkgSetting.setIsOrphaned(true);
        }

        // Take care of first install / last update times.
        final long scanFileTime = getLastModifiedTime(parsedPackage);
        final long existingFirstInstallTime = userId == UserHandle.USER_ALL
                ? PackageStateUtils.getEarliestFirstInstallTime(pkgSetting.getUserStates())
                : pkgSetting.readUserState(userId).getFirstInstallTime();
        if (currentTime != 0) {
            if (existingFirstInstallTime == 0) {
                pkgSetting.setFirstInstallTime(currentTime, userId)
                        .setLastUpdateTime(currentTime);
            } else if ((scanFlags & SCAN_UPDATE_TIME) != 0) {
                pkgSetting.setLastUpdateTime(currentTime);
            }
        } else if (existingFirstInstallTime == 0) {
            // We need *something*.  Take time stamp of the file.
            pkgSetting.setFirstInstallTime(scanFileTime, userId)
                    .setLastUpdateTime(scanFileTime);
        } else if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0) {
            if (scanFileTime != pkgSetting.getLastModifiedTime()) {
                // A package on the system image has changed; consider this
                // to be an update.
                pkgSetting.setLastUpdateTime(scanFileTime);
            }
        }
        pkgSetting.setLastModifiedTime(scanFileTime);
        // TODO(b/135203078): Remove, move to constructor
        pkgSetting.setPkg(parsedPackage)
                .setPkgFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
                        PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting));
        if (parsedPackage.getLongVersionCode() != pkgSetting.getVersionCode()) {
            pkgSetting.setLongVersionCode(parsedPackage.getLongVersionCode());
        }
        // Update volume if needed
        final String volumeUuid = parsedPackage.getVolumeUuid();
        if (!Objects.equals(volumeUuid, pkgSetting.getVolumeUuid())) {
            Slog.i(PackageManagerService.TAG,
                    "Update" + (pkgSetting.isSystem() ? " system" : "")
                            + " package " + parsedPackage.getPackageName()
                            + " volume from " + pkgSetting.getVolumeUuid()
                            + " to " + volumeUuid);
            pkgSetting.setVolumeUuid(volumeUuid);
        }

        SharedLibraryInfo sdkLibraryInfo = null;
        if (!TextUtils.isEmpty(parsedPackage.getSdkLibName())) {
            sdkLibraryInfo = AndroidPackageUtils.createSharedLibraryForSdk(parsedPackage);
        }
        SharedLibraryInfo staticSharedLibraryInfo = null;
        if (!TextUtils.isEmpty(parsedPackage.getStaticSharedLibName())) {
            staticSharedLibraryInfo =
                    AndroidPackageUtils.createSharedLibraryForStatic(parsedPackage);
        }
        List<SharedLibraryInfo> dynamicSharedLibraryInfos = null;
        if (!ArrayUtils.isEmpty(parsedPackage.getLibraryNames())) {
            dynamicSharedLibraryInfos = new ArrayList<>(parsedPackage.getLibraryNames().size());
            for (String name : parsedPackage.getLibraryNames()) {
                dynamicSharedLibraryInfos.add(
                        AndroidPackageUtils.createSharedLibraryForDynamic(parsedPackage, name));
            }
        }

        return new ScanResult(request, true, pkgSetting, changedAbiCodePath,
                !createNewPackage /* existingSettingCopied */,
                Process.INVALID_UID /* previousAppId */ , sdkLibraryInfo,
                staticSharedLibraryInfo, dynamicSharedLibraryInfos);
    }

    /**
     * Returns the actual scan flags depending upon the state of the other settings.
     * <p>Updated system applications will not have the following flags set
     * by default and need to be adjusted after the fact:
     * <ul>
     * <li>{@link PackageManagerService.SCAN_AS_SYSTEM}</li>
     * <li>{@link PackageManagerService.SCAN_AS_PRIVILEGED}</li>
     * <li>{@link PackageManagerService.SCAN_AS_OEM}</li>
     * <li>{@link PackageManagerService.SCAN_AS_VENDOR}</li>
     * <li>{@link PackageManagerService.SCAN_AS_PRODUCT}</li>
     * <li>{@link PackageManagerService.SCAN_AS_SYSTEM_EXT}</li>
     * <li>{@link PackageManagerService.SCAN_AS_INSTANT_APP}</li>
     * <li>{@link PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD}</li>
     * <li>{@link PackageManagerService.SCAN_AS_ODM}</li>
     * </ul>
     */
    public static @PackageManagerService.ScanFlags int adjustScanFlagsWithPackageSetting(
            @PackageManagerService.ScanFlags int scanFlags,
            PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user) {

        // TODO(patb): Do away entirely with disabledPkgSetting here. PkgSetting will always contain
        // the correct isSystem value now that we don't disable system packages before scan.
        final PackageSetting systemPkgSetting =
                (scanFlags & SCAN_NEW_INSTALL) != 0 && disabledPkgSetting == null
                        && pkgSetting != null && pkgSetting.isSystem()
                        ? pkgSetting
                        : disabledPkgSetting;
        if (systemPkgSetting != null)  {
            // updated system application, must at least have SCAN_AS_SYSTEM
            scanFlags |= SCAN_AS_SYSTEM;
            if ((systemPkgSetting.getPrivateFlags()
                    & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
                scanFlags |= SCAN_AS_PRIVILEGED;
            }
            if ((systemPkgSetting.getPrivateFlags()
                    & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
                scanFlags |= SCAN_AS_OEM;
            }
            if ((systemPkgSetting.getPrivateFlags()
                    & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0) {
                scanFlags |= SCAN_AS_VENDOR;
            }
            if ((systemPkgSetting.getPrivateFlags()
                    & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0) {
                scanFlags |= SCAN_AS_PRODUCT;
            }
            if ((systemPkgSetting.getPrivateFlags()
                    & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
                scanFlags |= SCAN_AS_SYSTEM_EXT;
            }
            if ((systemPkgSetting.getPrivateFlags()
                    & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
                scanFlags |= SCAN_AS_ODM;
            }
        }
        if (pkgSetting != null) {
            final int userId = ((user == null) ? 0 : user.getIdentifier());
            if (pkgSetting.getInstantApp(userId)) {
                scanFlags |= SCAN_AS_INSTANT_APP;
            }
            if (pkgSetting.getVirtualPreload(userId)) {
                scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
            }
        }

        return scanFlags;
    }

    /**
     * Enforces code policy for the package. This ensures that if an APK has
     * declared hasCode="true" in its manifest that the APK actually contains
     * code.
     *
     * @throws PackageManagerException If bytecode could not be found when it should exist
     */
    public static void assertCodePolicy(AndroidPackage pkg)
            throws PackageManagerException {
        final boolean shouldHaveCode = pkg.isHasCode();
        if (shouldHaveCode && !apkHasCode(pkg.getBaseApkPath())) {
            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                    "Package " + pkg.getBaseApkPath() + " code is missing");
        }

        if (!ArrayUtils.isEmpty(pkg.getSplitCodePaths())) {
            for (int i = 0; i < pkg.getSplitCodePaths().length; i++) {
                final boolean splitShouldHaveCode =
                        (pkg.getSplitFlags()[i] & ApplicationInfo.FLAG_HAS_CODE) != 0;
                if (splitShouldHaveCode && !apkHasCode(pkg.getSplitCodePaths()[i])) {
                    throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                            "Package " + pkg.getSplitCodePaths()[i] + " code is missing");
                }
            }
        }
    }

    public static void assertStaticSharedLibraryIsValid(AndroidPackage pkg,
            @PackageManagerService.ScanFlags int scanFlags) throws PackageManagerException {
        // Static shared libraries should have at least O target SDK
        if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.O) {
            throw new PackageManagerException(
                    "Packages declaring static-shared libs must target O SDK or higher");
        }

        // Package declaring static a shared lib cannot be instant apps
        if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
            throw new PackageManagerException(
                    "Packages declaring static-shared libs cannot be instant apps");
        }

        // Package declaring static a shared lib cannot be renamed since the package
        // name is synthetic and apps can't code around package manager internals.
        if (!ArrayUtils.isEmpty(pkg.getOriginalPackages())) {
            throw new PackageManagerException(
                    "Packages declaring static-shared libs cannot be renamed");
        }

        // Package declaring static a shared lib cannot declare dynamic libs
        if (!ArrayUtils.isEmpty(pkg.getLibraryNames())) {
            throw new PackageManagerException(
                    "Packages declaring static-shared libs cannot declare dynamic libs");
        }

        // Package declaring static a shared lib cannot declare shared users
        if (pkg.getSharedUserId() != null) {
            throw new PackageManagerException(
                    "Packages declaring static-shared libs cannot declare shared users");
        }

        // Static shared libs cannot declare activities
        if (!pkg.getActivities().isEmpty()) {
            throw new PackageManagerException(
                    "Static shared libs cannot declare activities");
        }

        // Static shared libs cannot declare services
        if (!pkg.getServices().isEmpty()) {
            throw new PackageManagerException(
                    "Static shared libs cannot declare services");
        }

        // Static shared libs cannot declare providers
        if (!pkg.getProviders().isEmpty()) {
            throw new PackageManagerException(
                    "Static shared libs cannot declare content providers");
        }

        // Static shared libs cannot declare receivers
        if (!pkg.getReceivers().isEmpty()) {
            throw new PackageManagerException(
                    "Static shared libs cannot declare broadcast receivers");
        }

        // Static shared libs cannot declare permission groups
        if (!pkg.getPermissionGroups().isEmpty()) {
            throw new PackageManagerException(
                    "Static shared libs cannot declare permission groups");
        }

        // Static shared libs cannot declare attributions
        if (!pkg.getAttributions().isEmpty()) {
            throw new PackageManagerException(
                    "Static shared libs cannot declare features");
        }

        // Static shared libs cannot declare permissions
        if (!pkg.getPermissions().isEmpty()) {
            throw new PackageManagerException(
                    "Static shared libs cannot declare permissions");
        }

        // Static shared libs cannot declare protected broadcasts
        if (!pkg.getProtectedBroadcasts().isEmpty()) {
            throw new PackageManagerException(
                    "Static shared libs cannot declare protected broadcasts");
        }

        // Static shared libs cannot be overlay targets
        if (pkg.getOverlayTarget() != null) {
            throw new PackageManagerException(
                    "Static shared libs cannot be overlay targets");
        }
    }

    public static void assertProcessesAreValid(AndroidPackage pkg) throws PackageManagerException {
        final Map<String, ParsedProcess> procs = pkg.getProcesses();
        if (!procs.isEmpty()) {
            if (!procs.containsKey(pkg.getProcessName())) {
                throw new PackageManagerException(
                        INSTALL_FAILED_PROCESS_NOT_DEFINED,
                        "Can't install because application tag's process attribute "
                                + pkg.getProcessName()
                                + " (in package " + pkg.getPackageName()
                                + ") is not included in the <processes> list");
            }
            assertPackageProcesses(pkg, pkg.getActivities(), procs, "activity");
            assertPackageProcesses(pkg, pkg.getServices(), procs, "service");
            assertPackageProcesses(pkg, pkg.getReceivers(), procs, "receiver");
            assertPackageProcesses(pkg, pkg.getProviders(), procs, "provider");
        }
    }

    private static <T extends ParsedMainComponent> void assertPackageProcesses(AndroidPackage pkg,
            List<T> components, Map<String, ParsedProcess> procs, String compName)
            throws PackageManagerException {
        if (components == null) {
            return;
        }
        for (int i = components.size() - 1; i >= 0; i--) {
            final ParsedMainComponent component = components.get(i);
            if (!procs.containsKey(component.getProcessName())) {
                throw new PackageManagerException(
                        INSTALL_FAILED_PROCESS_NOT_DEFINED,
                        "Can't install because " + compName + " " + component.getClassName()
                                + "'s process attribute " + component.getProcessName()
                                + " (in package " + pkg.getPackageName()
                                + ") is not included in the <processes> list");
            }
        }
    }

    public static void assertMinSignatureSchemeIsValid(AndroidPackage pkg,
            @ParsingPackageUtils.ParseFlags int parseFlags) throws PackageManagerException {
        int minSignatureSchemeVersion =
                ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
                        pkg.getTargetSdkVersion());
        if (pkg.getSigningDetails().getSignatureSchemeVersion()
                < minSignatureSchemeVersion) {
            throw new PackageManagerException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                    "No signature found in package of version " + minSignatureSchemeVersion
                            + " or newer for package " + pkg.getPackageName());
        }
    }

    /**
     * Returns the "real" name of the package.
     * <p>This may differ from the package's actual name if the application has already
     * been installed under one of this package's original names.
     */
    public static @Nullable String getRealPackageName(@NonNull AndroidPackage pkg,
            @Nullable String renamedPkgName) {
        if (isPackageRenamed(pkg, renamedPkgName)) {
            return AndroidPackageUtils.getRealPackageOrNull(pkg);
        }
        return null;
    }

    /** Returns {@code true} if the package has been renamed. Otherwise, {@code false}. */
    public static boolean isPackageRenamed(@NonNull AndroidPackage pkg,
            @Nullable String renamedPkgName) {
        return pkg.getOriginalPackages().contains(renamedPkgName);
    }

    /**
     * Renames the package if it was installed under a different name.
     * <p>When we've already installed the package under an original name, update
     * the new package so we can continue to have the old name.
     */
    public static void ensurePackageRenamed(@NonNull ParsedPackage parsedPackage,
            @NonNull String renamedPackageName) {
        if (!parsedPackage.getOriginalPackages().contains(renamedPackageName)
                || parsedPackage.getPackageName().equals(renamedPackageName)) {
            return;
        }
        parsedPackage.setPackageName(renamedPackageName);
    }

    /**
     * Returns {@code true} if the given file contains code. Otherwise {@code false}.
     */
    public static boolean apkHasCode(String fileName) {
        StrictJarFile jarFile = null;
        try {
            jarFile = new StrictJarFile(fileName,
                    false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
            return jarFile.findEntry("classes.dex") != null;
        } catch (IOException ignore) {
        } finally {
            try {
                if (jarFile != null) {
                    jarFile.close();
                }
            } catch (IOException ignore) {
            }
        }
        return false;
    }

    /**
     * Sets the enabled state of components configured through {@link SystemConfig}.
     * This modifies the {@link PackageSetting} object.
     *
     * TODO(b/135203078): Move this to package parsing
     **/
    public static void configurePackageComponents(AndroidPackage pkg) {
        final ArrayMap<String, Boolean> componentsEnabledStates = SystemConfig.getInstance()
                .getComponentsEnabledStates(pkg.getPackageName());
        if (componentsEnabledStates == null) {
            return;
        }

        for (int i = ArrayUtils.size(pkg.getActivities()) - 1; i >= 0; i--) {
            final ParsedActivity component = pkg.getActivities().get(i);
            final Boolean enabled = componentsEnabledStates.get(component.getName());
            if (enabled != null) {
                ComponentMutateUtils.setEnabled(component, enabled);
            }
        }

        for (int i = ArrayUtils.size(pkg.getReceivers()) - 1; i >= 0; i--) {
            final ParsedActivity component = pkg.getReceivers().get(i);
            final Boolean enabled = componentsEnabledStates.get(component.getName());
            if (enabled != null) {
                ComponentMutateUtils.setEnabled(component, enabled);
            }
        }

        for (int i = ArrayUtils.size(pkg.getProviders()) - 1; i >= 0; i--) {
            final ParsedProvider component = pkg.getProviders().get(i);
            final Boolean enabled = componentsEnabledStates.get(component.getName());
            if (enabled != null) {
                ComponentMutateUtils.setEnabled(component, enabled);
            }
        }

        for (int i = ArrayUtils.size(pkg.getServices()) - 1; i >= 0; i--) {
            final ParsedService component = pkg.getServices().get(i);
            final Boolean enabled = componentsEnabledStates.get(component.getName());
            if (enabled != null) {
                ComponentMutateUtils.setEnabled(component, enabled);
            }
        }
    }

    public static int getVendorPartitionVersion() {
        final String version = SystemProperties.get("ro.vndk.version");
        if (!version.isEmpty()) {
            try {
                return Integer.parseInt(version);
            } catch (NumberFormatException ignore) {
                if (ArrayUtils.contains(Build.VERSION.ACTIVE_CODENAMES, version)) {
                    return Build.VERSION_CODES.CUR_DEVELOPMENT;
                }
            }
        }
        return Build.VERSION_CODES.P;
    }

    /**
     * Applies policy to the parsed package based upon the given policy flags.
     * Ensures the package is in a good state.
     * <p>
     * Implementation detail: This method must NOT have any side effect. It would
     * ideally be static, but, it requires locks to read system state.
     */
    public static void applyPolicy(ParsedPackage parsedPackage,
            final @PackageManagerService.ScanFlags int scanFlags, AndroidPackage platformPkg,
            boolean isUpdatedSystemApp) {
        if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
            parsedPackage.setSystem(true);
            // TODO(b/135203078): Can this be done in PackageParser? Or just inferred when the flag
            //  is set during parse.
            if (parsedPackage.isDirectBootAware()) {
                parsedPackage.setAllComponentsDirectBootAware(true);
            }
            if (compressedFileExists(parsedPackage.getPath())) {
                parsedPackage.setStub(true);
            }
        } else {
            parsedPackage
                    // Non system apps cannot mark any broadcast as protected
                    .clearProtectedBroadcasts()
                    // non system apps can't be flagged as core
                    .setCoreApp(false)
                    // clear flags not applicable to regular apps
                    .setPersistent(false)
                    .setDefaultToDeviceProtectedStorage(false)
                    .setDirectBootAware(false)
                    // non system apps can't have permission priority
                    .capPermissionPriorities();
        }
        if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
            parsedPackage
                    .markNotActivitiesAsNotExportedIfSingleUser();
        }

        parsedPackage.setPrivileged((scanFlags & SCAN_AS_PRIVILEGED) != 0)
                .setOem((scanFlags & SCAN_AS_OEM) != 0)
                .setVendor((scanFlags & SCAN_AS_VENDOR) != 0)
                .setProduct((scanFlags & SCAN_AS_PRODUCT) != 0)
                .setSystemExt((scanFlags & SCAN_AS_SYSTEM_EXT) != 0)
                .setOdm((scanFlags & SCAN_AS_ODM) != 0);

        // Check if the package is signed with the same key as the platform package.
        parsedPackage.setSignedWithPlatformKey(
                (PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())
                        || (platformPkg != null && compareSignatures(
                        platformPkg.getSigningDetails().getSignatures(),
                        parsedPackage.getSigningDetails().getSignatures()
                ) == PackageManager.SIGNATURE_MATCH))
        );

        if (!parsedPackage.isSystem()) {
            // Only system apps can use these features.
            parsedPackage.clearOriginalPackages()
                    .clearAdoptPermissions();
        }

        PackageBackwardCompatibility.modifySharedLibraries(parsedPackage, isUpdatedSystemApp);
    }

    /**
     * Applies the adjusted ABI calculated by
     * {@link PackageAbiHelper#getAdjustedAbiForSharedUser(ArraySet, AndroidPackage)} to all
     * relevant packages and settings.
     * @param sharedUserSetting The {@code SharedUserSetting} to adjust
     * @param scannedPackage the package being scanned or null
     * @param adjustedAbi the adjusted ABI calculated by {@link PackageAbiHelper}
     * @return the list of code paths that belong to packages that had their ABIs adjusted.
     */
    public static List<String> applyAdjustedAbiToSharedUser(SharedUserSetting sharedUserSetting,
            ParsedPackage scannedPackage, String adjustedAbi) {
        if (scannedPackage != null)  {
            scannedPackage.setPrimaryCpuAbi(adjustedAbi);
        }
        List<String> changedAbiCodePath = null;
        final WatchedArraySet<PackageSetting> sharedUserPackageSettings =
                sharedUserSetting.getPackageSettings();
        for (int i = 0; i < sharedUserPackageSettings.size(); i++) {
            PackageSetting ps = sharedUserPackageSettings.valueAt(i);
            if (scannedPackage == null
                    || !scannedPackage.getPackageName().equals(ps.getPackageName())) {
                if (ps.getPrimaryCpuAbi() != null) {
                    continue;
                }

                ps.setPrimaryCpuAbi(adjustedAbi);
                ps.onChanged();
                if (ps.getPkg() != null) {
                    if (!TextUtils.equals(adjustedAbi,
                            AndroidPackageUtils.getRawPrimaryCpuAbi(ps.getPkg()))) {
                        if (DEBUG_ABI_SELECTION) {
                            Slog.i(TAG,
                                    "Adjusting ABI for " + ps.getPackageName() + " to "
                                            + adjustedAbi + " (scannedPackage="
                                            + (scannedPackage != null ? scannedPackage : "null")
                                            + ")");
                        }
                        if (changedAbiCodePath == null) {
                            changedAbiCodePath = new ArrayList<>();
                        }
                        changedAbiCodePath.add(ps.getPathString());
                    }
                }
            }
        }
        return changedAbiCodePath;
    }

    public static void collectCertificatesLI(PackageSetting ps, ParsedPackage parsedPackage,
            Settings.VersionInfo settingsVersionForPackage, boolean forceCollect,
            boolean skipVerify, boolean isPreNMR1Upgrade)
            throws PackageManagerException {
        // When upgrading from pre-N MR1, verify the package time stamp using the package
        // directory and not the APK file.
        final long lastModifiedTime = isPreNMR1Upgrade
                ? new File(parsedPackage.getPath()).lastModified()
                : getLastModifiedTime(parsedPackage);
        if (ps != null && !forceCollect
                && ps.getPathString().equals(parsedPackage.getPath())
                && ps.getLastModifiedTime() == lastModifiedTime
                && !ReconcilePackageUtils.isCompatSignatureUpdateNeeded(settingsVersionForPackage)
                && !ReconcilePackageUtils.isRecoverSignatureUpdateNeeded(
                settingsVersionForPackage)) {
            if (ps.getSigningDetails().getSignatures() != null
                    && ps.getSigningDetails().getSignatures().length != 0
                    && ps.getSigningDetails().getSignatureSchemeVersion()
                    != SigningDetails.SignatureSchemeVersion.UNKNOWN) {
                // Optimization: reuse the existing cached signing data
                // if the package appears to be unchanged.
                parsedPackage.setSigningDetails(
                        new SigningDetails(ps.getSigningDetails()));
                return;
            }

            Slog.w(TAG, "PackageSetting for " + ps.getPackageName()
                    + " is missing signatures.  Collecting certs again to recover them.");
        } else {
            Slog.i(TAG, parsedPackage.getPath() + " changed; collecting certs"
                    + (forceCollect ? " (forced)" : ""));
        }

        try {
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
            final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
                    input, parsedPackage, skipVerify);
            if (result.isError()) {
                throw new PackageManagerException(
                        result.getErrorCode(), result.getErrorMessage(), result.getException());
            }
            parsedPackage.setSigningDetails(result.getResult());
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }

    public static void setInstantAppForUser(PackageManagerServiceInjector injector,
            PackageSetting pkgSetting, int userId, boolean instantApp, boolean fullApp) {
        // no state specified; do nothing
        if (!instantApp && !fullApp) {
            return;
        }
        if (userId != UserHandle.USER_ALL) {
            if (instantApp && !pkgSetting.getInstantApp(userId)) {
                pkgSetting.setInstantApp(true /*instantApp*/, userId);
            } else if (fullApp && pkgSetting.getInstantApp(userId)) {
                pkgSetting.setInstantApp(false /*instantApp*/, userId);
            }
        } else {
            for (int currentUserId : injector.getUserManagerInternal().getUserIds()) {
                if (instantApp && !pkgSetting.getInstantApp(currentUserId)) {
                    pkgSetting.setInstantApp(true /*instantApp*/, currentUserId);
                } else if (fullApp && pkgSetting.getInstantApp(currentUserId)) {
                    pkgSetting.setInstantApp(false /*instantApp*/, currentUserId);
                }
            }
        }
    }

    /** Directory where installed application's 32-bit native libraries are copied. */
    public static File getAppLib32InstallDir() {
        return new File(Environment.getDataDirectory(), "app-lib");
    }
}
