OtaDexoptService.java revision 9438528d99a404ce10e567bbbb5f0cc9519fb5da
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
230        mCommandsForCurrentPackage = collectingConnection.commands;
231        if (mCommandsForCurrentPackage.isEmpty()) {
232            mCommandsForCurrentPackage = null;
233        }
234
235        return true;
236    }
237
238    @Override
239    public synchronized void dexoptNextPackage() throws RemoteException {
240        if (mDexoptPackages == null) {
241            throw new IllegalStateException("dexoptNextPackage() called before prepare()");
242        }
243        if (mDexoptPackages.isEmpty()) {
244            // Tolerate repeated calls.
245            return;
246        }
247
248        PackageParser.Package nextPackage = mDexoptPackages.remove(0);
249
250        if (DEBUG_DEXOPT) {
251            Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt.");
252        }
253
254        // Check for low space.
255        // TODO: If apps are not installed in the internal /data partition, we should compare
256        //       against that storage's free capacity.
257        File dataDir = Environment.getDataDirectory();
258        @SuppressWarnings("deprecation")
259        long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
260        if (lowThreshold == 0) {
261            throw new IllegalStateException("Invalid low memory threshold");
262        }
263        long usableSpace = dataDir.getUsableSpace();
264        if (usableSpace < lowThreshold) {
265            Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " +
266                    usableSpace);
267            return;
268        }
269
270        PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
271                mPackageManagerService.mInstaller, mPackageManagerService.mInstallLock, mContext);
272        optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, null /* ISAs */,
273                false /* checkProfiles */,
274                getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA));
275    }
276
277    private void moveAbArtifacts(Installer installer) {
278        if (mDexoptPackages != 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