OtaDexoptService.java revision a89087542f774c585b6a6ec535fc294721710521
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.pm; 18 19import android.app.AppGlobals; 20import android.content.Context; 21import android.content.Intent; 22import android.content.pm.IOtaDexopt; 23import android.content.pm.PackageParser; 24import android.content.pm.PackageParser.Package; 25import android.content.pm.ResolveInfo; 26import android.os.Environment; 27import android.os.RemoteException; 28import android.os.ResultReceiver; 29import android.os.ServiceManager; 30import android.os.UserHandle; 31import android.os.storage.StorageManager; 32import android.util.ArraySet; 33import android.util.Log; 34 35import dalvik.system.DexFile; 36 37import java.io.File; 38import java.io.FileDescriptor; 39import java.util.ArrayList; 40import java.util.Collection; 41import java.util.Date; 42import java.util.HashSet; 43import java.util.Iterator; 44import java.util.LinkedList; 45import java.util.List; 46import java.util.Set; 47 48import static com.android.server.pm.Installer.DEXOPT_OTA; 49 50/** 51 * A service for A/B OTA dexopting. 52 * 53 * {@hide} 54 */ 55public class OtaDexoptService extends IOtaDexopt.Stub { 56 private final static String TAG = "OTADexopt"; 57 private final static boolean DEBUG_DEXOPT = true; 58 // Apps used in the last 7 days. 59 private final static long DEXOPT_LRU_THRESHOLD_IN_MINUTES = 7 * 24 * 60; 60 61 private final Context mContext; 62 private final PackageDexOptimizer mPackageDexOptimizer; 63 private final PackageManagerService mPackageManagerService; 64 65 // TODO: Evaluate the need for WeakReferences here. 66 private List<PackageParser.Package> mDexoptPackages; 67 68 public OtaDexoptService(Context context, PackageManagerService packageManagerService) { 69 this.mContext = context; 70 this.mPackageManagerService = packageManagerService; 71 72 // Use the package manager install and install lock here for the OTA dex optimizer. 73 mPackageDexOptimizer = new OTADexoptPackageDexOptimizer(packageManagerService.mInstaller, 74 packageManagerService.mInstallLock, context); 75 } 76 77 public static OtaDexoptService main(Context context, 78 PackageManagerService packageManagerService) { 79 OtaDexoptService ota = new OtaDexoptService(context, packageManagerService); 80 ServiceManager.addService("otadexopt", ota); 81 82 return ota; 83 } 84 85 @Override 86 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 87 String[] args, ResultReceiver resultReceiver) throws RemoteException { 88 (new OtaDexoptShellCommand(this)).exec( 89 this, in, out, err, args, resultReceiver); 90 } 91 92 @Override 93 public synchronized void prepare() throws RemoteException { 94 if (mDexoptPackages != null) { 95 throw new IllegalStateException("already called prepare()"); 96 } 97 98 mDexoptPackages = new LinkedList<>(); 99 100 ArrayList<PackageParser.Package> pkgs; 101 synchronized (mPackageManagerService.mPackages) { 102 pkgs = new ArrayList<PackageParser.Package>(mPackageManagerService.mPackages.values()); 103 } 104 105 // Sort apps by importance for dexopt ordering. Important apps are given more priority 106 // in case the device runs out of space. 107 108 // Give priority to core apps. 109 for (PackageParser.Package pkg : pkgs) { 110 if (pkg.coreApp) { 111 if (DEBUG_DEXOPT) { 112 Log.i(TAG, "Adding core app " + mDexoptPackages.size() + ": " + pkg.packageName); 113 } 114 mDexoptPackages.add(pkg); 115 } 116 } 117 pkgs.removeAll(mDexoptPackages); 118 119 // Give priority to system apps that listen for pre boot complete. 120 Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); 121 ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM); 122 for (PackageParser.Package pkg : pkgs) { 123 if (pkgNames.contains(pkg.packageName)) { 124 if (DEBUG_DEXOPT) { 125 Log.i(TAG, "Adding pre boot system app " + mDexoptPackages.size() + ": " + 126 pkg.packageName); 127 } 128 mDexoptPackages.add(pkg); 129 } 130 } 131 pkgs.removeAll(mDexoptPackages); 132 133 // Filter out packages that aren't recently used, add all remaining apps. 134 // TODO: add a property to control this? 135 if (mPackageManagerService.isHistoricalPackageUsageAvailable()) { 136 filterRecentlyUsedApps(pkgs, DEXOPT_LRU_THRESHOLD_IN_MINUTES * 60 * 1000); 137 } 138 mDexoptPackages.addAll(pkgs); 139 140 // Now go ahead and also add the libraries required for these packages. 141 // TODO: Think about interleaving things. 142 Set<PackageParser.Package> dependencies = new HashSet<>(); 143 for (PackageParser.Package p : mDexoptPackages) { 144 dependencies.addAll(mPackageManagerService.findSharedNonSystemLibraries(p)); 145 } 146 if (!dependencies.isEmpty()) { 147 dependencies.removeAll(mDexoptPackages); 148 } 149 mDexoptPackages.addAll(dependencies); 150 151 if (DEBUG_DEXOPT) { 152 StringBuilder sb = new StringBuilder(); 153 for (PackageParser.Package pkg : mDexoptPackages) { 154 if (sb.length() > 0) { 155 sb.append(", "); 156 } 157 sb.append(pkg.packageName); 158 } 159 Log.i(TAG, "Packages to be optimized: " + sb.toString()); 160 } 161 } 162 163 @Override 164 public synchronized void cleanup() throws RemoteException { 165 if (DEBUG_DEXOPT) { 166 Log.i(TAG, "Cleaning up OTA Dexopt state."); 167 } 168 mDexoptPackages = null; 169 } 170 171 @Override 172 public synchronized boolean isDone() throws RemoteException { 173 if (mDexoptPackages == null) { 174 throw new IllegalStateException("done() called before prepare()"); 175 } 176 177 return mDexoptPackages.isEmpty(); 178 } 179 180 @Override 181 public synchronized void dexoptNextPackage() throws RemoteException { 182 if (mDexoptPackages == null) { 183 throw new IllegalStateException("dexoptNextPackage() called before prepare()"); 184 } 185 if (mDexoptPackages.isEmpty()) { 186 // Tolerate repeated calls. 187 return; 188 } 189 190 PackageParser.Package nextPackage = mDexoptPackages.remove(0); 191 192 if (DEBUG_DEXOPT) { 193 Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt."); 194 } 195 196 // Check for low space. 197 // TODO: If apps are not installed in the internal /data partition, we should compare 198 // against that storage's free capacity. 199 File dataDir = Environment.getDataDirectory(); 200 long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir); 201 if (lowThreshold == 0) { 202 throw new IllegalStateException("Invalid low memory threshold"); 203 } 204 long usableSpace = dataDir.getUsableSpace(); 205 if (usableSpace < lowThreshold) { 206 Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " + 207 usableSpace); 208 return; 209 } 210 211 mPackageDexOptimizer.performDexOpt(nextPackage, null /* ISAs */, false /* useProfiles */, 212 false /* extractOnly */); 213 } 214 215 private ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) { 216 List<ResolveInfo> ris = null; 217 try { 218 ris = AppGlobals.getPackageManager().queryIntentReceivers( 219 intent, null, 0, userId); 220 } catch (RemoteException e) { 221 } 222 ArraySet<String> pkgNames = new ArraySet<String>(ris == null ? 0 : ris.size()); 223 if (ris != null) { 224 for (ResolveInfo ri : ris) { 225 pkgNames.add(ri.activityInfo.packageName); 226 } 227 } 228 return pkgNames; 229 } 230 231 private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs, 232 long dexOptLRUThresholdInMills) { 233 // Filter out packages that aren't recently used. 234 int total = pkgs.size(); 235 int skipped = 0; 236 long now = System.currentTimeMillis(); 237 for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) { 238 PackageParser.Package pkg = i.next(); 239 long then = pkg.mLastPackageUsageTimeInMills; 240 if (then + dexOptLRUThresholdInMills < now) { 241 if (DEBUG_DEXOPT) { 242 Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " + 243 ((then == 0) ? "never" : new Date(then))); 244 } 245 i.remove(); 246 skipped++; 247 } 248 } 249 if (DEBUG_DEXOPT) { 250 Log.i(TAG, "Skipped optimizing " + skipped + " of " + total); 251 } 252 } 253 254 private static class OTADexoptPackageDexOptimizer extends 255 PackageDexOptimizer.ForcedUpdatePackageDexOptimizer { 256 257 public OTADexoptPackageDexOptimizer(Installer installer, Object installLock, 258 Context context) { 259 super(installer, installLock, context, "*otadexopt*"); 260 } 261 262 @Override 263 protected int adjustDexoptFlags(int dexoptFlags) { 264 // Add the OTA flag. 265 return dexoptFlags | DEXOPT_OTA; 266 } 267 268 @Override 269 protected void recordSuccessfulDexopt(Package pkg, String instructionSet) { 270 // Never record the dexopt, as it's in the B partition. 271 } 272 273 } 274} 275