| /* |
| * 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.os.Trace.TRACE_TAG_PACKAGE_MANAGER; |
| |
| import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; |
| import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; |
| import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; |
| import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_OTA; |
| import static com.android.server.pm.PackageManagerService.REASON_CMDLINE; |
| import static com.android.server.pm.PackageManagerService.REASON_FIRST_BOOT; |
| import static com.android.server.pm.PackageManagerService.STUB_SUFFIX; |
| import static com.android.server.pm.PackageManagerService.TAG; |
| import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter; |
| import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG; |
| |
| import android.annotation.NonNull; |
| import android.app.ActivityManager; |
| import android.app.AppGlobals; |
| import android.content.Intent; |
| import android.content.pm.ResolveInfo; |
| import android.content.pm.SharedLibraryInfo; |
| import android.content.pm.dex.ArtManager; |
| import android.os.Binder; |
| import android.os.RemoteException; |
| import android.os.SystemClock; |
| import android.os.SystemProperties; |
| import android.os.Trace; |
| import android.os.UserHandle; |
| import android.util.ArraySet; |
| import android.util.Log; |
| import android.util.Slog; |
| |
| import com.android.internal.R; |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.server.pm.dex.DexManager; |
| import com.android.server.pm.dex.DexoptOptions; |
| import com.android.server.pm.parsing.pkg.AndroidPackage; |
| import com.android.server.pm.parsing.pkg.AndroidPackageUtils; |
| import com.android.server.pm.pkg.PackageStateInternal; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| import java.util.function.Predicate; |
| |
| final class DexOptHelper { |
| private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000; |
| |
| private final PackageManagerService mPm; |
| |
| public boolean isDexOptDialogShown() { |
| return mDexOptDialogShown; |
| } |
| |
| @GuardedBy("mPm.mLock") |
| private boolean mDexOptDialogShown; |
| |
| DexOptHelper(PackageManagerService pm) { |
| mPm = pm; |
| } |
| |
| /* |
| * Return the prebuilt profile path given a package base code path. |
| */ |
| private static String getPrebuildProfilePath(AndroidPackage pkg) { |
| return pkg.getBaseApkPath() + ".prof"; |
| } |
| |
| /** |
| * Performs dexopt on the set of packages in {@code packages} and returns an int array |
| * containing statistics about the invocation. The array consists of three elements, |
| * which are (in order) {@code numberOfPackagesOptimized}, {@code numberOfPackagesSkipped} |
| * and {@code numberOfPackagesFailed}. |
| */ |
| public int[] performDexOptUpgrade(List<AndroidPackage> pkgs, boolean showDialog, |
| final int compilationReason, boolean bootComplete) { |
| |
| int numberOfPackagesVisited = 0; |
| int numberOfPackagesOptimized = 0; |
| int numberOfPackagesSkipped = 0; |
| int numberOfPackagesFailed = 0; |
| final int numberOfPackagesToDexopt = pkgs.size(); |
| |
| for (AndroidPackage pkg : pkgs) { |
| numberOfPackagesVisited++; |
| |
| boolean useProfileForDexopt = false; |
| |
| if ((mPm.isFirstBoot() || mPm.isDeviceUpgrading()) && pkg.isSystem()) { |
| // Copy over initial preopt profiles since we won't get any JIT samples for methods |
| // that are already compiled. |
| File profileFile = new File(getPrebuildProfilePath(pkg)); |
| // Copy profile if it exists. |
| if (profileFile.exists()) { |
| try { |
| // We could also do this lazily before calling dexopt in |
| // PackageDexOptimizer to prevent this happening on first boot. The issue |
| // is that we don't have a good way to say "do this only once". |
| if (!mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(), |
| pkg.getUid(), pkg.getPackageName(), |
| ArtManager.getProfileName(null))) { |
| Log.e(TAG, "Installer failed to copy system profile!"); |
| } else { |
| // Disabled as this causes speed-profile compilation during first boot |
| // even if things are already compiled. |
| // useProfileForDexopt = true; |
| } |
| } catch (Exception e) { |
| Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ", |
| e); |
| } |
| } else { |
| PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr( |
| pkg.getPackageName()); |
| // Handle compressed APKs in this path. Only do this for stubs with profiles to |
| // minimize the number off apps being speed-profile compiled during first boot. |
| // The other paths will not change the filter. |
| if (disabledPs != null && disabledPs.getPkg().isStub()) { |
| // The package is the stub one, remove the stub suffix to get the normal |
| // package and APK names. |
| String systemProfilePath = getPrebuildProfilePath(disabledPs.getPkg()) |
| .replace(STUB_SUFFIX, ""); |
| profileFile = new File(systemProfilePath); |
| // If we have a profile for a compressed APK, copy it to the reference |
| // location. |
| // Note that copying the profile here will cause it to override the |
| // reference profile every OTA even though the existing reference profile |
| // may have more data. We can't copy during decompression since the |
| // directories are not set up at that point. |
| if (profileFile.exists()) { |
| try { |
| // We could also do this lazily before calling dexopt in |
| // PackageDexOptimizer to prevent this happening on first boot. The |
| // issue is that we don't have a good way to say "do this only |
| // once". |
| if (!mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(), |
| pkg.getUid(), pkg.getPackageName(), |
| ArtManager.getProfileName(null))) { |
| Log.e(TAG, "Failed to copy system profile for stub package!"); |
| } else { |
| useProfileForDexopt = true; |
| } |
| } catch (Exception e) { |
| Log.e(TAG, "Failed to copy profile " |
| + profileFile.getAbsolutePath() + " ", e); |
| } |
| } |
| } |
| } |
| } |
| |
| if (!mPm.mPackageDexOptimizer.canOptimizePackage(pkg)) { |
| if (DEBUG_DEXOPT) { |
| Log.i(TAG, "Skipping update of non-optimizable app " + pkg.getPackageName()); |
| } |
| numberOfPackagesSkipped++; |
| continue; |
| } |
| |
| if (DEBUG_DEXOPT) { |
| Log.i(TAG, "Updating app " + numberOfPackagesVisited + " of " |
| + numberOfPackagesToDexopt + ": " + pkg.getPackageName()); |
| } |
| |
| if (showDialog) { |
| try { |
| ActivityManager.getService().showBootMessage( |
| mPm.mContext.getResources().getString(R.string.android_upgrading_apk, |
| numberOfPackagesVisited, numberOfPackagesToDexopt), true); |
| } catch (RemoteException e) { |
| } |
| synchronized (mPm.mLock) { |
| mDexOptDialogShown = true; |
| } |
| } |
| |
| int pkgCompilationReason = compilationReason; |
| if (useProfileForDexopt) { |
| // Use background dexopt mode to try and use the profile. Note that this does not |
| // guarantee usage of the profile. |
| pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT; |
| } |
| |
| if (SystemProperties.getBoolean(mPm.PRECOMPILE_LAYOUTS, false)) { |
| mPm.mArtManagerService.compileLayouts(pkg); |
| } |
| |
| // checkProfiles is false to avoid merging profiles during boot which |
| // might interfere with background compilation (b/28612421). |
| // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will |
| // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a |
| // trade-off worth doing to save boot time work. |
| int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0; |
| if (compilationReason == REASON_FIRST_BOOT) { |
| // TODO: This doesn't cover the upgrade case, we should check for this too. |
| dexoptFlags |= DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE; |
| } |
| int primaryDexOptStatus = performDexOptTraced(new DexoptOptions( |
| pkg.getPackageName(), |
| pkgCompilationReason, |
| dexoptFlags)); |
| |
| switch (primaryDexOptStatus) { |
| case PackageDexOptimizer.DEX_OPT_PERFORMED: |
| numberOfPackagesOptimized++; |
| break; |
| case PackageDexOptimizer.DEX_OPT_SKIPPED: |
| numberOfPackagesSkipped++; |
| break; |
| case PackageDexOptimizer.DEX_OPT_CANCELLED: |
| // ignore this case |
| break; |
| case PackageDexOptimizer.DEX_OPT_FAILED: |
| numberOfPackagesFailed++; |
| break; |
| default: |
| Log.e(TAG, "Unexpected dexopt return code " + primaryDexOptStatus); |
| break; |
| } |
| } |
| |
| return new int[]{numberOfPackagesOptimized, numberOfPackagesSkipped, |
| numberOfPackagesFailed}; |
| } |
| |
| public void performPackageDexOptUpgradeIfNeeded() { |
| PackageManagerServiceUtils.enforceSystemOrRoot( |
| "Only the system can request package update"); |
| |
| // We need to re-extract after an OTA. |
| boolean causeUpgrade = mPm.isDeviceUpgrading(); |
| |
| // First boot or factory reset. |
| // Note: we also handle devices that are upgrading to N right now as if it is their |
| // first boot, as they do not have profile data. |
| boolean causeFirstBoot = mPm.isFirstBoot() || mPm.isPreNUpgrade(); |
| |
| if (!causeUpgrade && !causeFirstBoot) { |
| return; |
| } |
| |
| List<PackageStateInternal> pkgSettings = |
| getPackagesForDexopt(mPm.getPackageStates().values(), mPm); |
| |
| List<AndroidPackage> pkgs = new ArrayList<>(pkgSettings.size()); |
| for (int index = 0; index < pkgSettings.size(); index++) { |
| pkgs.add(pkgSettings.get(index).getPkg()); |
| } |
| |
| final long startTime = System.nanoTime(); |
| final int[] stats = performDexOptUpgrade(pkgs, mPm.isPreNUpgrade() /* showDialog */, |
| causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA, |
| false /* bootComplete */); |
| |
| final int elapsedTimeSeconds = |
| (int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime); |
| |
| MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_dexopted", stats[0]); |
| MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_skipped", stats[1]); |
| MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_failed", stats[2]); |
| MetricsLogger.histogram( |
| mPm.mContext, "opt_dialog_num_total", getOptimizablePackages().size()); |
| MetricsLogger.histogram(mPm.mContext, "opt_dialog_time_s", elapsedTimeSeconds); |
| } |
| |
| public ArraySet<String> getOptimizablePackages() { |
| ArraySet<String> pkgs = new ArraySet<>(); |
| synchronized (mPm.mLock) { |
| for (AndroidPackage p : mPm.mPackages.values()) { |
| if (mPm.mPackageDexOptimizer.canOptimizePackage(p)) { |
| pkgs.add(p.getPackageName()); |
| } |
| } |
| } |
| return pkgs; |
| } |
| |
| /*package*/ boolean performDexOpt(DexoptOptions options) { |
| if (mPm.getInstantAppPackageName(Binder.getCallingUid()) != null) { |
| return false; |
| } else if (mPm.isInstantApp(options.getPackageName(), UserHandle.getCallingUserId())) { |
| return false; |
| } |
| |
| if (options.isDexoptOnlySecondaryDex()) { |
| return mPm.getDexManager().dexoptSecondaryDex(options); |
| } else { |
| int dexoptStatus = performDexOptWithStatus(options); |
| return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED; |
| } |
| } |
| |
| /** |
| * Perform dexopt on the given package and return one of following result: |
| * {@link PackageDexOptimizer#DEX_OPT_SKIPPED} |
| * {@link PackageDexOptimizer#DEX_OPT_PERFORMED} |
| * {@link PackageDexOptimizer#DEX_OPT_CANCELLED} |
| * {@link PackageDexOptimizer#DEX_OPT_FAILED} |
| */ |
| @PackageDexOptimizer.DexOptResult |
| /* package */ int performDexOptWithStatus(DexoptOptions options) { |
| return performDexOptTraced(options); |
| } |
| |
| private int performDexOptTraced(DexoptOptions options) { |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); |
| try { |
| return performDexOptInternal(options); |
| } finally { |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| } |
| |
| // Run dexopt on a given package. Returns true if dexopt did not fail, i.e. |
| // if the package can now be considered up to date for the given filter. |
| private int performDexOptInternal(DexoptOptions options) { |
| AndroidPackage p; |
| PackageSetting pkgSetting; |
| synchronized (mPm.mLock) { |
| p = mPm.mPackages.get(options.getPackageName()); |
| pkgSetting = mPm.mSettings.getPackageLPr(options.getPackageName()); |
| if (p == null || pkgSetting == null) { |
| // Package could not be found. Report failure. |
| return PackageDexOptimizer.DEX_OPT_FAILED; |
| } |
| mPm.getPackageUsage().maybeWriteAsync(mPm.mSettings.getPackagesLocked()); |
| mPm.mCompilerStats.maybeWriteAsync(); |
| } |
| final long callingId = Binder.clearCallingIdentity(); |
| try { |
| synchronized (mPm.mInstallLock) { |
| return performDexOptInternalWithDependenciesLI(p, pkgSetting, options); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(callingId); |
| } |
| } |
| |
| private int performDexOptInternalWithDependenciesLI(AndroidPackage p, |
| @NonNull PackageStateInternal pkgSetting, DexoptOptions options) { |
| // System server gets a special path. |
| if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) { |
| return mPm.getDexManager().dexoptSystemServer(options); |
| } |
| |
| // Select the dex optimizer based on the force parameter. |
| // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to |
| // allocate an object here. |
| PackageDexOptimizer pdo = options.isForce() |
| ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPm.mPackageDexOptimizer) |
| : mPm.mPackageDexOptimizer; |
| |
| // Dexopt all dependencies first. Note: we ignore the return value and march on |
| // on errors. |
| // Note that we are going to call performDexOpt on those libraries as many times as |
| // they are referenced in packages. When we do a batch of performDexOpt (for example |
| // at boot, or background job), the passed 'targetCompilerFilter' stays the same, |
| // and the first package that uses the library will dexopt it. The |
| // others will see that the compiled code for the library is up to date. |
| Collection<SharedLibraryInfo> deps = SharedLibraryUtils.findSharedLibraries(pkgSetting); |
| final String[] instructionSets = getAppDexInstructionSets( |
| AndroidPackageUtils.getPrimaryCpuAbi(p, pkgSetting), |
| AndroidPackageUtils.getSecondaryCpuAbi(p, pkgSetting)); |
| if (!deps.isEmpty()) { |
| DexoptOptions libraryOptions = new DexoptOptions(options.getPackageName(), |
| options.getCompilationReason(), options.getCompilerFilter(), |
| options.getSplitName(), |
| options.getFlags() | DexoptOptions.DEXOPT_AS_SHARED_LIBRARY); |
| for (SharedLibraryInfo info : deps) { |
| AndroidPackage depPackage = null; |
| PackageSetting depPackageSetting = null; |
| synchronized (mPm.mLock) { |
| depPackage = mPm.mPackages.get(info.getPackageName()); |
| depPackageSetting = mPm.mSettings.getPackageLPr(info.getPackageName()); |
| } |
| if (depPackage != null && depPackageSetting != null) { |
| // TODO: Analyze and investigate if we (should) profile libraries. |
| pdo.performDexOpt(depPackage, depPackageSetting, instructionSets, |
| mPm.getOrCreateCompilerPackageStats(depPackage), |
| mPm.getDexManager().getPackageUseInfoOrDefault( |
| depPackage.getPackageName()), libraryOptions); |
| } else { |
| // TODO(ngeoffray): Support dexopting system shared libraries. |
| } |
| } |
| } |
| |
| return pdo.performDexOpt(p, pkgSetting, instructionSets, |
| mPm.getOrCreateCompilerPackageStats(p), |
| mPm.getDexManager().getPackageUseInfoOrDefault(p.getPackageName()), options); |
| } |
| |
| public void forceDexOpt(String packageName) { |
| PackageManagerServiceUtils.enforceSystemOrRoot("forceDexOpt"); |
| |
| AndroidPackage pkg; |
| PackageSetting pkgSetting; |
| synchronized (mPm.mLock) { |
| pkg = mPm.mPackages.get(packageName); |
| pkgSetting = mPm.mSettings.getPackageLPr(packageName); |
| if (pkg == null || pkgSetting == null) { |
| throw new IllegalArgumentException("Unknown package: " + packageName); |
| } |
| } |
| |
| synchronized (mPm.mInstallLock) { |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); |
| |
| // Whoever is calling forceDexOpt wants a compiled package. |
| // Don't use profiles since that may cause compilation to be skipped. |
| final int res = performDexOptInternalWithDependenciesLI(pkg, pkgSetting, |
| new DexoptOptions(packageName, |
| getDefaultCompilerFilter(), |
| DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE)); |
| |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) { |
| throw new IllegalStateException("Failed to dexopt: " + res); |
| } |
| } |
| } |
| |
| public boolean performDexOptMode(String packageName, |
| boolean checkProfiles, String targetCompilerFilter, boolean force, |
| boolean bootComplete, String splitName) { |
| PackageManagerServiceUtils.enforceSystemOrRootOrShell("performDexOptMode"); |
| |
| int flags = (checkProfiles ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES : 0) |
| | (force ? DexoptOptions.DEXOPT_FORCE : 0) |
| | (bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0); |
| return performDexOpt(new DexoptOptions(packageName, REASON_CMDLINE, |
| targetCompilerFilter, splitName, flags)); |
| } |
| |
| public boolean performDexOptSecondary(String packageName, String compilerFilter, |
| boolean force) { |
| int flags = DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
| | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
| | DexoptOptions.DEXOPT_BOOT_COMPLETE |
| | (force ? DexoptOptions.DEXOPT_FORCE : 0); |
| return performDexOpt(new DexoptOptions(packageName, compilerFilter, flags)); |
| } |
| |
| // Sort apps by importance for dexopt ordering. Important apps are given |
| // more priority in case the device runs out of space. |
| public static List<PackageStateInternal> getPackagesForDexopt( |
| Collection<? extends PackageStateInternal> packages, |
| PackageManagerService packageManagerService) { |
| return getPackagesForDexopt(packages, packageManagerService, DEBUG_DEXOPT); |
| } |
| |
| public static List<PackageStateInternal> getPackagesForDexopt( |
| Collection<? extends PackageStateInternal> pkgSettings, |
| PackageManagerService packageManagerService, |
| boolean debug) { |
| List<PackageStateInternal> result = new LinkedList<>(); |
| ArrayList<PackageStateInternal> remainingPkgSettings = new ArrayList<>(pkgSettings); |
| |
| // First, remove all settings without available packages |
| remainingPkgSettings.removeIf(REMOVE_IF_NULL_PKG); |
| |
| ArrayList<PackageStateInternal> sortTemp = new ArrayList<>(remainingPkgSettings.size()); |
| |
| // Give priority to core apps. |
| applyPackageFilter(pkgSetting -> pkgSetting.getPkg().isCoreApp(), result, |
| remainingPkgSettings, sortTemp, packageManagerService); |
| |
| // Give priority to system apps that listen for pre boot complete. |
| Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); |
| final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM); |
| applyPackageFilter(pkgSetting -> pkgNames.contains(pkgSetting.getPackageName()), result, |
| remainingPkgSettings, sortTemp, packageManagerService); |
| |
| // Give priority to apps used by other apps. |
| DexManager dexManager = packageManagerService.getDexManager(); |
| applyPackageFilter(pkgSetting -> |
| dexManager.getPackageUseInfoOrDefault(pkgSetting.getPackageName()) |
| .isAnyCodePathUsedByOtherApps(), |
| result, remainingPkgSettings, sortTemp, packageManagerService); |
| |
| // Filter out packages that aren't recently used, add all remaining apps. |
| // TODO: add a property to control this? |
| Predicate<PackageStateInternal> remainingPredicate; |
| if (!remainingPkgSettings.isEmpty() |
| && packageManagerService.isHistoricalPackageUsageAvailable()) { |
| if (debug) { |
| Log.i(TAG, "Looking at historical package use"); |
| } |
| // Get the package that was used last. |
| PackageStateInternal lastUsed = Collections.max(remainingPkgSettings, |
| Comparator.comparingLong( |
| pkgSetting -> pkgSetting.getTransientState() |
| .getLatestForegroundPackageUseTimeInMills())); |
| if (debug) { |
| Log.i(TAG, "Taking package " + lastUsed.getPackageName() |
| + " as reference in time use"); |
| } |
| long estimatedPreviousSystemUseTime = lastUsed.getTransientState() |
| .getLatestForegroundPackageUseTimeInMills(); |
| // Be defensive if for some reason package usage has bogus data. |
| if (estimatedPreviousSystemUseTime != 0) { |
| final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS; |
| remainingPredicate = pkgSetting -> pkgSetting.getTransientState() |
| .getLatestForegroundPackageUseTimeInMills() >= cutoffTime; |
| } else { |
| // No meaningful historical info. Take all. |
| remainingPredicate = pkgSetting -> true; |
| } |
| sortPackagesByUsageDate(remainingPkgSettings, packageManagerService); |
| } else { |
| // No historical info. Take all. |
| remainingPredicate = pkgSetting -> true; |
| } |
| applyPackageFilter(remainingPredicate, result, remainingPkgSettings, sortTemp, |
| packageManagerService); |
| |
| if (debug) { |
| Log.i(TAG, "Packages to be dexopted: " + packagesToString(result)); |
| Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgSettings)); |
| } |
| |
| return result; |
| } |
| |
| // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the |
| // package will be removed from {@code packages} and added to {@code result} with its |
| // dependencies. If usage data is available, the positive packages will be sorted by usage |
| // data (with {@code sortTemp} as temporary storage). |
| private static void applyPackageFilter( |
| Predicate<PackageStateInternal> filter, |
| Collection<PackageStateInternal> result, |
| Collection<PackageStateInternal> packages, |
| @NonNull List<PackageStateInternal> sortTemp, |
| PackageManagerService packageManagerService) { |
| for (PackageStateInternal pkgSetting : packages) { |
| if (filter.test(pkgSetting)) { |
| sortTemp.add(pkgSetting); |
| } |
| } |
| |
| sortPackagesByUsageDate(sortTemp, packageManagerService); |
| packages.removeAll(sortTemp); |
| |
| for (PackageStateInternal pkgSetting : sortTemp) { |
| result.add(pkgSetting); |
| |
| List<PackageStateInternal> deps = |
| packageManagerService.findSharedNonSystemLibraries(pkgSetting); |
| if (!deps.isEmpty()) { |
| deps.removeAll(result); |
| result.addAll(deps); |
| packages.removeAll(deps); |
| } |
| } |
| |
| sortTemp.clear(); |
| } |
| |
| // Sort a list of apps by their last usage, most recently used apps first. The order of |
| // packages without usage data is undefined (but they will be sorted after the packages |
| // that do have usage data). |
| private static void sortPackagesByUsageDate(List<PackageStateInternal> pkgSettings, |
| PackageManagerService packageManagerService) { |
| if (!packageManagerService.isHistoricalPackageUsageAvailable()) { |
| return; |
| } |
| |
| Collections.sort(pkgSettings, (pkgSetting1, pkgSetting2) -> |
| Long.compare( |
| pkgSetting2.getTransientState().getLatestForegroundPackageUseTimeInMills(), |
| pkgSetting1.getTransientState().getLatestForegroundPackageUseTimeInMills()) |
| ); |
| } |
| |
| private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) { |
| List<ResolveInfo> ris = null; |
| try { |
| ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId) |
| .getList(); |
| } catch (RemoteException e) { |
| } |
| ArraySet<String> pkgNames = new ArraySet<String>(); |
| if (ris != null) { |
| for (ResolveInfo ri : ris) { |
| pkgNames.add(ri.activityInfo.packageName); |
| } |
| } |
| return pkgNames; |
| } |
| |
| public static String packagesToString(List<PackageStateInternal> pkgSettings) { |
| StringBuilder sb = new StringBuilder(); |
| for (int index = 0; index < pkgSettings.size(); index++) { |
| if (sb.length() > 0) { |
| sb.append(", "); |
| } |
| sb.append(pkgSettings.get(index).getPackageName()); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Requests that files preopted on a secondary system partition be copied to the data partition |
| * if possible. Note that the actual copying of the files is accomplished by init for security |
| * reasons. This simply requests that the copy takes place and awaits confirmation of its |
| * completion. See platform/system/extras/cppreopt/ for the implementation of the actual copy. |
| */ |
| public static void requestCopyPreoptedFiles() { |
| final int WAIT_TIME_MS = 100; |
| final String CP_PREOPT_PROPERTY = "sys.cppreopt"; |
| if (SystemProperties.getInt("ro.cp_system_other_odex", 0) == 1) { |
| SystemProperties.set(CP_PREOPT_PROPERTY, "requested"); |
| // We will wait for up to 100 seconds. |
| final long timeStart = SystemClock.uptimeMillis(); |
| final long timeEnd = timeStart + 100 * 1000; |
| long timeNow = timeStart; |
| while (!SystemProperties.get(CP_PREOPT_PROPERTY).equals("finished")) { |
| try { |
| Thread.sleep(WAIT_TIME_MS); |
| } catch (InterruptedException e) { |
| // Do nothing |
| } |
| timeNow = SystemClock.uptimeMillis(); |
| if (timeNow > timeEnd) { |
| SystemProperties.set(CP_PREOPT_PROPERTY, "timed-out"); |
| Slog.wtf(TAG, "cppreopt did not finish!"); |
| break; |
| } |
| } |
| |
| Slog.i(TAG, "cppreopts took " + (timeNow - timeStart) + " ms"); |
| } |
| } |
| |
| /*package*/ void controlDexOptBlocking(boolean block) { |
| mPm.mPackageDexOptimizer.controlDexOptBlocking(block); |
| } |
| } |