1/*
2 * Copyright (C) 2017 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 android.app;
18
19import android.os.FileUtils;
20import android.os.RemoteException;
21import android.os.SystemProperties;
22import android.util.Slog;
23
24import com.android.internal.annotations.GuardedBy;
25
26import dalvik.system.BaseDexClassLoader;
27import dalvik.system.VMRuntime;
28
29import java.io.File;
30import java.io.IOException;
31import java.util.HashSet;
32import java.util.List;
33import java.util.Set;
34
35/**
36 * A dex load reporter which will notify package manager of any dex file loaded
37 * with {@code BaseDexClassLoader}.
38 * The goals are:
39 *     1) discover secondary dex files so that they can be optimized during the
40 *        idle maintenance job.
41 *     2) determine whether or not a dex file is used by an app which does not
42 *        own it (in order to select the optimal compilation method).
43 * @hide
44 */
45/*package*/ class DexLoadReporter implements BaseDexClassLoader.Reporter {
46    private static final String TAG = "DexLoadReporter";
47
48    private static final DexLoadReporter INSTANCE = new DexLoadReporter();
49
50    private static final boolean DEBUG = false;
51
52    // We must guard the access to the list of data directories because
53    // we might have concurrent accesses. Apps might load dex files while
54    // new data dirs are registered (due to creation of LoadedApks via
55    // create createApplicationContext).
56    @GuardedBy("mDataDirs")
57    private final Set<String> mDataDirs;
58
59    private DexLoadReporter() {
60        mDataDirs = new HashSet<>();
61    }
62
63    /*package*/ static DexLoadReporter getInstance() {
64        return INSTANCE;
65    }
66
67    /**
68     * Register an application data directory with the reporter.
69     * The data directories are used to determine if a dex file is secondary dex or not.
70     * Note that this method may be called multiple times for the same app, registering
71     * different data directories. This may happen when apps share the same user id
72     * ({@code android:sharedUserId}). For example, if app1 and app2 share the same user
73     * id, and app1 loads app2 apk, then both data directories will be registered.
74     */
75    /*package*/ void registerAppDataDir(String packageName, String dataDir) {
76        if (DEBUG) {
77            Slog.i(TAG, "Package " + packageName + " registering data dir: " + dataDir);
78        }
79        // TODO(calin): A few code paths imply that the data dir
80        // might be null. Investigate when that can happen.
81        if (dataDir != null) {
82            synchronized (mDataDirs) {
83                mDataDirs.add(dataDir);
84            }
85        }
86    }
87
88    @Override
89    public void report(List<String> dexPaths) {
90        if (dexPaths.isEmpty()) {
91            return;
92        }
93        // Notify the package manager about the dex loads unconditionally.
94        // The load might be for either a primary or secondary dex file.
95        notifyPackageManager(dexPaths);
96        // Check for secondary dex files and register them for profiling if
97        // possible.
98        registerSecondaryDexForProfiling(dexPaths);
99    }
100
101    private void notifyPackageManager(List<String> dexPaths) {
102        String packageName = ActivityThread.currentPackageName();
103        try {
104            ActivityThread.getPackageManager().notifyDexLoad(
105                    packageName, dexPaths, VMRuntime.getRuntime().vmInstructionSet());
106        } catch (RemoteException re) {
107            Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
108        }
109    }
110
111    private void registerSecondaryDexForProfiling(List<String> dexPaths) {
112        if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
113            return;
114        }
115        // Make a copy of the current data directories so that we don't keep the lock
116        // while registering for profiling. The registration will perform I/O to
117        // check for or create the profile.
118        String[] dataDirs;
119        synchronized (mDataDirs) {
120            dataDirs = mDataDirs.toArray(new String[0]);
121        }
122        for (String dexPath : dexPaths) {
123            registerSecondaryDexForProfiling(dexPath, dataDirs);
124        }
125    }
126
127    private void registerSecondaryDexForProfiling(String dexPath, String[] dataDirs) {
128        if (!isSecondaryDexFile(dexPath, dataDirs)) {
129            // The dex path is not a secondary dex file. Nothing to do.
130            return;
131        }
132        File secondaryProfile = getSecondaryProfileFile(dexPath);
133        try {
134            // Create the profile if not already there.
135            // Returns true if the file was created, false if the file already exists.
136            // or throws exceptions in case of errors.
137            boolean created = secondaryProfile.createNewFile();
138            if (DEBUG && created) {
139                Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile);
140            }
141        } catch (IOException ex) {
142            Slog.e(TAG, "Failed to create profile for secondary dex " + secondaryProfile +
143                    ":" + ex.getMessage());
144            // Don't move forward with the registration if we failed to create the profile.
145            return;
146        }
147
148        VMRuntime.registerAppInfo(secondaryProfile.getPath(), new String[] { dexPath });
149    }
150
151    // A dex file is a secondary dex file if it is in any of the registered app
152    // data directories.
153    private boolean isSecondaryDexFile(String dexPath, String[] dataDirs) {
154        for (String dataDir : dataDirs) {
155            if (FileUtils.contains(dataDir, dexPath)) {
156                return true;
157            }
158        }
159        return false;
160    }
161
162    // Secondary dex profiles are stored next to the dex file and have the same
163    // name with '.prof' appended.
164    // NOTE: Keep in sync with installd.
165    private File getSecondaryProfileFile(String dexPath) {
166        return new File(dexPath + ".prof");
167    }
168}
169