OtaDexoptService.java revision d15300cf38ae3840309dc6cb6c093b17713fc277
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 static com.android.server.pm.Installer.DEXOPT_OTA; 20import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; 21import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; 22import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason; 23 24import android.content.Context; 25import android.content.pm.IOtaDexopt; 26import android.content.pm.PackageParser; 27import android.os.Environment; 28import android.os.RemoteException; 29import android.os.ResultReceiver; 30import android.os.ServiceManager; 31import android.os.storage.StorageManager; 32import android.util.Log; 33import android.util.Slog; 34 35import com.android.internal.os.InstallerConnection; 36import com.android.internal.os.InstallerConnection.InstallerException; 37 38import java.io.File; 39import java.io.FileDescriptor; 40import java.util.ArrayList; 41import java.util.Collection; 42import java.util.List; 43 44/** 45 * A service for A/B OTA dexopting. 46 * 47 * {@hide} 48 */ 49public class OtaDexoptService extends IOtaDexopt.Stub { 50 private final static String TAG = "OTADexopt"; 51 private final static boolean DEBUG_DEXOPT = true; 52 53 private final Context mContext; 54 private final PackageManagerService mPackageManagerService; 55 56 // TODO: Evaluate the need for WeakReferences here. 57 58 /** 59 * The list of packages to dexopt. 60 */ 61 private List<PackageParser.Package> mDexoptPackages; 62 63 /** 64 * The list of dexopt invocations for the current package (which will no longer be in 65 * mDexoptPackages). This can be more than one as a package may have multiple code paths, 66 * e.g., in the split-APK case. 67 */ 68 private List<String> mCommandsForCurrentPackage; 69 70 private int completeSize; 71 72 public OtaDexoptService(Context context, PackageManagerService packageManagerService) { 73 this.mContext = context; 74 this.mPackageManagerService = packageManagerService; 75 76 // Now it's time to check whether we need to move any A/B artifacts. 77 moveAbArtifacts(packageManagerService.mInstaller); 78 } 79 80 public static OtaDexoptService main(Context context, 81 PackageManagerService packageManagerService) { 82 OtaDexoptService ota = new OtaDexoptService(context, packageManagerService); 83 ServiceManager.addService("otadexopt", ota); 84 85 return ota; 86 } 87 88 @Override 89 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 90 String[] args, ResultReceiver resultReceiver) throws RemoteException { 91 (new OtaDexoptShellCommand(this)).exec( 92 this, in, out, err, args, resultReceiver); 93 } 94 95 @Override 96 public synchronized void prepare() throws RemoteException { 97 if (mDexoptPackages != null) { 98 throw new IllegalStateException("already called prepare()"); 99 } 100 synchronized (mPackageManagerService.mPackages) { 101 mDexoptPackages = PackageManagerServiceUtils.getPackagesForDexopt( 102 mPackageManagerService.mPackages.values(), mPackageManagerService); 103 } 104 completeSize = mDexoptPackages.size(); 105 mCommandsForCurrentPackage = null; 106 } 107 108 @Override 109 public synchronized void cleanup() throws RemoteException { 110 if (DEBUG_DEXOPT) { 111 Log.i(TAG, "Cleaning up OTA Dexopt state."); 112 } 113 mDexoptPackages = null; 114 mCommandsForCurrentPackage = null; 115 } 116 117 @Override 118 public synchronized boolean isDone() throws RemoteException { 119 if (mDexoptPackages == null) { 120 throw new IllegalStateException("done() called before prepare()"); 121 } 122 123 return mDexoptPackages.isEmpty() && (mCommandsForCurrentPackage == null); 124 } 125 126 @Override 127 public synchronized float getProgress() throws RemoteException { 128 // We approximate by number of packages here. We could track all compiles, if we 129 // generated them ahead of time. Right now we're trying to conserve memory. 130 if (completeSize == 0) { 131 return 1f; 132 } 133 int packagesLeft = mDexoptPackages.size() + (mCommandsForCurrentPackage != null ? 1 : 0); 134 return (completeSize - packagesLeft) / ((float)completeSize); 135 } 136 137 /** 138 * Return the next dexopt command for the current package. Enforces the invariant 139 */ 140 private String getNextPackageDexopt() { 141 if (mCommandsForCurrentPackage != null) { 142 String next = mCommandsForCurrentPackage.remove(0); 143 if (mCommandsForCurrentPackage.isEmpty()) { 144 mCommandsForCurrentPackage = null; 145 } 146 return next; 147 } 148 return null; 149 } 150 151 @Override 152 public synchronized String nextDexoptCommand() throws RemoteException { 153 if (mDexoptPackages == null) { 154 throw new IllegalStateException("dexoptNextPackage() called before prepare()"); 155 } 156 157 // Get the next command. 158 for (;;) { 159 // Check whether there's one for the current package. 160 String next = getNextPackageDexopt(); 161 if (next != null) { 162 return next; 163 } 164 165 // Move to the next package, if possible. 166 if (mDexoptPackages.isEmpty()) { 167 return "Nothing to do"; 168 } 169 170 PackageParser.Package nextPackage = mDexoptPackages.remove(0); 171 172 if (DEBUG_DEXOPT) { 173 Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt."); 174 } 175 176 // Generate the next mPackageDexopts state. Ignore errors, this loop is strongly 177 // monotonically increasing, anyways. 178 generatePackageDexopts(nextPackage); 179 180 // Invariant check: mPackageDexopts is null or not empty. 181 if (mCommandsForCurrentPackage != null && mCommandsForCurrentPackage.isEmpty()) { 182 cleanup(); 183 throw new IllegalStateException("mPackageDexopts empty for " + nextPackage); 184 } 185 } 186 } 187 188 /** 189 * Generate all dexopt commands for the given package and place them into mPackageDexopts. 190 * Returns true on success, false in an error situation like low disk space. 191 */ 192 private synchronized boolean generatePackageDexopts(PackageParser.Package nextPackage) { 193 // Check for low space. 194 // TODO: If apps are not installed in the internal /data partition, we should compare 195 // against that storage's free capacity. 196 File dataDir = Environment.getDataDirectory(); 197 @SuppressWarnings("deprecation") 198 long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir); 199 if (lowThreshold == 0) { 200 throw new IllegalStateException("Invalid low memory threshold"); 201 } 202 long usableSpace = dataDir.getUsableSpace(); 203 if (usableSpace < lowThreshold) { 204 Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " + 205 usableSpace); 206 return false; 207 } 208 209 // Use our custom connection that just collects the commands. 210 RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection(); 211 Installer collectingInstaller = new Installer(mContext, collectingConnection); 212 213 // Use the package manager install and install lock here for the OTA dex optimizer. 214 PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer( 215 collectingInstaller, mPackageManagerService.mInstallLock, mContext); 216 optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, 217 null /* ISAs */, false /* checkProfiles */, 218 getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA)); 219 220 mCommandsForCurrentPackage = collectingConnection.commands; 221 if (mCommandsForCurrentPackage.isEmpty()) { 222 mCommandsForCurrentPackage = null; 223 } 224 225 return true; 226 } 227 228 @Override 229 public synchronized void dexoptNextPackage() throws RemoteException { 230 if (mDexoptPackages == null) { 231 throw new IllegalStateException("dexoptNextPackage() called before prepare()"); 232 } 233 if (mDexoptPackages.isEmpty()) { 234 // Tolerate repeated calls. 235 return; 236 } 237 238 PackageParser.Package nextPackage = mDexoptPackages.remove(0); 239 240 if (DEBUG_DEXOPT) { 241 Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt."); 242 } 243 244 // Check for low space. 245 // TODO: If apps are not installed in the internal /data partition, we should compare 246 // against that storage's free capacity. 247 File dataDir = Environment.getDataDirectory(); 248 @SuppressWarnings("deprecation") 249 long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir); 250 if (lowThreshold == 0) { 251 throw new IllegalStateException("Invalid low memory threshold"); 252 } 253 long usableSpace = dataDir.getUsableSpace(); 254 if (usableSpace < lowThreshold) { 255 Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " + 256 usableSpace); 257 return; 258 } 259 260 PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer( 261 mPackageManagerService.mInstaller, mPackageManagerService.mInstallLock, mContext); 262 optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, null /* ISAs */, 263 false /* checkProfiles */, 264 getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA)); 265 } 266 267 private void moveAbArtifacts(Installer installer) { 268 if (mDexoptPackages != null) { 269 throw new IllegalStateException("Should not be ota-dexopting when trying to move."); 270 } 271 272 // Look into all packages. 273 Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages(); 274 for (PackageParser.Package pkg : pkgs) { 275 if (pkg == null) { 276 continue; 277 } 278 279 // Does the package have code? If not, there won't be any artifacts. 280 if (!PackageDexOptimizer.canOptimizePackage(pkg)) { 281 continue; 282 } 283 if (pkg.codePath == null) { 284 Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath"); 285 continue; 286 } 287 288 // If the path is in /system or /vendor, ignore. It will have been ota-dexopted into 289 // /data/ota and moved into the dalvik-cache already. 290 if (pkg.codePath.startsWith("/system") || pkg.codePath.startsWith("/vendor")) { 291 continue; 292 } 293 294 final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo); 295 final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly(); 296 final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); 297 for (String dexCodeInstructionSet : dexCodeInstructionSets) { 298 for (String path : paths) { 299 String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)). 300 getAbsolutePath(); 301 302 // TODO: Check first whether there is an artifact, to save the roundtrip time. 303 304 try { 305 installer.moveAb(path, dexCodeInstructionSet, oatDir); 306 } catch (InstallerException e) { 307 } 308 } 309 } 310 } 311 } 312 313 private static class OTADexoptPackageDexOptimizer extends 314 PackageDexOptimizer.ForcedUpdatePackageDexOptimizer { 315 316 public OTADexoptPackageDexOptimizer(Installer installer, Object installLock, 317 Context context) { 318 super(installer, installLock, context, "*otadexopt*"); 319 } 320 321 @Override 322 protected int adjustDexoptFlags(int dexoptFlags) { 323 // Add the OTA flag. 324 return dexoptFlags | DEXOPT_OTA; 325 } 326 327 } 328 329 private static class RecordingInstallerConnection extends InstallerConnection { 330 public List<String> commands = new ArrayList<String>(1); 331 332 @Override 333 public void setWarnIfHeld(Object warnIfHeld) { 334 throw new IllegalStateException("Should not reach here"); 335 } 336 337 @Override 338 public synchronized String transact(String cmd) { 339 commands.add(cmd); 340 return "0"; 341 } 342 343 @Override 344 public boolean mergeProfiles(int uid, String pkgName) throws InstallerException { 345 throw new IllegalStateException("Should not reach here"); 346 } 347 348 @Override 349 public boolean dumpProfiles(String gid, String packageName, String codePaths) 350 throws InstallerException { 351 throw new IllegalStateException("Should not reach here"); 352 } 353 354 @Override 355 public void disconnect() { 356 throw new IllegalStateException("Should not reach here"); 357 } 358 359 @Override 360 public void waitForConnection() { 361 throw new IllegalStateException("Should not reach here"); 362 } 363 } 364} 365