10eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com/*
20eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com * Copyright (C) 2010 The Android Open Source Project
30eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com *
40eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com * Licensed under the Apache License, Version 2.0 (the "License");
50eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com * you may not use this file except in compliance with the License.
60eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com * You may obtain a copy of the License at
70eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com *
80eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com *      http://www.apache.org/licenses/LICENSE-2.0
90eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com *
100eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com * Unless required by applicable law or agreed to in writing, software
110eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com * distributed under the License is distributed on an "AS IS" BASIS,
120eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com * See the License for the specific language governing permissions and
140eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com * limitations under the License.
150eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com */
160eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
170eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.compackage vogar.android;
180eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
197029503206e4c89fe167c3389c1062d89cf35c52Paul Duffinimport com.google.common.annotations.VisibleForTesting;
200eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.comimport com.google.common.collect.ImmutableList;
210eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.comimport java.io.File;
220eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.comimport java.io.FileNotFoundException;
230eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.comimport java.util.List;
247029503206e4c89fe167c3389c1062d89cf35c52Paul Duffinimport java.util.concurrent.TimeoutException;
250eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.comimport java.util.regex.Matcher;
260eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.comimport java.util.regex.Pattern;
277029503206e4c89fe167c3389c1062d89cf35c52Paul Duffinimport vogar.Log;
287029503206e4c89fe167c3389c1062d89cf35c52Paul Duffinimport vogar.Md5Cache;
290eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.comimport vogar.Target;
300eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.comimport vogar.commands.Command;
310eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
320eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.compublic final class AdbTarget extends Target {
330eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
34411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin    private static final ImmutableList<String> TARGET_PROCESS_PREFIX =
35411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            ImmutableList.of("adb", "shell");
36411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
377029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    private final Log log;
387029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin
397029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    private final DeviceFilesystem deviceFilesystem;
407029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin
417029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    private final Md5Cache pushCache;
427029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin
437029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin   @VisibleForTesting
447029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin   public AdbTarget(Log log, DeviceFilesystem deviceFilesystem, DeviceFileCache deviceFileCache) {
457029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin       this.log = log;
467029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin       this.deviceFilesystem = deviceFilesystem;
477029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin       this.pushCache =
487029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin               deviceFileCache == null ? null : new Md5Cache(log, "pushed", deviceFileCache);
490eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
500eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
517029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    public static File defaultDeviceDir() {
52a3baab9f2576bcdebd5f16647bcb3f0f80621e58Neil Fuller        return new File("/data/local/tmp/vogar");
530eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
540eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
55411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin    @Override protected ImmutableList<String> targetProcessPrefix() {
56411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        return TARGET_PROCESS_PREFIX;
570eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
580eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
59ab130e4323645a4ce682eb2b871b6b22ca2e5d3aNicolas Geoffray    @Override public void await(File directory) {
607029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        waitForDevice();
617029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        ensureDirectory(directory);
627029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        remount();
637029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    }
647029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin
657029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    private void waitForDevice() {
667029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        new Command.Builder(log)
677029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            .args("adb", "wait-for-device")
687029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            .permitNonZeroExitStatus(true)
697029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            .execute();
707029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    }
717029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin
727029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    /**
737029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin     * Make sure the directory exists.
747029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin     */
757029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    private void ensureDirectory(File directory) {
767029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        String pathArgument = directory.getPath() + "/";
777029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        if (pathArgument.equals("/sdcard/")) {
787029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            // /sdcard is a mount point. If it exists but is empty we do
797029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            // not want to use it. So we wait until it is not empty.
807029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            waitForNonEmptyDirectory(pathArgument, 5 * 60);
817029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        } else {
827029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            Command command = new Command.Builder(log)
837029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                .args("adb", "shell", "ls", pathArgument)
847029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                .permitNonZeroExitStatus(true)
857029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                .build();
867029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            List<String> output = command.execute();
877029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            // TODO: We should avoid checking for the error message, and instead have
887029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            // the Command class understand a non-zero exit code from an adb shell command.
897029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            if (!output.isEmpty()
907029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                && output.get(0).equals(pathArgument + ": No such file or directory")) {
917029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                throw new RuntimeException("'" + pathArgument + "' does not exist on device");
927029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            }
937029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            // Otherwise the directory exists.
947029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        }
957029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    }
967029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin
977029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    private void remount() {
987029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        new Command(log, "adb", "remount").execute();
997029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    }
1007029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin
1017029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin    private void waitForNonEmptyDirectory(String pathArgument, int timeoutSeconds) {
1027029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        final int millisPerSecond = 1000;
1037029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        final long start = System.currentTimeMillis();
1047029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        final long deadline = start + (millisPerSecond * timeoutSeconds);
1057029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin
1067029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        while (true) {
1077029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            final int remainingSeconds =
1087029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                    (int) ((deadline - System.currentTimeMillis()) / millisPerSecond);
1097029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            Command command = new Command.Builder(log)
1107029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                    .args("adb", "shell", "ls", pathArgument)
1117029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                    .permitNonZeroExitStatus(true)
1127029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                    .build();
1137029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            List<String> output;
1147029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            try {
1157029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                output = command.executeWithTimeout(remainingSeconds);
1167029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            } catch (TimeoutException e) {
1177029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                throw new RuntimeException("Timed out after " + timeoutSeconds
1187029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                        + " seconds waiting for " + pathArgument, e);
1197029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            }
1207029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            try {
1217029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                Thread.sleep(millisPerSecond);
1227029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            } catch (InterruptedException e) {
1237029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                throw new RuntimeException(e);
1247029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            }
1257029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin
1267029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            // We just want any output.
1277029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            if (!output.isEmpty()) {
1287029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                return;
1297029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            }
1307029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin
1317029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            log.warn("Waiting on " + pathArgument + " to be mounted ");
1327029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        }
1330eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
1340eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
1350eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    @Override public List<File> ls(File directory) throws FileNotFoundException {
1367029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        return deviceFilesystem.ls(directory);
1370eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
1380eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
1390eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    @Override public String getDeviceUserName() {
1400eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com        // The default environment doesn't include $USER, so dalvikvm doesn't set "user.name".
1417029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        // DeviceRuntime uses this to set "user.name" manually with -D.
1427029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        String line = new Command(log, "adb", "shell", "id").execute().get(0);
143cc4bfe8e88d686c6fc28624c2b6a7a7e434beb77Spencer Low        // TODO: use 'id -un' when we don't need to support anything older than M
144cc4bfe8e88d686c6fc28624c2b6a7a7e434beb77Spencer Low        Matcher m = Pattern.compile("^uid=\\d+\\((\\S+)\\) gid=\\d+\\(\\S+\\).*").matcher(line);
1450eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com        return m.matches() ? m.group(1) : "root";
1460eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
1470eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
1480eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    @Override public void rm(File file) {
1497029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        new Command.Builder(log).args("adb", "shell", "rm", "-r", file.getPath())
1507029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                // Note: When all supported versions of Android correctly return the exit code
1517029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                // from adb we can rely on the exit code to detect failure. Until then: no.
1527029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                .permitNonZeroExitStatus(true)
1537029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                .execute();
1540eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
1550eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
1560eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    @Override public void mkdirs(File file) {
1577029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        deviceFilesystem.mkdirs(file);
1580eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
1590eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
1600eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    @Override public void forwardTcp(int port) {
1617029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        new Command(log, "adb", "forward", "tcp:" + port, "tcp:" + port).execute();
1620eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
1630eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
1640eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    @Override public void push(File local, File remote) {
1657029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        Command fallback = new Command(log, "adb", "push", local.getPath(), remote.getPath());
1667029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        deviceFilesystem.mkdirs(remote.getParentFile());
1677029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin
1687029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        // don't yet cache directories (only used by jtreg tests)
1697029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        if (pushCache != null && local.isFile()) {
1707029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            String key = pushCache.makeKey(local);
1717029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            boolean cacheHit = pushCache.getFromCache(remote, key);
1727029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            if (cacheHit) {
1737029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                log.verbose("device cache hit for " + local);
1747029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin                return;
1757029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            }
1767029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            fallback.execute();
1777029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            pushCache.insert(key, remote);
1787029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        } else {
1797029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin            fallback.execute();
1807029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        }
1810eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
1820eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
1830eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    @Override public void pull(File remote, File local) {
1847029503206e4c89fe167c3389c1062d89cf35c52Paul Duffin        new Command(log, "adb", "pull", remote.getPath(), local.getPath()).execute();
1850eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
1860eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com}
187