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