OtaDexoptService.java revision 14186731f98d326239922d093371d1e63b8c22a5
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 dexopt invocations for all work. 60 */ 61 private List<String> mDexoptCommands; 62 63 private int completeSize; 64 65 public OtaDexoptService(Context context, PackageManagerService packageManagerService) { 66 this.mContext = context; 67 this.mPackageManagerService = packageManagerService; 68 69 // Now it's time to check whether we need to move any A/B artifacts. 70 moveAbArtifacts(packageManagerService.mInstaller); 71 } 72 73 public static OtaDexoptService main(Context context, 74 PackageManagerService packageManagerService) { 75 OtaDexoptService ota = new OtaDexoptService(context, packageManagerService); 76 ServiceManager.addService("otadexopt", ota); 77 78 return ota; 79 } 80 81 @Override 82 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 83 String[] args, ResultReceiver resultReceiver) throws RemoteException { 84 (new OtaDexoptShellCommand(this)).exec( 85 this, in, out, err, args, resultReceiver); 86 } 87 88 @Override 89 public synchronized void prepare() throws RemoteException { 90 if (mDexoptCommands != null) { 91 throw new IllegalStateException("already called prepare()"); 92 } 93 synchronized (mPackageManagerService.mPackages) { 94 // Important: the packages we need to run with ab-ota compiler-reason. 95 List<PackageParser.Package> important = PackageManagerServiceUtils.getPackagesForDexopt( 96 mPackageManagerService.mPackages.values(), mPackageManagerService); 97 // Others: we should optimize this with the (first-)boot compiler-reason. 98 List<PackageParser.Package> others = 99 new ArrayList<>(mPackageManagerService.mPackages.values()); 100 others.removeAll(important); 101 102 // Pre-size the array list by over-allocating by a factor of 1.5. 103 mDexoptCommands = new ArrayList<>(3 * mPackageManagerService.mPackages.size() / 2); 104 105 for (PackageParser.Package p : important) { 106 // Make sure that core apps are optimized according to their own "reason". 107 // If the core apps are not preopted in the B OTA, and REASON_AB_OTA is not speed 108 // (by default is speed-profile) they will be interepreted/JITed. This in itself is 109 // not a problem as we will end up doing profile guided compilation. However, some 110 // core apps may be loaded by system server which doesn't JIT and we need to make 111 // sure we don't interpret-only 112 int compilationReason = p.coreApp 113 ? PackageManagerService.REASON_CORE_APP 114 : PackageManagerService.REASON_AB_OTA; 115 mDexoptCommands.addAll(generatePackageDexopts(p, compilationReason)); 116 } 117 for (PackageParser.Package p : others) { 118 // We assume here that there are no core apps left. 119 if (p.coreApp) { 120 throw new IllegalStateException("Found a core app that's not important"); 121 } 122 mDexoptCommands.addAll( 123 generatePackageDexopts(p, PackageManagerService.REASON_FIRST_BOOT)); 124 } 125 } 126 completeSize = mDexoptCommands.size(); 127 } 128 129 @Override 130 public synchronized void cleanup() throws RemoteException { 131 if (DEBUG_DEXOPT) { 132 Log.i(TAG, "Cleaning up OTA Dexopt state."); 133 } 134 mDexoptCommands = null; 135 } 136 137 @Override 138 public synchronized boolean isDone() throws RemoteException { 139 if (mDexoptCommands == null) { 140 throw new IllegalStateException("done() called before prepare()"); 141 } 142 143 return mDexoptCommands.isEmpty(); 144 } 145 146 @Override 147 public synchronized float getProgress() throws RemoteException { 148 // Approximate the progress by the amount of already completed commands. 149 if (completeSize == 0) { 150 return 1f; 151 } 152 int commandsLeft = mDexoptCommands.size(); 153 return (completeSize - commandsLeft) / ((float)completeSize); 154 } 155 156 @Override 157 public synchronized String nextDexoptCommand() throws RemoteException { 158 if (mDexoptCommands == null) { 159 throw new IllegalStateException("dexoptNextPackage() called before prepare()"); 160 } 161 162 if (mDexoptCommands.isEmpty()) { 163 return "(all done)"; 164 } 165 166 String next = mDexoptCommands.remove(0); 167 168 if (IsFreeSpaceAvailable()) { 169 return next; 170 } else { 171 mDexoptCommands.clear(); 172 return "(no free space)"; 173 } 174 } 175 176 /** 177 * Check for low space. Returns true if there's space left. 178 */ 179 private boolean IsFreeSpaceAvailable() { 180 // TODO: If apps are not installed in the internal /data partition, we should compare 181 // against that storage's free capacity. 182 File dataDir = Environment.getDataDirectory(); 183 @SuppressWarnings("deprecation") 184 long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir); 185 if (lowThreshold == 0) { 186 throw new IllegalStateException("Invalid low memory threshold"); 187 } 188 long usableSpace = dataDir.getUsableSpace(); 189 return (usableSpace >= lowThreshold); 190 } 191 192 /** 193 * Generate all dexopt commands for the given package. 194 */ 195 private synchronized List<String> generatePackageDexopts(PackageParser.Package pkg, 196 int compilationReason) { 197 // Use our custom connection that just collects the commands. 198 RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection(); 199 Installer collectingInstaller = new Installer(mContext, collectingConnection); 200 201 // Use the package manager install and install lock here for the OTA dex optimizer. 202 PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer( 203 collectingInstaller, mPackageManagerService.mInstallLock, mContext); 204 205 optimizer.performDexOpt(pkg, pkg.usesLibraryFiles, 206 null /* ISAs */, false /* checkProfiles */, 207 getCompilerFilterForReason(compilationReason), 208 null /* CompilerStats.PackageStats */); 209 210 return collectingConnection.commands; 211 } 212 213 @Override 214 public synchronized void dexoptNextPackage() throws RemoteException { 215 throw new UnsupportedOperationException(); 216 } 217 218 private void moveAbArtifacts(Installer installer) { 219 if (mDexoptCommands != null) { 220 throw new IllegalStateException("Should not be ota-dexopting when trying to move."); 221 } 222 223 // Look into all packages. 224 Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages(); 225 for (PackageParser.Package pkg : pkgs) { 226 if (pkg == null) { 227 continue; 228 } 229 230 // Does the package have code? If not, there won't be any artifacts. 231 if (!PackageDexOptimizer.canOptimizePackage(pkg)) { 232 continue; 233 } 234 if (pkg.codePath == null) { 235 Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath"); 236 continue; 237 } 238 239 // If the path is in /system or /vendor, ignore. It will have been ota-dexopted into 240 // /data/ota and moved into the dalvik-cache already. 241 if (pkg.codePath.startsWith("/system") || pkg.codePath.startsWith("/vendor")) { 242 continue; 243 } 244 245 final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo); 246 final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly(); 247 final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); 248 for (String dexCodeInstructionSet : dexCodeInstructionSets) { 249 for (String path : paths) { 250 String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)). 251 getAbsolutePath(); 252 253 // TODO: Check first whether there is an artifact, to save the roundtrip time. 254 255 try { 256 installer.moveAb(path, dexCodeInstructionSet, oatDir); 257 } catch (InstallerException e) { 258 } 259 } 260 } 261 } 262 } 263 264 private static class OTADexoptPackageDexOptimizer extends 265 PackageDexOptimizer.ForcedUpdatePackageDexOptimizer { 266 267 public OTADexoptPackageDexOptimizer(Installer installer, Object installLock, 268 Context context) { 269 super(installer, installLock, context, "*otadexopt*"); 270 } 271 272 @Override 273 protected int adjustDexoptFlags(int dexoptFlags) { 274 // Add the OTA flag. 275 return dexoptFlags | DEXOPT_OTA; 276 } 277 278 } 279 280 private static class RecordingInstallerConnection extends InstallerConnection { 281 public List<String> commands = new ArrayList<String>(1); 282 283 @Override 284 public void setWarnIfHeld(Object warnIfHeld) { 285 throw new IllegalStateException("Should not reach here"); 286 } 287 288 @Override 289 public synchronized String transact(String cmd) { 290 commands.add(cmd); 291 return "0"; 292 } 293 294 @Override 295 public boolean mergeProfiles(int uid, String pkgName) throws InstallerException { 296 throw new IllegalStateException("Should not reach here"); 297 } 298 299 @Override 300 public boolean dumpProfiles(String gid, String packageName, String codePaths) 301 throws InstallerException { 302 throw new IllegalStateException("Should not reach here"); 303 } 304 305 @Override 306 public void disconnect() { 307 throw new IllegalStateException("Should not reach here"); 308 } 309 310 @Override 311 public void waitForConnection() { 312 throw new IllegalStateException("Should not reach here"); 313 } 314 } 315} 316