PageRenderTime 27ms CodeModel.GetById 4ms RepoModel.GetById 0ms app.codeStats 0ms

/src/vogar/android/AndroidSdk.java

http://vogar.googlecode.com/
Java | 350 lines | 247 code | 39 blank | 64 comment | 31 complexity | f465d7df5c80a313564fc85f44b6f15a MD5 | raw file
  1. /*
  2. * Copyright (C) 2010 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. package vogar.android;
  17. import java.io.File;
  18. import java.io.FilenameFilter;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.Collection;
  22. import java.util.Collections;
  23. import java.util.List;
  24. import java.util.concurrent.TimeoutException;
  25. import vogar.Classpath;
  26. import vogar.HostFileCache;
  27. import vogar.Log;
  28. import vogar.Md5Cache;
  29. import vogar.ModeId;
  30. import vogar.commands.Command;
  31. import vogar.commands.Mkdir;
  32. import vogar.util.Strings;
  33. /**
  34. * Android SDK commands such as adb, aapt and dx.
  35. */
  36. public class AndroidSdk {
  37. // $BOOTCLASSPATH defined by system/core/rootdir/init.rc
  38. public static final String[] BOOTCLASSPATH = new String[] { "core",
  39. "conscrypt",
  40. "okhttp",
  41. "core-junit",
  42. "bouncycastle",
  43. "ext",
  44. "framework",
  45. "telephony-common",
  46. "mms-common",
  47. "framework",
  48. "android.policy",
  49. "services",
  50. "apache-xml"};
  51. public static final String[] HOST_BOOTCLASSPATH = new String[] {
  52. "core-hostdex",
  53. "conscrypt-hostdex",
  54. "okhttp-hostdex",
  55. "bouncycastle-hostdex",
  56. "apache-xml-hostdex",
  57. };
  58. private final Log log;
  59. private final Mkdir mkdir;
  60. private final File[] androidClasses;
  61. public final DeviceFilesystem deviceFilesystem;
  62. private Md5Cache dexCache;
  63. private Md5Cache pushCache;
  64. public static Collection<File> defaultExpectations() {
  65. File[] files = new File("libcore/expectations").listFiles(new FilenameFilter() {
  66. // ignore obviously temporary files
  67. public boolean accept(File dir, String name) {
  68. return !name.endsWith("~") && !name.startsWith(".");
  69. }
  70. });
  71. return (files != null) ? Arrays.asList(files) : Collections.<File>emptyList();
  72. }
  73. public AndroidSdk(Log log, Mkdir mkdir, ModeId mode) {
  74. this.log = log;
  75. this.mkdir = mkdir;
  76. this.deviceFilesystem = new DeviceFilesystem(log, "adb", "shell");
  77. List<String> path = new Command(log, "which", "adb").execute();
  78. if (path.isEmpty()) {
  79. throw new RuntimeException("adb not found");
  80. }
  81. File adb = new File(path.get(0)).getAbsoluteFile();
  82. String parentFileName = adb.getParentFile().getName();
  83. /*
  84. * We probably get aapt/adb/dx from either a copy of the Android SDK or a copy
  85. * of the Android source code. In either case, all three tools are in the same
  86. * directory as each other.
  87. *
  88. * Android SDK >= v9 (gingerbread):
  89. * <sdk>/platform-tools/aapt
  90. * <sdk>/platform-tools/adb
  91. * <sdk>/platform-tools/dx
  92. * <sdk>/platforms/android-?/android.jar
  93. *
  94. * Android build tree:
  95. * <source>/out/host/linux-x86/bin/aapt
  96. * <source>/out/host/linux-x86/bin/adb
  97. * <source>/out/host/linux-x86/bin/dx
  98. * <source>/out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar
  99. */
  100. if ("platform-tools".equals(parentFileName)) {
  101. File sdkRoot = adb.getParentFile().getParentFile();
  102. File newestPlatform = getNewestPlatform(sdkRoot);
  103. log.verbose("using android platform: " + newestPlatform);
  104. androidClasses = new File[] { new File(newestPlatform, "android.jar") };
  105. log.verbose("using android sdk: " + sdkRoot);
  106. } else if ("bin".equals(parentFileName)) {
  107. File sourceRoot = adb.getParentFile().getParentFile()
  108. .getParentFile().getParentFile().getParentFile();
  109. log.verbose("using android build tree: " + sourceRoot);
  110. String pattern = "out/target/common/obj/JAVA_LIBRARIES/%s_intermediates/classes.jar";
  111. if (mode == ModeId.HOST) {
  112. pattern = "out/host/common/obj/JAVA_LIBRARIES/%s-hostdex_intermediates/classes.jar";
  113. }
  114. androidClasses = new File[BOOTCLASSPATH.length];
  115. for (int i = 0; i < BOOTCLASSPATH.length; i++) {
  116. String jar = BOOTCLASSPATH[i];
  117. androidClasses[i] = new File(sourceRoot, String.format(pattern, jar));
  118. }
  119. } else {
  120. throw new RuntimeException("Couldn't derive Android home from " + adb);
  121. }
  122. }
  123. /**
  124. * Returns the platform directory that has the highest API version. API
  125. * platform directories are named like "android-9" or "android-11".
  126. */
  127. private File getNewestPlatform(File sdkRoot) {
  128. File newestPlatform = null;
  129. int newestPlatformVersion = 0;
  130. for (File platform : new File(sdkRoot, "platforms").listFiles()) {
  131. try {
  132. int version = Integer.parseInt(platform.getName().substring("android-".length()));
  133. if (version > newestPlatformVersion) {
  134. newestPlatform = platform;
  135. newestPlatformVersion = version;
  136. }
  137. } catch (NumberFormatException ignore) {
  138. // Ignore non-numeric preview versions like android-Honeycomb
  139. }
  140. }
  141. return newestPlatform;
  142. }
  143. public static Collection<File> defaultSourcePath() {
  144. return filterNonExistentPathsFrom("libcore/support/src/test/java",
  145. "external/mockwebserver/src/main/java/");
  146. }
  147. public static Collection<File> defaultResourceClassPath() {
  148. return filterNonExistentPathsFrom("libcore/dom/src/test/resources",
  149. "libcore/support/src/test/java/tests/resources",
  150. "libcore/luni/src/test/etc/loading-test-jar/resources",
  151. "libcore/luni/src/test/etc/loading-test2-jar/resources",
  152. "libcore/luni/src/test/resources");
  153. }
  154. private static Collection<File> filterNonExistentPathsFrom(String... paths) {
  155. ArrayList<File> result = new ArrayList<File>();
  156. String buildRoot = System.getenv("ANDROID_BUILD_TOP");
  157. for (String path : paths) {
  158. File file = new File(buildRoot, path);
  159. if (file.exists()) {
  160. result.add(file);
  161. }
  162. }
  163. return result;
  164. }
  165. public File[] getAndroidClasses() {
  166. return androidClasses;
  167. }
  168. public void setCaches(HostFileCache hostFileCache, DeviceFileCache deviceCache) {
  169. this.dexCache = new Md5Cache(log, "dex", hostFileCache);
  170. this.pushCache = new Md5Cache(log, "pushed", deviceCache);
  171. }
  172. /**
  173. * Converts all the .class files on 'classpath' into a dex file written to 'output'.
  174. */
  175. public void dex(File output, Classpath classpath) {
  176. mkdir.mkdirs(output.getParentFile());
  177. String key = dexCache.makeKey(classpath);
  178. if (key != null) {
  179. boolean cacheHit = dexCache.getFromCache(output, key);
  180. if (cacheHit) {
  181. log.verbose("dex cache hit for " + classpath);
  182. return;
  183. }
  184. }
  185. /*
  186. * We pass --core-library so that we can write tests in the
  187. * same package they're testing, even when that's a core
  188. * library package. If you're actually just using this tool to
  189. * execute arbitrary code, this has the unfortunate
  190. * side-effect of preventing "dx" from protecting you from
  191. * yourself.
  192. *
  193. * Memory options pulled from build/core/definitions.mk to
  194. * handle large dx input when building dex for APK.
  195. */
  196. new Command.Builder(log)
  197. .args("dx")
  198. .args("-JXms16M")
  199. .args("-JXmx1536M")
  200. .args("--dex")
  201. .args("--output=" + output)
  202. .args("--core-library")
  203. .args((Object[]) Strings.objectsToStrings(classpath.getElements())).execute();
  204. dexCache.insert(key, output);
  205. }
  206. public void packageApk(File apk, File manifest) {
  207. List<String> aapt = new ArrayList<String>(Arrays.asList("aapt",
  208. "package",
  209. "-F", apk.getPath(),
  210. "-M", manifest.getPath(),
  211. "-I", "prebuilts/sdk/current/android.jar"));
  212. for (File jar : androidClasses) {
  213. aapt.add("-I");
  214. aapt.add(jar.getPath());
  215. }
  216. new Command(log, aapt).execute();
  217. }
  218. public void addToApk(File apk, File dex) {
  219. new Command(log, "aapt", "add", "-k", apk.getPath(), dex.getPath()).execute();
  220. }
  221. public void mv(File source, File destination) {
  222. new Command(log, "adb", "shell", "mv", source.getPath(), destination.getPath()).execute();
  223. }
  224. public void rm(File name) {
  225. new Command(log, "adb", "shell", "rm", "-r", name.getPath()).execute();
  226. }
  227. public void cp(File source, File destination) {
  228. // adb doesn't support "cp" command directly
  229. new Command(log, "adb", "shell", "cat", source.getPath(), ">", destination.getPath())
  230. .execute();
  231. }
  232. public void pull(File remote, File local) {
  233. new Command(log, "adb", "pull", remote.getPath(), local.getPath()).execute();
  234. }
  235. public void push(File local, File remote) {
  236. Command fallback = new Command(log, "adb", "push", local.getPath(), remote.getPath());
  237. deviceFilesystem.mkdirs(remote.getParentFile());
  238. // don't yet cache directories (only used by jtreg tests)
  239. if (pushCache != null && local.isFile()) {
  240. String key = pushCache.makeKey(local);
  241. boolean cacheHit = pushCache.getFromCache(remote, key);
  242. if (cacheHit) {
  243. log.verbose("device cache hit for " + local);
  244. return;
  245. }
  246. fallback.execute();
  247. pushCache.insert(key, remote);
  248. } else {
  249. fallback.execute();
  250. }
  251. }
  252. public void install(File apk) {
  253. new Command(log, "adb", "install", "-r", apk.getPath()).execute();
  254. }
  255. public void uninstall(String packageName) {
  256. new Command(log, "adb", "uninstall", packageName).execute();
  257. }
  258. public void forwardTcp(int port) {
  259. new Command(log, "adb", "forward", "tcp:" + port, "tcp:" + port).execute();
  260. }
  261. public void remount() {
  262. new Command(log, "adb", "remount").execute();
  263. }
  264. public void waitForDevice() {
  265. new Command(log, "adb", "wait-for-device").execute();
  266. }
  267. /**
  268. * Loop until we see a non-empty directory on the device. For
  269. * example, wait until /sdcard is mounted.
  270. */
  271. public void waitForNonEmptyDirectory(File path, int timeoutSeconds) {
  272. waitFor(false, path, timeoutSeconds);
  273. }
  274. private void waitFor(boolean file, File path, int timeoutSeconds) {
  275. final int millisPerSecond = 1000;
  276. final long start = System.currentTimeMillis();
  277. final long deadline = start + (millisPerSecond * timeoutSeconds);
  278. while (true) {
  279. final int remainingSeconds =
  280. (int) ((deadline - System.currentTimeMillis()) / millisPerSecond);
  281. String pathArgument = path.getPath();
  282. if (!file) {
  283. pathArgument += "/";
  284. }
  285. Command command = new Command(log, "adb", "shell", "ls", pathArgument);
  286. List<String> output;
  287. try {
  288. output = command.executeWithTimeout(remainingSeconds);
  289. } catch (TimeoutException e) {
  290. throw new RuntimeException("Timed out after " + timeoutSeconds
  291. + " seconds waiting for file " + path, e);
  292. }
  293. try {
  294. Thread.sleep(millisPerSecond);
  295. } catch (InterruptedException e) {
  296. throw new RuntimeException(e);
  297. }
  298. if (file) {
  299. // for files, we expect one line of output that matches the filename
  300. if (output.size() == 1 && output.get(0).equals(path.getPath())) {
  301. return;
  302. }
  303. } else {
  304. // for a non empty directory, we just want any output
  305. if (!output.isEmpty()) {
  306. return;
  307. }
  308. }
  309. }
  310. }
  311. }