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