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
17import java.io.File;
18import java.io.FileInputStream;
19import java.io.FileOutputStream;
20import java.io.IOException;
21import java.lang.reflect.Method;
22import java.lang.reflect.Constructor;
23import java.util.HashMap;
24
25public class Main {
26
27  private static final String PROFILE_NAME = "primary.prof";
28  private static final String APP_DIR_PREFIX = "app_dir_";
29  private static final String FOREIGN_DEX_PROFILE_DIR = "foreign-dex";
30  private static final String TEMP_FILE_NAME_PREFIX = "dummy";
31  private static final String TEMP_FILE_NAME_SUFFIX = "-file";
32
33  public static void main(String[] args) throws Exception {
34    File tmpFile = null;
35    File appDir = null;
36    File profileFile = null;
37    File foreignDexProfileDir = null;
38
39    try {
40      // Create the necessary files layout.
41      tmpFile = createTempFile();
42      appDir = new File(tmpFile.getParent(), APP_DIR_PREFIX + tmpFile.getName());
43      appDir.mkdir();
44      foreignDexProfileDir = new File(tmpFile.getParent(), FOREIGN_DEX_PROFILE_DIR);
45      foreignDexProfileDir.mkdir();
46      profileFile = createTempFile();
47
48      String codePath = System.getenv("DEX_LOCATION") + "/577-profile-foreign-dex.jar";
49
50      // Register the app with the runtime
51      VMRuntime.registerAppInfo(profileFile.getPath(), appDir.getPath(),
52             new String[] { codePath }, foreignDexProfileDir.getPath());
53
54      testMarkerForForeignDex(foreignDexProfileDir);
55      testMarkerForCodePath(foreignDexProfileDir);
56      testMarkerForApplicationDexFile(foreignDexProfileDir, appDir);
57    } finally {
58      if (tmpFile != null) {
59        tmpFile.delete();
60      }
61      if (profileFile != null) {
62        profileFile.delete();
63      }
64      if (foreignDexProfileDir != null) {
65        foreignDexProfileDir.delete();
66      }
67      if (appDir != null) {
68        appDir.delete();
69      }
70    }
71  }
72
73  // Verify we actually create a marker on disk for foreign dex files.
74  private static void testMarkerForForeignDex(File foreignDexProfileDir) throws Exception {
75    String foreignDex = System.getenv("DEX_LOCATION") + "/577-profile-foreign-dex-ex.jar";
76    loadDexFile(foreignDex);
77    checkMarker(foreignDexProfileDir, foreignDex, /* exists */ true);
78  }
79
80  // Verify we do not create a marker on disk for dex files path of the code path.
81  private static void testMarkerForCodePath(File foreignDexProfileDir) throws Exception {
82    String codePath = System.getenv("DEX_LOCATION") + "/577-profile-foreign-dex.jar";
83    loadDexFile(codePath);
84    checkMarker(foreignDexProfileDir, codePath, /* exists */ false);
85  }
86
87  private static void testMarkerForApplicationDexFile(File foreignDexProfileDir, File appDir)
88      throws Exception {
89    // Copy the -ex jar to the application directory and load it from there.
90    // This will record duplicate class conflicts but we don't care for this use case.
91    File foreignDex = new File(System.getenv("DEX_LOCATION") + "/577-profile-foreign-dex-ex.jar");
92    File appDex = new File(appDir, "appDex.jar");
93    try {
94      copyFile(foreignDex, appDex);
95
96      loadDexFile(appDex.getAbsolutePath());
97      checkMarker(foreignDexProfileDir, appDex.getAbsolutePath(), /* exists */ false);
98    } finally {
99      if (appDex != null) {
100        appDex.delete();
101      }
102    }
103  }
104
105  private static void checkMarker(File foreignDexProfileDir, String dexFile, boolean exists) {
106    File marker = new File(foreignDexProfileDir, dexFile.replace('/', '@'));
107    boolean result_ok = exists ? marker.exists() : !marker.exists();
108    if (!result_ok) {
109      throw new RuntimeException("Marker test failed for:" + marker.getPath());
110    }
111  }
112
113  private static void loadDexFile(String dexFile) throws Exception {
114    Class pathClassLoader = Class.forName("dalvik.system.PathClassLoader");
115    if (pathClassLoader == null) {
116        throw new RuntimeException("Couldn't find path class loader class");
117    }
118    Constructor constructor =
119        pathClassLoader.getDeclaredConstructor(String.class, ClassLoader.class);
120    constructor.newInstance(
121            dexFile, ClassLoader.getSystemClassLoader());
122  }
123
124  private static class VMRuntime {
125    private static final Method registerAppInfoMethod;
126    static {
127      try {
128        Class c = Class.forName("dalvik.system.VMRuntime");
129        registerAppInfoMethod = c.getDeclaredMethod("registerAppInfo",
130            String.class, String.class, String[].class, String.class);
131      } catch (Exception e) {
132        throw new RuntimeException(e);
133      }
134    }
135
136    public static void registerAppInfo(String pkgName, String appDir,
137        String[] codePath, String foreignDexProfileDir) throws Exception {
138      registerAppInfoMethod.invoke(null, pkgName, appDir, codePath, foreignDexProfileDir);
139    }
140  }
141
142  private static void copyFile(File fromFile, File toFile) throws Exception {
143    FileInputStream in = new FileInputStream(fromFile);
144    FileOutputStream out = new FileOutputStream(toFile);
145    try {
146      byte[] buffer = new byte[4096];
147      int bytesRead;
148      while ((bytesRead = in.read(buffer)) >= 0) {
149          out.write(buffer, 0, bytesRead);
150      }
151    } finally {
152      out.flush();
153      try {
154          out.getFD().sync();
155      } catch (IOException e) {
156      }
157      out.close();
158      in.close();
159    }
160  }
161
162  private static File createTempFile() throws Exception {
163    try {
164      return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
165    } catch (IOException e) {
166      System.setProperty("java.io.tmpdir", "/data/local/tmp");
167      try {
168        return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
169      } catch (IOException e2) {
170        System.setProperty("java.io.tmpdir", "/sdcard");
171        return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
172      }
173    }
174  }
175}
176