• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.server.pm;
18 
19 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
20 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
21 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
22 
23 import android.annotation.Nullable;
24 import android.content.Context;
25 import android.content.pm.IOtaDexopt;
26 import android.content.pm.PackageManagerInternal;
27 import android.os.Environment;
28 import android.os.RemoteException;
29 import android.os.ResultReceiver;
30 import android.os.ServiceManager;
31 import android.os.ShellCallback;
32 import android.os.storage.StorageManager;
33 import android.util.ArrayMap;
34 import android.util.Log;
35 import android.util.Slog;
36 
37 import com.android.internal.logging.MetricsLogger;
38 import com.android.server.LocalServices;
39 import com.android.server.pm.Installer.InstallerException;
40 import com.android.server.pm.dex.DexoptOptions;
41 import com.android.server.pm.parsing.pkg.AndroidPackage;
42 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
43 import com.android.server.pm.pkg.PackageStateInternal;
44 
45 import java.io.File;
46 import java.io.FileDescriptor;
47 import java.util.ArrayList;
48 import java.util.Collection;
49 import java.util.Collections;
50 import java.util.Comparator;
51 import java.util.List;
52 import java.util.concurrent.TimeUnit;
53 import java.util.function.Predicate;
54 
55 /**
56  * A service for A/B OTA dexopting.
57  *
58  * {@hide}
59  */
60 public class OtaDexoptService extends IOtaDexopt.Stub {
61     private final static String TAG = "OTADexopt";
62     private final static boolean DEBUG_DEXOPT = true;
63 
64     // The amount of "available" (free - low threshold) space necessary at the start of an OTA to
65     // not bulk-delete unused apps' odex files.
66     private final static long BULK_DELETE_THRESHOLD = 1024 * 1024 * 1024;  // 1GB.
67 
68     private final Context mContext;
69     private final PackageManagerService mPackageManagerService;
70     private final MetricsLogger metricsLogger;
71 
72     // TODO: Evaluate the need for WeakReferences here.
73 
74     /**
75      * The list of dexopt invocations for all work.
76      */
77     private List<String> mDexoptCommands;
78 
79     private int completeSize;
80 
81     // MetricsLogger properties.
82 
83     // Space before and after.
84     private long availableSpaceBefore;
85     private long availableSpaceAfterBulkDelete;
86     private long availableSpaceAfterDexopt;
87 
88     // Packages.
89     private int importantPackageCount;
90     private int otherPackageCount;
91 
92     // Number of dexopt commands. This may be different from the count of packages.
93     private int dexoptCommandCountTotal;
94     private int dexoptCommandCountExecuted;
95 
96     // For spent time.
97     private long otaDexoptTimeStart;
98 
99 
OtaDexoptService(Context context, PackageManagerService packageManagerService)100     public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
101         this.mContext = context;
102         this.mPackageManagerService = packageManagerService;
103         metricsLogger = new MetricsLogger();
104     }
105 
main(Context context, PackageManagerService packageManagerService)106     public static OtaDexoptService main(Context context,
107             PackageManagerService packageManagerService) {
108         OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
109         ServiceManager.addService("otadexopt", ota);
110 
111         // Now it's time to check whether we need to move any A/B artifacts.
112         ota.moveAbArtifacts(packageManagerService.mInstaller);
113 
114         return ota;
115     }
116 
117     @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)118     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
119             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
120         (new OtaDexoptShellCommand(this)).exec(
121                 this, in, out, err, args, callback, resultReceiver);
122     }
123 
124     @Override
prepare()125     public synchronized void prepare() throws RemoteException {
126         if (mDexoptCommands != null) {
127             throw new IllegalStateException("already called prepare()");
128         }
129         final List<PackageStateInternal> important;
130         final List<PackageStateInternal> others;
131         Predicate<PackageStateInternal> isPlatformPackage = pkgSetting ->
132                 PLATFORM_PACKAGE_NAME.equals(pkgSetting.getPkg().getPackageName());
133         // Important: the packages we need to run with ab-ota compiler-reason.
134         final Computer snapshot = mPackageManagerService.snapshotComputer();
135         final Collection<? extends PackageStateInternal> allPackageStates =
136                 snapshot.getPackageStates().values();
137         important = DexOptHelper.getPackagesForDexopt(allPackageStates,mPackageManagerService,
138                 DEBUG_DEXOPT);
139         // Remove Platform Package from A/B OTA b/160735835.
140         important.removeIf(isPlatformPackage);
141         // Others: we should optimize this with the (first-)boot compiler-reason.
142         others = new ArrayList<>(allPackageStates);
143         others.removeAll(important);
144         others.removeIf(PackageManagerServiceUtils.REMOVE_IF_NULL_PKG);
145         others.removeIf(isPlatformPackage);
146 
147         // Pre-size the array list by over-allocating by a factor of 1.5.
148         mDexoptCommands = new ArrayList<>(3 * allPackageStates.size() / 2);
149 
150         for (PackageStateInternal pkgSetting : important) {
151             mDexoptCommands.addAll(generatePackageDexopts(pkgSetting.getPkg(), pkgSetting,
152                     PackageManagerService.REASON_AB_OTA));
153         }
154         for (PackageStateInternal pkgSetting : others) {
155             // We assume here that there are no core apps left.
156             if (pkgSetting.getPkg().isCoreApp()) {
157                 throw new IllegalStateException("Found a core app that's not important");
158             }
159             mDexoptCommands.addAll(generatePackageDexopts(pkgSetting.getPkg(), pkgSetting,
160                     PackageManagerService.REASON_FIRST_BOOT));
161         }
162         completeSize = mDexoptCommands.size();
163 
164         long spaceAvailable = getAvailableSpace();
165         if (spaceAvailable < BULK_DELETE_THRESHOLD) {
166             Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
167                     + DexOptHelper.packagesToString(others));
168             for (PackageStateInternal pkg : others) {
169                 mPackageManagerService.deleteOatArtifactsOfPackage(snapshot, pkg.getPackageName());
170             }
171         }
172         long spaceAvailableNow = getAvailableSpace();
173 
174         prepareMetricsLogging(important.size(), others.size(), spaceAvailable, spaceAvailableNow);
175 
176         if (DEBUG_DEXOPT) {
177             try {
178                 // Output some data about the packages.
179                 PackageStateInternal lastUsed = Collections.max(important,
180                         Comparator.comparingLong(pkgSetting -> pkgSetting.getTransientState()
181                                 .getLatestForegroundPackageUseTimeInMills()));
182                 Log.d(TAG, "A/B OTA: lastUsed time = "
183                         + lastUsed.getTransientState().getLatestForegroundPackageUseTimeInMills());
184                 Log.d(TAG, "A/B OTA: deprioritized packages:");
185                 for (PackageStateInternal pkgSetting : others) {
186                     Log.d(TAG, "  " + pkgSetting.getPackageName() + " - "
187                             + pkgSetting.getTransientState()
188                             .getLatestForegroundPackageUseTimeInMills());
189                 }
190             } catch (Exception ignored) {
191             }
192         }
193     }
194 
195     @Override
cleanup()196     public synchronized void cleanup() throws RemoteException {
197         if (DEBUG_DEXOPT) {
198             Log.i(TAG, "Cleaning up OTA Dexopt state.");
199         }
200         mDexoptCommands = null;
201         availableSpaceAfterDexopt = getAvailableSpace();
202 
203         performMetricsLogging();
204     }
205 
206     @Override
isDone()207     public synchronized boolean isDone() throws RemoteException {
208         if (mDexoptCommands == null) {
209             throw new IllegalStateException("done() called before prepare()");
210         }
211 
212         return mDexoptCommands.isEmpty();
213     }
214 
215     @Override
getProgress()216     public synchronized float getProgress() throws RemoteException {
217         // Approximate the progress by the amount of already completed commands.
218         if (completeSize == 0) {
219             return 1f;
220         }
221         int commandsLeft = mDexoptCommands.size();
222         return (completeSize - commandsLeft) / ((float)completeSize);
223     }
224 
225     @Override
nextDexoptCommand()226     public synchronized String nextDexoptCommand() throws RemoteException {
227         if (mDexoptCommands == null) {
228             throw new IllegalStateException("dexoptNextPackage() called before prepare()");
229         }
230 
231         if (mDexoptCommands.isEmpty()) {
232             return "(all done)";
233         }
234 
235         String next = mDexoptCommands.remove(0);
236 
237         if (getAvailableSpace() > 0) {
238             dexoptCommandCountExecuted++;
239 
240             Log.d(TAG, "Next command: " + next);
241             return next;
242         } else {
243             if (DEBUG_DEXOPT) {
244                 Log.w(TAG, "Not enough space for OTA dexopt, stopping with "
245                         + (mDexoptCommands.size() + 1) + " commands left.");
246             }
247             mDexoptCommands.clear();
248             return "(no free space)";
249         }
250     }
251 
getMainLowSpaceThreshold()252     private long getMainLowSpaceThreshold() {
253         File dataDir = Environment.getDataDirectory();
254         @SuppressWarnings("deprecation")
255         long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
256         if (lowThreshold == 0) {
257             throw new IllegalStateException("Invalid low memory threshold");
258         }
259         return lowThreshold;
260     }
261 
262     /**
263      * Returns the difference of free space to the low-storage-space threshold. Positive values
264      * indicate free bytes.
265      */
getAvailableSpace()266     private long getAvailableSpace() {
267         // TODO: If apps are not installed in the internal /data partition, we should compare
268         //       against that storage's free capacity.
269         long lowThreshold = getMainLowSpaceThreshold();
270 
271         File dataDir = Environment.getDataDirectory();
272         long usableSpace = dataDir.getUsableSpace();
273 
274         return usableSpace - lowThreshold;
275     }
276 
277     /**
278      * Generate all dexopt commands for the given package.
279      */
generatePackageDexopts(AndroidPackage pkg, PackageStateInternal pkgSetting, int compilationReason)280     private synchronized List<String> generatePackageDexopts(AndroidPackage pkg,
281             PackageStateInternal pkgSetting, int compilationReason) {
282         // Intercept and collect dexopt requests
283         final List<String> commands = new ArrayList<String>();
284         final Installer collectingInstaller = new Installer(mContext, true) {
285             /**
286              * Encode the dexopt command into a string.
287              *
288              * Note: If you have to change the signature of this function, increase the version
289              *       number, and update the counterpart in
290              *       frameworks/native/cmds/installd/otapreopt.cpp.
291              */
292             @Override
293             public boolean dexopt(String apkPath, int uid, @Nullable String pkgName,
294                     String instructionSet, int dexoptNeeded, @Nullable String outputPath,
295                     int dexFlags, String compilerFilter, @Nullable String volumeUuid,
296                     @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade,
297                     int targetSdkVersion, @Nullable String profileName,
298                     @Nullable String dexMetadataPath, @Nullable String dexoptCompilationReason)
299                     throws InstallerException {
300                 final StringBuilder builder = new StringBuilder();
301 
302                 // The current version. For v10, see b/115993344.
303                 builder.append("10 ");
304 
305                 builder.append("dexopt");
306 
307                 encodeParameter(builder, apkPath);
308                 encodeParameter(builder, uid);
309                 encodeParameter(builder, pkgName);
310                 encodeParameter(builder, instructionSet);
311                 encodeParameter(builder, dexoptNeeded);
312                 encodeParameter(builder, outputPath);
313                 encodeParameter(builder, dexFlags);
314                 encodeParameter(builder, compilerFilter);
315                 encodeParameter(builder, volumeUuid);
316                 encodeParameter(builder, sharedLibraries);
317                 encodeParameter(builder, seInfo);
318                 encodeParameter(builder, downgrade);
319                 encodeParameter(builder, targetSdkVersion);
320                 encodeParameter(builder, profileName);
321                 encodeParameter(builder, dexMetadataPath);
322                 encodeParameter(builder, dexoptCompilationReason);
323 
324                 commands.add(builder.toString());
325 
326                 // Cancellation cannot happen for OtaDexOpt. Returns true always.
327                 return true;
328             }
329 
330             /**
331              * Encode a parameter as necessary for the commands string.
332              */
333             private void encodeParameter(StringBuilder builder, Object arg) {
334                 builder.append(' ');
335 
336                 if (arg == null) {
337                     builder.append('!');
338                     return;
339                 }
340 
341                 String txt = String.valueOf(arg);
342                 if (txt.indexOf('\0') != -1 || txt.indexOf(' ') != -1 || "!".equals(txt)) {
343                     throw new IllegalArgumentException(
344                             "Invalid argument while executing " + arg);
345                 }
346                 builder.append(txt);
347             }
348         };
349 
350         // Use the package manager install and install lock here for the OTA dex optimizer.
351         PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
352                 collectingInstaller, mPackageManagerService.mInstallLock, mContext);
353 
354         optimizer.performDexOpt(pkg, pkgSetting,
355                 null /* ISAs */,
356                 null /* CompilerStats.PackageStats */,
357                 mPackageManagerService.getDexManager().getPackageUseInfoOrDefault(
358                         pkg.getPackageName()),
359                 new DexoptOptions(pkg.getPackageName(), compilationReason,
360                         DexoptOptions.DEXOPT_BOOT_COMPLETE));
361 
362         return commands;
363     }
364 
365     @Override
dexoptNextPackage()366     public synchronized void dexoptNextPackage() throws RemoteException {
367         throw new UnsupportedOperationException();
368     }
369 
moveAbArtifacts(Installer installer)370     private void moveAbArtifacts(Installer installer) {
371         if (mDexoptCommands != null) {
372             throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
373         }
374 
375         if (!mPackageManagerService.isDeviceUpgrading()) {
376             Slog.d(TAG, "No upgrade, skipping A/B artifacts check.");
377             return;
378         }
379 
380         // Make a copy of all packages and look into each package.
381         final ArrayMap<String, ? extends PackageStateInternal> packageStates =
382                 LocalServices.getService(PackageManagerInternal.class).getPackageStates();
383         int packagePaths = 0;
384         int pathsSuccessful = 0;
385         for (int index = 0; index < packageStates.size(); index++) {
386             final PackageStateInternal packageState = packageStates.valueAt(index);
387             final AndroidPackage pkg = packageState.getPkg();
388             if (pkg == null) {
389                 continue;
390             }
391 
392             // Does the package have code? If not, there won't be any artifacts.
393             if (!mPackageManagerService.mPackageDexOptimizer.canOptimizePackage(pkg)) {
394                 continue;
395             }
396             if (pkg.getPath() == null) {
397                 Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath");
398                 continue;
399             }
400 
401             // If the path is in /system, /vendor, /product or /system_ext, ignore. It will
402             // have been ota-dexopted into /data/ota and moved into the dalvik-cache already.
403             if (pkg.getPath().startsWith("/system")
404                     || pkg.getPath().startsWith("/vendor")
405                     || pkg.getPath().startsWith("/product")
406                     || pkg.getPath().startsWith("/system_ext")) {
407                 continue;
408             }
409 
410             final String[] instructionSets = getAppDexInstructionSets(
411                     AndroidPackageUtils.getPrimaryCpuAbi(pkg, packageState),
412                     AndroidPackageUtils.getSecondaryCpuAbi(pkg, packageState));
413             final List<String> paths =
414                     AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg);
415             final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
416             final String packageName = pkg.getPackageName();
417             for (String dexCodeInstructionSet : dexCodeInstructionSets) {
418                 for (String path : paths) {
419                     String oatDir = PackageDexOptimizer.getOatDir(
420                             new File(pkg.getPath())).getAbsolutePath();
421 
422                     // TODO: Check first whether there is an artifact, to save the roundtrip time.
423 
424                     packagePaths++;
425                     try {
426                         installer.moveAb(packageName, path, dexCodeInstructionSet, oatDir);
427                         pathsSuccessful++;
428                     } catch (InstallerException e) {
429                     }
430                 }
431             }
432         }
433         Slog.i(TAG, "Moved " + pathsSuccessful + "/" + packagePaths);
434     }
435 
436     /**
437      * Initialize logging fields.
438      */
prepareMetricsLogging(int important, int others, long spaceBegin, long spaceBulk)439     private void prepareMetricsLogging(int important, int others, long spaceBegin, long spaceBulk) {
440         availableSpaceBefore = spaceBegin;
441         availableSpaceAfterBulkDelete = spaceBulk;
442         availableSpaceAfterDexopt = 0;
443 
444         importantPackageCount = important;
445         otherPackageCount = others;
446 
447         dexoptCommandCountTotal = mDexoptCommands.size();
448         dexoptCommandCountExecuted = 0;
449 
450         otaDexoptTimeStart = System.nanoTime();
451     }
452 
inMegabytes(long value)453     private static int inMegabytes(long value) {
454         long in_mega_bytes = value / (1024 * 1024);
455         if (in_mega_bytes > Integer.MAX_VALUE) {
456             Log.w(TAG, "Recording " + in_mega_bytes + "MB of free space, overflowing range");
457             return Integer.MAX_VALUE;
458         }
459         return (int)in_mega_bytes;
460     }
461 
performMetricsLogging()462     private void performMetricsLogging() {
463         long finalTime = System.nanoTime();
464 
465         metricsLogger.histogram("ota_dexopt_available_space_before_mb",
466                 inMegabytes(availableSpaceBefore));
467         metricsLogger.histogram("ota_dexopt_available_space_after_bulk_delete_mb",
468                 inMegabytes(availableSpaceAfterBulkDelete));
469         metricsLogger.histogram("ota_dexopt_available_space_after_dexopt_mb",
470                 inMegabytes(availableSpaceAfterDexopt));
471 
472         metricsLogger.histogram("ota_dexopt_num_important_packages", importantPackageCount);
473         metricsLogger.histogram("ota_dexopt_num_other_packages", otherPackageCount);
474 
475         metricsLogger.histogram("ota_dexopt_num_commands", dexoptCommandCountTotal);
476         metricsLogger.histogram("ota_dexopt_num_commands_executed", dexoptCommandCountExecuted);
477 
478         final int elapsedTimeSeconds =
479                 (int) TimeUnit.NANOSECONDS.toSeconds(finalTime - otaDexoptTimeStart);
480         metricsLogger.histogram("ota_dexopt_time_s", elapsedTimeSeconds);
481     }
482 
483     private static class OTADexoptPackageDexOptimizer extends
484             PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
OTADexoptPackageDexOptimizer(Installer installer, Object installLock, Context context)485         public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
486                 Context context) {
487             super(installer, installLock, context, "*otadexopt*");
488         }
489     }
490 }
491