OtaDexoptService.java revision 14186731f98d326239922d093371d1e63b8c22a5
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 dexopt invocations for all work.
60     */
61    private List<String> mDexoptCommands;
62
63    private int completeSize;
64
65    public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
66        this.mContext = context;
67        this.mPackageManagerService = packageManagerService;
68
69        // Now it's time to check whether we need to move any A/B artifacts.
70        moveAbArtifacts(packageManagerService.mInstaller);
71    }
72
73    public static OtaDexoptService main(Context context,
74            PackageManagerService packageManagerService) {
75        OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
76        ServiceManager.addService("otadexopt", ota);
77
78        return ota;
79    }
80
81    @Override
82    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
83            String[] args, ResultReceiver resultReceiver) throws RemoteException {
84        (new OtaDexoptShellCommand(this)).exec(
85                this, in, out, err, args, resultReceiver);
86    }
87
88    @Override
89    public synchronized void prepare() throws RemoteException {
90        if (mDexoptCommands != null) {
91            throw new IllegalStateException("already called prepare()");
92        }
93        synchronized (mPackageManagerService.mPackages) {
94            // Important: the packages we need to run with ab-ota compiler-reason.
95            List<PackageParser.Package> important = PackageManagerServiceUtils.getPackagesForDexopt(
96                    mPackageManagerService.mPackages.values(), mPackageManagerService);
97            // Others: we should optimize this with the (first-)boot compiler-reason.
98            List<PackageParser.Package> others =
99                    new ArrayList<>(mPackageManagerService.mPackages.values());
100            others.removeAll(important);
101
102            // Pre-size the array list by over-allocating by a factor of 1.5.
103            mDexoptCommands = new ArrayList<>(3 * mPackageManagerService.mPackages.size() / 2);
104
105            for (PackageParser.Package p : important) {
106                // Make sure that core apps are optimized according to their own "reason".
107                // If the core apps are not preopted in the B OTA, and REASON_AB_OTA is not speed
108                // (by default is speed-profile) they will be interepreted/JITed. This in itself is
109                // not a problem as we will end up doing profile guided compilation. However, some
110                // core apps may be loaded by system server which doesn't JIT and we need to make
111                // sure we don't interpret-only
112                int compilationReason = p.coreApp
113                        ? PackageManagerService.REASON_CORE_APP
114                        : PackageManagerService.REASON_AB_OTA;
115                mDexoptCommands.addAll(generatePackageDexopts(p, compilationReason));
116            }
117            for (PackageParser.Package p : others) {
118                // We assume here that there are no core apps left.
119                if (p.coreApp) {
120                    throw new IllegalStateException("Found a core app that's not important");
121                }
122                mDexoptCommands.addAll(
123                        generatePackageDexopts(p, PackageManagerService.REASON_FIRST_BOOT));
124            }
125        }
126        completeSize = mDexoptCommands.size();
127    }
128
129    @Override
130    public synchronized void cleanup() throws RemoteException {
131        if (DEBUG_DEXOPT) {
132            Log.i(TAG, "Cleaning up OTA Dexopt state.");
133        }
134        mDexoptCommands = null;
135    }
136
137    @Override
138    public synchronized boolean isDone() throws RemoteException {
139        if (mDexoptCommands == null) {
140            throw new IllegalStateException("done() called before prepare()");
141        }
142
143        return mDexoptCommands.isEmpty();
144    }
145
146    @Override
147    public synchronized float getProgress() throws RemoteException {
148        // Approximate the progress by the amount of already completed commands.
149        if (completeSize == 0) {
150            return 1f;
151        }
152        int commandsLeft = mDexoptCommands.size();
153        return (completeSize - commandsLeft) / ((float)completeSize);
154    }
155
156    @Override
157    public synchronized String nextDexoptCommand() throws RemoteException {
158        if (mDexoptCommands == null) {
159            throw new IllegalStateException("dexoptNextPackage() called before prepare()");
160        }
161
162        if (mDexoptCommands.isEmpty()) {
163            return "(all done)";
164        }
165
166        String next = mDexoptCommands.remove(0);
167
168        if (IsFreeSpaceAvailable()) {
169            return next;
170        } else {
171            mDexoptCommands.clear();
172            return "(no free space)";
173        }
174    }
175
176    /**
177     * Check for low space. Returns true if there's space left.
178     */
179    private boolean IsFreeSpaceAvailable() {
180        // TODO: If apps are not installed in the internal /data partition, we should compare
181        //       against that storage's free capacity.
182        File dataDir = Environment.getDataDirectory();
183        @SuppressWarnings("deprecation")
184        long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
185        if (lowThreshold == 0) {
186            throw new IllegalStateException("Invalid low memory threshold");
187        }
188        long usableSpace = dataDir.getUsableSpace();
189        return (usableSpace >= lowThreshold);
190    }
191
192    /**
193     * Generate all dexopt commands for the given package.
194     */
195    private synchronized List<String> generatePackageDexopts(PackageParser.Package pkg,
196            int compilationReason) {
197        // Use our custom connection that just collects the commands.
198        RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection();
199        Installer collectingInstaller = new Installer(mContext, collectingConnection);
200
201        // Use the package manager install and install lock here for the OTA dex optimizer.
202        PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
203                collectingInstaller, mPackageManagerService.mInstallLock, mContext);
204
205        optimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
206                null /* ISAs */, false /* checkProfiles */,
207                getCompilerFilterForReason(compilationReason),
208                null /* CompilerStats.PackageStats */);
209
210        return collectingConnection.commands;
211    }
212
213    @Override
214    public synchronized void dexoptNextPackage() throws RemoteException {
215        throw new UnsupportedOperationException();
216    }
217
218    private void moveAbArtifacts(Installer installer) {
219        if (mDexoptCommands != null) {
220            throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
221        }
222
223        // Look into all packages.
224        Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages();
225        for (PackageParser.Package pkg : pkgs) {
226            if (pkg == null) {
227                continue;
228            }
229
230            // Does the package have code? If not, there won't be any artifacts.
231            if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
232                continue;
233            }
234            if (pkg.codePath == null) {
235                Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath");
236                continue;
237            }
238
239            // If the path is in /system or /vendor, ignore. It will have been ota-dexopted into
240            // /data/ota and moved into the dalvik-cache already.
241            if (pkg.codePath.startsWith("/system") || pkg.codePath.startsWith("/vendor")) {
242                continue;
243            }
244
245            final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
246            final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
247            final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
248            for (String dexCodeInstructionSet : dexCodeInstructionSets) {
249                for (String path : paths) {
250                    String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)).
251                            getAbsolutePath();
252
253                    // TODO: Check first whether there is an artifact, to save the roundtrip time.
254
255                    try {
256                        installer.moveAb(path, dexCodeInstructionSet, oatDir);
257                    } catch (InstallerException e) {
258                    }
259                }
260            }
261        }
262    }
263
264    private static class OTADexoptPackageDexOptimizer extends
265            PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
266
267        public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
268                Context context) {
269            super(installer, installLock, context, "*otadexopt*");
270        }
271
272        @Override
273        protected int adjustDexoptFlags(int dexoptFlags) {
274            // Add the OTA flag.
275            return dexoptFlags | DEXOPT_OTA;
276        }
277
278    }
279
280    private static class RecordingInstallerConnection extends InstallerConnection {
281        public List<String> commands = new ArrayList<String>(1);
282
283        @Override
284        public void setWarnIfHeld(Object warnIfHeld) {
285            throw new IllegalStateException("Should not reach here");
286        }
287
288        @Override
289        public synchronized String transact(String cmd) {
290            commands.add(cmd);
291            return "0";
292        }
293
294        @Override
295        public boolean mergeProfiles(int uid, String pkgName) throws InstallerException {
296            throw new IllegalStateException("Should not reach here");
297        }
298
299        @Override
300        public boolean dumpProfiles(String gid, String packageName, String codePaths)
301                throws InstallerException {
302            throw new IllegalStateException("Should not reach here");
303        }
304
305        @Override
306        public void disconnect() {
307            throw new IllegalStateException("Should not reach here");
308        }
309
310        @Override
311        public void waitForConnection() {
312            throw new IllegalStateException("Should not reach here");
313        }
314    }
315}
316