[Из песочницы] Взаимодейтсвие Java и Shell-скриптов в Android

Так сложилось, что в моём текущем проекте необходимо было реализовать выполнение shell-скриптов прямиком из кода.

Для того, чтобы войти в курс дела, советую вам прочитать эту статью: Shell-скриптинг в среде Android

В ней очень хорошо описаны возможности языка Shell, однако мне помимо самих скриптов нужно было выполнять методы Java.
В процессе разработки был использован образ Android_x86. Однако можно использовать рутованный телефон (наличие прав суперпользователя обязательно).

Сами скрипты из Java выполняются довольно просто:

    /**
     * Метод выполняет скрипты shell в отдельном потоке.
     *
     * @param command shell скрипт.
     */
    public void runCommand(final String command) {

        // Чтобы не вис интерфейс, запускаем в другом потоке
        new Thread(new Runnable() {
            public void run() {
                OutputStream out = null;
                InputStream in = null;
                try {
                   // Отправляем скрипт в рантайм процесс
                    Process child = Runtime.getRuntime().exec(command);
                    // Выходной и входной потоки
                    out = child.getOutputStream();
                    in = child.getInputStream();

                    //Входной поток может что-нибудь вернуть
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
                    String line;
                    String result = "";
                    while ((line = bufferedReader.readLine()) != null)
                        result += line;

                    //Обработка того, что он вернул
                    handleBashCommandsResult(result);

                } catch (IOException e) {             
                    e.printStackTrace();
                } finally {
                    if (in != null) {
                        try {
                            in.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (out != null) {
                        try {
                            out.flush();
                            out.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();
    }


В Android_x86 можно отправлять скрипты сразу в процесс, и они буду выполняться. Если использовать телефон, то вот эту строчку:

Process child = Runtime.getRuntime().exec(command);


нужно заменить на вот эти:

Process child = Runtime.getRuntime().exec(new String[] { "su", "-c", "system/bin/sh" });
DataOutputStream stdin = new DataOutputStream(child.getOutputStream());
//Скрипт
stdin.writeBytes(command);


Я не знаю, чем можно объяснить это, но на телефоне перед выполнением скриптов нужно запустить командную оболочку. В остальном всё одинаково.

Для примера напишем несколько shell-команд, которые потом можно будет собирать в скрипты:

  /**
     * Пауза между командами
     *
     * @param i секунд
     * @return команда
     */
    public static String doSleep(int i) {
        return "adb shell 'sleep " + i + "' ;";
    }

   /**
     * Жест свайп
     *
     * @param x1 откуда
     * @param y1 откуда
     * @param x2 куда
     * @param y2 куда
     * @return команда
     */
    public static String doSwipe(int x1, int y1, int x2, int y2) {
        return "adb shell input swipe " + x1 + " " + y1 + " " + x2 + " " + y2 + " ;";
    }

    /**
     * Жест тап
     *
     * @param x
     * @param y
     * @return команда
     */
    public static String doTap(int x, int y) {
        return "adb shell input tap " + x + " " + y + " ;";
    }

    /**
     * Ввод текста в поле ввода
     *
     * @param text текст
     * @return команда
     */
    public static String doInputText(String text) {
        return "adb shell input text " + text + " ;";
    }

    /**
     * Нажатие кнопки
     *
     * @param keycode код кнопки
     * @return команда
     */
    public static String doInputKeyevent(int keycode) {
        return "adb shell input keyevent " + keycode + " ;";
    }

    /**
     * Вывод сообщения (которое потом во входном потоке ловится)
     *
     * @param message сообщение
     * @return команда
     */
    public static String echo(String message) {
        return "echo '" + message + "' ;";
    }



Данные команды можно также комбинировать в различные скрипты:

    /**
     * Пример скрипта
     *
     * @return скрипт
     */
    public static String sampleScript(){
        String command = "";

        command = command
                .concat(doSwipe(100, 200, 100, 500))
                .concat(doSleep(1))
                .concat(doTap(100, 150))
                .concat(doSleep(1))
                .concat(doInputText("Я скрипт"))
                .concat(doSleep(1))
                .concat(doInputKeyevent(KeyEvent.KEYCODE_ENTER))
                .concat(doSleep(1))
                .concat(echo("SCRIPT_FINISHED"));
        return command;
    }

Вызвать данный скрипт очень просто:

runCommand(sampleScript());


Данный скрипт с интервалом в 1 секунду сначала свайпает, затем тапает, затем вводит текст, эмулирует нажатие клавиши Enter и затем посылает сообщение с сигналом о завершении.

Теперь самое интересное. Данный скрипт будет выполняться в фоне несколько секунд, поскольку в нём выставлены задержки. По его завершению во входном потоке будет это сообщение. В моём примере я преобразовываю его в строку, и после этого вызываю метод handleBashCommandsResult (result), который в качестве входного параметра и принимает эту строку. В этом методе результат можно сравнить

 /**
     * Обработка того, что вернул скрипт (возвращает он обычно ключевое слово командой echo)
     *
     * @param result ответ скрипта.
     */
    private void handleBashCommandsResult(String result) {

        if (result.contains("SCRIPT_FINISHED")) {
            //Здесь делаем всё что хотели сделать после завершение скрипта
        } else if (.....){
           //А вот здесь можно сделать что-нибудь после другого скрипта
        } else {
          //А вот здесь можно сделать всё остальное
        }
     }


На этом в общем то и всё. В методе handleBashCommandsResult можно выполнить, например, какие-нибудь проверки и запустить выполнение одного из нескольких других скриптов, в зависимости от результатов этой проверки. Так или иначе, я кратко описал, как наладить взаимодейтвие shell-скриптов и Java кода, что в общем-то и хотел.

Надеюсь, кому-нибудь это может пригодиться. Если остались какие-либо вопросы, я постараюсь вам ответить.

© Habrahabr.ru