OtaDexoptService.java revision dab38e000436bf8234955b0333eaecf389e65b6f
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    // The synthetic library dependencies denoting "no checks."
54    private final static String[] NO_LIBRARIES = new String[] { "&" };
55
56    // The amount of "available" (free - low threshold) space necessary at the start of an OTA to
57    // not bulk-delete unused apps' odex files.
58    private final static long BULK_DELETE_THRESHOLD = 1024 * 1024 * 1024;  // 1GB.
59
60    private final Context mContext;
61    private final PackageManagerService mPackageManagerService;
62
63    // TODO: Evaluate the need for WeakReferences here.
64
65    /**
66     * The list of dexopt invocations for all work.
67     */
68    private List<String> mDexoptCommands;
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 (mDexoptCommands != null) {
98            throw new IllegalStateException("already called prepare()");
99        }
100        final List<PackageParser.Package> important;
101        final List<PackageParser.Package> others;
102        synchronized (mPackageManagerService.mPackages) {
103            // Important: the packages we need to run with ab-ota compiler-reason.
104            important = PackageManagerServiceUtils.getPackagesForDexopt(
105                    mPackageManagerService.mPackages.values(), mPackageManagerService);
106            // Others: we should optimize this with the (first-)boot compiler-reason.
107            others = new ArrayList<>(mPackageManagerService.mPackages.values());
108            others.removeAll(important);
109
110            // Pre-size the array list by over-allocating by a factor of 1.5.
111            mDexoptCommands = new ArrayList<>(3 * mPackageManagerService.mPackages.size() / 2);
112        }
113
114        for (PackageParser.Package p : important) {
115            // Make sure that core apps are optimized according to their own "reason".
116            // If the core apps are not preopted in the B OTA, and REASON_AB_OTA is not speed
117            // (by default is speed-profile) they will be interepreted/JITed. This in itself is
118            // not a problem as we will end up doing profile guided compilation. However, some
119            // core apps may be loaded by system server which doesn't JIT and we need to make
120            // sure we don't interpret-only
121            int compilationReason = p.coreApp
122                    ? PackageManagerService.REASON_CORE_APP
123                    : PackageManagerService.REASON_AB_OTA;
124            mDexoptCommands.addAll(generatePackageDexopts(p, compilationReason));
125        }
126        for (PackageParser.Package p : others) {
127            // We assume here that there are no core apps left.
128            if (p.coreApp) {
129                throw new IllegalStateException("Found a core app that's not important");
130            }
131            mDexoptCommands.addAll(
132                    generatePackageDexopts(p, PackageManagerService.REASON_FIRST_BOOT));
133        }
134        completeSize = mDexoptCommands.size();
135
136        if (getAvailableSpace() < BULK_DELETE_THRESHOLD) {
137            Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
138                    + PackageManagerServiceUtils.packagesToString(others));
139            for (PackageParser.Package pkg : others) {
140                deleteOatArtifactsOfPackage(pkg);
141            }
142        }
143    }
144
145    @Override
146    public synchronized void cleanup() throws RemoteException {
147        if (DEBUG_DEXOPT) {
148            Log.i(TAG, "Cleaning up OTA Dexopt state.");
149        }
150        mDexoptCommands = null;
151    }
152
153    @Override
154    public synchronized boolean isDone() throws RemoteException {
155        if (mDexoptCommands == null) {
156            throw new IllegalStateException("done() called before prepare()");
157        }
158
159        return mDexoptCommands.isEmpty();
160    }
161
162    @Override
163    public synchronized float getProgress() throws RemoteException {
164        // Approximate the progress by the amount of already completed commands.
165        if (completeSize == 0) {
166            return 1f;
167        }
168        int commandsLeft = mDexoptCommands.size();
169        return (completeSize - commandsLeft) / ((float)completeSize);
170    }
171
172    @Override
173    public synchronized String nextDexoptCommand() throws RemoteException {
174        if (mDexoptCommands == null) {
175            throw new IllegalStateException("dexoptNextPackage() called before prepare()");
176        }
177
178        if (mDexoptCommands.isEmpty()) {
179            return "(all done)";
180        }
181
182        String next = mDexoptCommands.remove(0);
183
184        if (getAvailableSpace() > 0) {
185            return next;
186        } else {
187            if (DEBUG_DEXOPT) {
188                Log.w(TAG, "Not enough space for OTA dexopt, stopping with "
189                        + (mDexoptCommands.size() + 1) + " commands left.");
190            }
191            mDexoptCommands.clear();
192            return "(no free space)";
193        }
194    }
195
196    private long getMainLowSpaceThreshold() {
197        File dataDir = Environment.getDataDirectory();
198        @SuppressWarnings("deprecation")
199        long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
200        if (lowThreshold == 0) {
201            throw new IllegalStateException("Invalid low memory threshold");
202        }
203        return lowThreshold;
204    }
205
206    /**
207     * Returns the difference of free space to the low-storage-space threshold. Positive values
208     * indicate free bytes.
209     */
210    private long getAvailableSpace() {
211        // TODO: If apps are not installed in the internal /data partition, we should compare
212        //       against that storage's free capacity.
213        long lowThreshold = getMainLowSpaceThreshold();
214
215        File dataDir = Environment.getDataDirectory();
216        long usableSpace = dataDir.getUsableSpace();
217
218        return usableSpace - lowThreshold;
219    }
220
221    private static String getOatDir(PackageParser.Package pkg) {
222        if (!pkg.canHaveOatDir()) {
223            return null;
224        }
225        File codePath = new File(pkg.codePath);
226        if (codePath.isDirectory()) {
227            return PackageDexOptimizer.getOatDir(codePath).getAbsolutePath();
228        }
229        return null;
230    }
231
232    private void deleteOatArtifactsOfPackage(PackageParser.Package pkg) {
233        String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
234        for (String codePath : pkg.getAllCodePaths()) {
235            for (String isa : instructionSets) {
236                try {
237                    mPackageManagerService.mInstaller.deleteOdex(codePath, isa, getOatDir(pkg));
238                } catch (InstallerException e) {
239                    Log.e(TAG, "Failed deleting oat files for " + codePath, e);
240                }
241            }
242        }
243    }
244
245    /**
246     * Generate all dexopt commands for the given package.
247     */
248    private synchronized List<String> generatePackageDexopts(PackageParser.Package pkg,
249            int compilationReason) {
250        // Use our custom connection that just collects the commands.
251        RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection();
252        Installer collectingInstaller = new Installer(mContext, collectingConnection);
253
254        // Use the package manager install and install lock here for the OTA dex optimizer.
255        PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
256                collectingInstaller, mPackageManagerService.mInstallLock, mContext);
257
258        String[] libraryDependencies = pkg.usesLibraryFiles;
259        if (pkg.isSystemApp()) {
260            // For system apps, we want to avoid classpaths checks.
261            libraryDependencies = NO_LIBRARIES;
262        }
263
264        optimizer.performDexOpt(pkg, libraryDependencies,
265                null /* ISAs */, false /* checkProfiles */,
266                getCompilerFilterForReason(compilationReason),
267                null /* CompilerStats.PackageStats */);
268
269        return collectingConnection.commands;
270    }
271
272    @Override
273    public synchronized void dexoptNextPackage() throws RemoteException {
274        throw new UnsupportedOperationException();
275    }
276
277    private void moveAbArtifacts(Installer installer) {
278        if (mDexoptCommands != null) {
279            throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
280        }
281
282        // Look into all packages.
283        Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages();
284        for (PackageParser.Package pkg : pkgs) {
285            if (pkg == null) {
286                continue;
287            }
288
289            // Does the package have code? If not, there won't be any artifacts.
290            if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
291                continue;
292            }
293            if (pkg.codePath == null) {
294                Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath");
295                continue;
296            }
297
298            // If the path is in /system or /vendor, ignore. It will have been ota-dexopted into
299            // /data/ota and moved into the dalvik-cache already.
300            if (pkg.codePath.startsWith("/system") || pkg.codePath.startsWith("/vendor")) {
301                continue;
302            }
303
304            final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
305            final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
306            final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
307            for (String dexCodeInstructionSet : dexCodeInstructionSets) {
308                for (String path : paths) {
309                    String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)).
310                            getAbsolutePath();
311
312                    // TODO: Check first whether there is an artifact, to save the roundtrip time.
313
314                    try {
315                        installer.moveAb(path, dexCodeInstructionSet, oatDir);
316                    } catch (InstallerException e) {
317                    }
318                }
319            }
320        }
321    }
322
323    private static class OTADexoptPackageDexOptimizer extends
324            PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
325
326        public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
327                Context context) {
328            super(installer, installLock, context, "*otadexopt*");
329        }
330
331        @Override
332        protected int adjustDexoptFlags(int dexoptFlags) {
333            // Add the OTA flag.
334            return dexoptFlags | DEXOPT_OTA;
335        }
336
337    }
338
339    private static class RecordingInstallerConnection extends InstallerConnection {
340        public List<String> commands = new ArrayList<String>(1);
341
342        @Override
343        public void setWarnIfHeld(Object warnIfHeld) {
344            throw new IllegalStateException("Should not reach here");
345        }
346
347        @Override
348        public synchronized String transact(String cmd) {
349            commands.add(cmd);
350            return "0";
351        }
352
353        @Override
354        public boolean mergeProfiles(int uid, String pkgName) throws InstallerException {
355            throw new IllegalStateException("Should not reach here");
356        }
357
358        @Override
359        public boolean dumpProfiles(String gid, String packageName, String codePaths)
360                throws InstallerException {
361            throw new IllegalStateException("Should not reach here");
362        }
363
364        @Override
365        public void disconnect() {
366            throw new IllegalStateException("Should not reach here");
367        }
368
369        @Override
370        public void waitForConnection() {
371            throw new IllegalStateException("Should not reach here");
372        }
373    }
374}
375