OtaDexoptService.java revision 37e5fdc6b4963f3533caecdd92b129f79da69dd8
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        // Make sure that core apps are optimized according to their own "reason".
217        // If the core apps are not preopted in the B OTA, and REASON_AB_OTA is not speed
218        // (by default is speed-profile) they will be interepreted/JITed. This in itself is not a
219        // problem as we will end up doing profile guided compilation. However, some core apps may
220        // be loaded by system server which doesn't JIT and we need to make sure we don't
221        // interpret-only
222        int compilationReason = nextPackage.coreApp
223                ? PackageManagerService.REASON_CORE_APP
224                : PackageManagerService.REASON_AB_OTA;
225
226        optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles,
227                null /* ISAs */, false /* checkProfiles */,
228                getCompilerFilterForReason(compilationReason),
229                null /* CompilerStats.PackageStats */);
230
231        mCommandsForCurrentPackage = collectingConnection.commands;
232        if (mCommandsForCurrentPackage.isEmpty()) {
233            mCommandsForCurrentPackage = null;
234        }
235
236        return true;
237    }
238
239    @Override
240    public synchronized void dexoptNextPackage() throws RemoteException {
241        if (mDexoptPackages == null) {
242            throw new IllegalStateException("dexoptNextPackage() called before prepare()");
243        }
244        if (mDexoptPackages.isEmpty()) {
245            // Tolerate repeated calls.
246            return;
247        }
248
249        PackageParser.Package nextPackage = mDexoptPackages.remove(0);
250
251        if (DEBUG_DEXOPT) {
252            Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt.");
253        }
254
255        // Check for low space.
256        // TODO: If apps are not installed in the internal /data partition, we should compare
257        //       against that storage's free capacity.
258        File dataDir = Environment.getDataDirectory();
259        @SuppressWarnings("deprecation")
260        long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
261        if (lowThreshold == 0) {
262            throw new IllegalStateException("Invalid low memory threshold");
263        }
264        long usableSpace = dataDir.getUsableSpace();
265        if (usableSpace < lowThreshold) {
266            Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " +
267                    usableSpace);
268            return;
269        }
270
271        PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
272                mPackageManagerService.mInstaller, mPackageManagerService.mInstallLock, mContext);
273        optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, null /* ISAs */,
274                false /* checkProfiles */,
275                getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA),
276                mPackageManagerService.getOrCreateCompilerPackageStats(nextPackage));
277    }
278
279    private void moveAbArtifacts(Installer installer) {
280        if (mDexoptPackages != null) {
281            throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
282        }
283
284        // Look into all packages.
285        Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages();
286        for (PackageParser.Package pkg : pkgs) {
287            if (pkg == null) {
288                continue;
289            }
290
291            // Does the package have code? If not, there won't be any artifacts.
292            if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
293                continue;
294            }
295            if (pkg.codePath == null) {
296                Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath");
297                continue;
298            }
299
300            // If the path is in /system or /vendor, ignore. It will have been ota-dexopted into
301            // /data/ota and moved into the dalvik-cache already.
302            if (pkg.codePath.startsWith("/system") || pkg.codePath.startsWith("/vendor")) {
303                continue;
304            }
305
306            final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
307            final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
308            final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
309            for (String dexCodeInstructionSet : dexCodeInstructionSets) {
310                for (String path : paths) {
311                    String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)).
312                            getAbsolutePath();
313
314                    // TODO: Check first whether there is an artifact, to save the roundtrip time.
315
316                    try {
317                        installer.moveAb(path, dexCodeInstructionSet, oatDir);
318                    } catch (InstallerException e) {
319                    }
320                }
321            }
322        }
323    }
324
325    private static class OTADexoptPackageDexOptimizer extends
326            PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
327
328        public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
329                Context context) {
330            super(installer, installLock, context, "*otadexopt*");
331        }
332
333        @Override
334        protected int adjustDexoptFlags(int dexoptFlags) {
335            // Add the OTA flag.
336            return dexoptFlags | DEXOPT_OTA;
337        }
338
339    }
340
341    private static class RecordingInstallerConnection extends InstallerConnection {
342        public List<String> commands = new ArrayList<String>(1);
343
344        @Override
345        public void setWarnIfHeld(Object warnIfHeld) {
346            throw new IllegalStateException("Should not reach here");
347        }
348
349        @Override
350        public synchronized String transact(String cmd) {
351            commands.add(cmd);
352            return "0";
353        }
354
355        @Override
356        public boolean mergeProfiles(int uid, String pkgName) throws InstallerException {
357            throw new IllegalStateException("Should not reach here");
358        }
359
360        @Override
361        public boolean dumpProfiles(String gid, String packageName, String codePaths)
362                throws InstallerException {
363            throw new IllegalStateException("Should not reach here");
364        }
365
366        @Override
367        public void disconnect() {
368            throw new IllegalStateException("Should not reach here");
369        }
370
371        @Override
372        public void waitForConnection() {
373            throw new IllegalStateException("Should not reach here");
374        }
375    }
376}
377