PackageDexOptimizer.java revision bdd30d86ef98456161069d11481b2ccd25a11b4e
1/*
2 * Copyright (C) 2015 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 android.annotation.Nullable;
20import android.content.Context;
21import android.content.pm.ApplicationInfo;
22import android.content.pm.PackageParser;
23import android.os.Environment;
24import android.os.PowerManager;
25import android.os.UserHandle;
26import android.os.WorkSource;
27import android.util.Log;
28import android.util.Slog;
29
30import com.android.internal.os.InstallerConnection.InstallerException;
31
32import java.io.File;
33import java.io.IOException;
34import java.util.List;
35
36import dalvik.system.DexFile;
37
38import static com.android.server.pm.Installer.DEXOPT_BOOTCOMPLETE;
39import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE;
40import static com.android.server.pm.Installer.DEXOPT_PROFILE_GUIDED;
41import static com.android.server.pm.Installer.DEXOPT_PUBLIC;
42import static com.android.server.pm.Installer.DEXOPT_SAFEMODE;
43import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
44import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
45import static com.android.server.pm.PackageManagerServiceCompilerMapping.getFullCompilerFilter;
46
47/**
48 * Helper class for running dexopt command on packages.
49 */
50class PackageDexOptimizer {
51    private static final String TAG = "PackageManager.DexOptimizer";
52    static final String OAT_DIR_NAME = "oat";
53    // TODO b/19550105 Remove error codes and use exceptions
54    static final int DEX_OPT_SKIPPED = 0;
55    static final int DEX_OPT_PERFORMED = 1;
56    static final int DEX_OPT_DEFERRED = 2;
57    static final int DEX_OPT_FAILED = -1;
58
59    private final Installer mInstaller;
60    private final Object mInstallLock;
61
62    private final PowerManager.WakeLock mDexoptWakeLock;
63    private volatile boolean mSystemReady;
64
65    PackageDexOptimizer(Installer installer, Object installLock, Context context,
66            String wakeLockTag) {
67        this.mInstaller = installer;
68        this.mInstallLock = installLock;
69
70        PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
71        mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
72    }
73
74    protected PackageDexOptimizer(PackageDexOptimizer from) {
75        this.mInstaller = from.mInstaller;
76        this.mInstallLock = from.mInstallLock;
77        this.mDexoptWakeLock = from.mDexoptWakeLock;
78        this.mSystemReady = from.mSystemReady;
79    }
80
81    static boolean canOptimizePackage(PackageParser.Package pkg) {
82        return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0;
83    }
84
85    /**
86     * Performs dexopt on all code paths and libraries of the specified package for specified
87     * instruction sets.
88     *
89     * <p>Calls to {@link com.android.server.pm.Installer#dexopt} on {@link #mInstaller} are
90     * synchronized on {@link #mInstallLock}.
91     */
92    int performDexOpt(PackageParser.Package pkg, String[] instructionSets, boolean checkProfiles,
93            String targetCompilationFilter) {
94        synchronized (mInstallLock) {
95            final boolean useLock = mSystemReady;
96            if (useLock) {
97                mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
98                mDexoptWakeLock.acquire();
99            }
100            try {
101                return performDexOptLI(pkg, instructionSets, checkProfiles,
102                        targetCompilationFilter);
103            } finally {
104                if (useLock) {
105                    mDexoptWakeLock.release();
106                }
107            }
108        }
109    }
110
111    /**
112     * Adjust the given dexopt-needed value. Can be overridden to influence the decision to
113     * optimize or not (and in what way).
114     */
115    protected int adjustDexoptNeeded(int dexoptNeeded) {
116        return dexoptNeeded;
117    }
118
119    /**
120     * Adjust the given dexopt flags that will be passed to the installer.
121     */
122    protected int adjustDexoptFlags(int dexoptFlags) {
123        return dexoptFlags;
124    }
125
126    private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
127            boolean checkProfiles, String targetCompilerFilter) {
128        final String[] instructionSets = targetInstructionSets != null ?
129                targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
130
131        if (!canOptimizePackage(pkg)) {
132            return DEX_OPT_SKIPPED;
133        }
134
135        final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
136        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
137
138        boolean isProfileGuidedFilter = DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter);
139        // If any part of the app is used by other apps, we cannot use profile-guided
140        // compilation.
141        // TODO: This needs to be refactored to be also checked when the target mode is
142        //       profile-guided.
143        if (isProfileGuidedFilter) {
144            for (String path : paths) {
145                if (isUsedByOtherApps(path)) {
146                    checkProfiles = false;
147
148                    // TODO: Should we only upgrade to the non-profile-guided version? That is,
149                    //       given verify-profile, should we move to interpret-only?
150                    targetCompilerFilter = getFullCompilerFilter();
151                    isProfileGuidedFilter = false;
152
153                    break;
154                }
155            }
156        }
157
158        // If we're asked to take profile updates into account, check now.
159        boolean newProfile = false;
160        if (checkProfiles && isProfileGuidedFilter) {
161            // Merge profiles, see if we need to do anything.
162            try {
163                newProfile = mInstaller.mergeProfiles(sharedGid, pkg.packageName);
164            } catch (InstallerException e) {
165                Slog.w(TAG, "Failed to merge profiles", e);
166            }
167        }
168
169        final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
170        final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
171
172        boolean performedDexOpt = false;
173        final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
174        for (String dexCodeInstructionSet : dexCodeInstructionSets) {
175            for (String path : paths) {
176                int dexoptNeeded;
177                try {
178                    dexoptNeeded = DexFile.getDexOptNeeded(path,
179                            dexCodeInstructionSet, targetCompilerFilter, newProfile);
180                } catch (IOException ioe) {
181                    Slog.w(TAG, "IOException reading apk: " + path, ioe);
182                    return DEX_OPT_FAILED;
183                }
184                dexoptNeeded = adjustDexoptNeeded(dexoptNeeded);
185
186                final String dexoptType;
187                String oatDir = null;
188                switch (dexoptNeeded) {
189                    case DexFile.NO_DEXOPT_NEEDED:
190                        continue;
191                    case DexFile.DEX2OAT_NEEDED:
192                        dexoptType = "dex2oat";
193                        oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
194                        break;
195                    case DexFile.PATCHOAT_NEEDED:
196                        dexoptType = "patchoat";
197                        break;
198                    case DexFile.SELF_PATCHOAT_NEEDED:
199                        dexoptType = "self patchoat";
200                        break;
201                    default:
202                        throw new IllegalStateException("Invalid dexopt:" + dexoptNeeded);
203                }
204
205                Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
206                        + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
207                        + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
208                        + " target-filter=" + targetCompilerFilter + " oatDir = " + oatDir);
209                // Profile guide compiled oat files should not be public.
210                final boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter;
211                final int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
212                final int dexFlags = adjustDexoptFlags(
213                        ( isPublic ? DEXOPT_PUBLIC : 0)
214                        | (vmSafeMode ? DEXOPT_SAFEMODE : 0)
215                        | (debuggable ? DEXOPT_DEBUGGABLE : 0)
216                        | profileFlag
217                        | DEXOPT_BOOTCOMPLETE);
218
219                try {
220                    mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
221                            dexoptNeeded, oatDir, dexFlags, targetCompilerFilter, pkg.volumeUuid);
222                    performedDexOpt = true;
223                } catch (InstallerException e) {
224                    Slog.w(TAG, "Failed to dexopt", e);
225                }
226            }
227        }
228
229        // If we've gotten here, we're sure that no error occurred and that we haven't
230        // deferred dex-opt. We've either dex-opted one more paths or instruction sets or
231        // we've skipped all of them because they are up to date. In both cases this
232        // package doesn't need dexopt any longer.
233        return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
234    }
235
236    /**
237     * Creates oat dir for the specified package. In certain cases oat directory
238     * <strong>cannot</strong> be created:
239     * <ul>
240     *      <li>{@code pkg} is a system app, which is not updated.</li>
241     *      <li>Package location is not a directory, i.e. monolithic install.</li>
242     * </ul>
243     *
244     * @return Absolute path to the oat directory or null, if oat directory
245     * cannot be created.
246     */
247    @Nullable
248    private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet) {
249        if (!pkg.canHaveOatDir()) {
250            return null;
251        }
252        File codePath = new File(pkg.codePath);
253        if (codePath.isDirectory()) {
254            File oatDir = getOatDir(codePath);
255            try {
256                mInstaller.createOatDir(oatDir.getAbsolutePath(), dexInstructionSet);
257            } catch (InstallerException e) {
258                Slog.w(TAG, "Failed to create oat dir", e);
259                return null;
260            }
261            return oatDir.getAbsolutePath();
262        }
263        return null;
264    }
265
266    static File getOatDir(File codePath) {
267        return new File(codePath, OAT_DIR_NAME);
268    }
269
270    void systemReady() {
271        mSystemReady = true;
272    }
273
274    private boolean isUsedByOtherApps(String apkPath) {
275        try {
276            apkPath = new File(apkPath).getCanonicalPath();
277        } catch (IOException e) {
278            // Log an error but continue without it.
279            Slog.w(TAG, "Failed to get canonical path", e);
280        }
281        String useMarker = apkPath.replace('/', '@');
282        final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
283        for (int i = 0; i < currentUserIds.length; i++) {
284            File profileDir = Environment.getDataProfilesDeForeignDexDirectory(currentUserIds[i]);
285            File foreignUseMark = new File(profileDir, useMarker);
286            if (foreignUseMark.exists()) {
287                return true;
288            }
289        }
290        return false;
291    }
292
293    /**
294     * A specialized PackageDexOptimizer that overrides already-installed checks, forcing a
295     * dexopt path.
296     */
297    public static class ForcedUpdatePackageDexOptimizer extends PackageDexOptimizer {
298
299        public ForcedUpdatePackageDexOptimizer(Installer installer, Object installLock,
300                Context context, String wakeLockTag) {
301            super(installer, installLock, context, wakeLockTag);
302        }
303
304        public ForcedUpdatePackageDexOptimizer(PackageDexOptimizer from) {
305            super(from);
306        }
307
308        @Override
309        protected int adjustDexoptNeeded(int dexoptNeeded) {
310            // Ensure compilation, no matter the current state.
311            // TODO: The return value is wrong when patchoat is needed.
312            return DexFile.DEX2OAT_NEEDED;
313        }
314    }
315}
316