OSDN Git Service

original
[gb-231r1-is01/GB_2.3_IS01.git] / cts / tools / host / src / com / android / cts / ConsoleUi.java
diff --git a/cts/tools/host/src/com/android/cts/ConsoleUi.java b/cts/tools/host/src/com/android/cts/ConsoleUi.java
new file mode 100644 (file)
index 0000000..c9b0e1d
--- /dev/null
@@ -0,0 +1,1329 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts;
+
+import com.android.cts.TestHost.ActionType;
+import com.android.cts.TestHost.MODE;
+
+import org.xml.sax.SAXException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+
+/**
+ * Main console of CTS providing user with the interface to interact. <BR>
+ * Using CommandParser to parse command line argument and process.
+ * <ul>
+ *    <li> start a test plan
+ *    <li> remove a test plan
+ *    <li> add a test package
+ *    <li> remove a test package
+ *    <li> list current available devices
+ *    <li> list current available test plan
+ *    <li> list current available package
+ *    <li> list current test result
+ *    <li> view CTS' status, uninitialized, idle or running
+ *    <li> view command history
+ *    <li> select a history command to run
+ * </ul>
+ */
+public class ConsoleUi {
+
+    private static final String OS_NAME_LINUX = "Linux";
+    private static final String LS_PLAN_SEPARATOR = "=================================";
+    private static final String CMD_TYPE_LEADING_SPACE = "  ";
+    private static final String CMD_OPT_LEADING_SPACE = "    ";
+    private static final String CREATE_SESSION = "create a new session";
+    private static final String CHOOSE_SESSION = "choose a session";
+
+    private TestHost mHost;
+    private boolean mKeepRunning;
+    private BufferedReader mCommandInput;
+    // private static ConsoleInputStream sConsoleReader;
+    private CommandHistory mCommandHistory = new CommandHistory();
+    private String mOsName = "none";
+
+    // Define test case name pattern
+    static final String CASE_NAME_PATTERN_STR = "((\\S+\\.)+\\S+)\\.(\\S+):(\\S+)";
+    private static HashMap<String, Integer> mResultCodeMap;
+
+    static {
+        mResultCodeMap = new HashMap<String, Integer>();
+        mResultCodeMap.put(CtsTestResult.STR_PASS, CtsTestResult.CODE_PASS);
+        mResultCodeMap.put(CtsTestResult.STR_FAIL, CtsTestResult.CODE_FAIL);
+        mResultCodeMap.put(CtsTestResult.STR_ERROR, CtsTestResult.CODE_ERROR);
+        mResultCodeMap.put(CtsTestResult.STR_NOT_EXECUTED, CtsTestResult.CODE_NOT_EXECUTED);
+        mResultCodeMap.put(CtsTestResult.STR_TIMEOUT, CtsTestResult.CODE_TIMEOUT);
+        mResultCodeMap.put(CtsTestResult.STR_OMITTED, CtsTestResult.CODE_OMITTED);
+    }
+
+    public ConsoleUi(TestHost host) {
+        mHost = host;
+        mCommandInput = new BufferedReader(new InputStreamReader(System.in));
+        mKeepRunning = true;
+
+        initInputStream();
+    }
+
+    /**
+     * Start the console user interface.
+     *
+     */
+    public void startUi() {
+        while (mKeepRunning) {
+            try {
+                String cmdLine = readLine(CUIOutputStream.CTS_PROMPT_SIGN);
+                CommandParser cp = CommandParser.parse(cmdLine);
+                processCommand(cp);
+                mCommandHistory.addCommand(cp, cmdLine);
+            } catch (CommandNotFoundException e) {
+                // avoid displaying help message for empty
+                // command by pressing ENTER over console directly
+            } catch (Exception e) {
+                Log.e("Got exception while processing command.", e);
+                showHelp();
+            }
+        }
+    }
+
+    /**
+     * Initialize the CommandProcessor.
+     */
+    private void initInputStream() {
+        // mOsName = System.getProperty("os.name");
+        if (mOsName.equals(OS_NAME_LINUX)) {
+            // sConsoleReader = new ConsoleInputStream(new FileInputStream(
+            // FileDescriptor.in), mCommandHistory);
+            // sConsoleReader.setup();
+        } else {
+            mCommandInput = new BufferedReader(new InputStreamReader(System.in));
+        }
+    }
+
+    /**
+     * Read a message line from console.
+     *
+     * @param prompt The notification message print out to console before reading.
+     * @return The string user typed in.
+     */
+    private String readLine(String prompt) throws IOException {
+        String cmdLine = null;
+        if (mOsName.equals(OS_NAME_LINUX)) {
+            // cmdLine = sConsoleReader.readLine(prompt).trim();
+        } else {
+            CUIOutputStream.print(prompt);
+            cmdLine = mCommandInput.readLine().trim();
+        }
+        return cmdLine;
+    }
+
+    /**
+     * Display the help message.
+     */
+    private void showHelp() {
+        CUIOutputStream.println("Usage: command options");
+        CUIOutputStream.println("Avaiable commands and options:");
+        showHostCmdHelp();
+        showPlanCmdHelp();
+        showPackageCmdHelp();
+        showResultCmdHelp();
+        showHistoryCmdHelp();
+        showDeviceCmdHelp();
+    }
+
+    /**
+     * Display the help message related to history commands.
+     */
+    private void showHistoryCmdHelp() {
+        final String cmdStr = CTSCommand.HISTORY + "/" + CTSCommand.H;
+
+        CUIOutputStream.println(CMD_TYPE_LEADING_SPACE + "History:");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + ": list all commands in command history");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + " count: list the latest count records in command history");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + " " + CTSCommand.OPTION_E
+                + " num: run the command designated by 'num' in command history");
+    }
+
+    /**
+     * Display the help message related to result commands.
+     */
+    private void showResultCmdHelp() {
+        final String cmdStr = CTSCommand.LIST + " " + CTSCommand.OPTION_R
+                + "/" + CTSCommand.OPTION_RESULT;
+        final String sessionStr = CTSCommand.OPTION_S + "/" + CTSCommand.OPTION_SESSION;
+        final String resultsStr = " [" + CtsTestResult.STR_PASS
+                       + "/" + CtsTestResult.STR_FAIL
+                       + "/" + CtsTestResult.STR_NOT_EXECUTED
+                       + "/" + CtsTestResult.STR_TIMEOUT
+                       + "] ";
+
+        CUIOutputStream.println(CMD_TYPE_LEADING_SPACE + "Result:");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + ": list all result of sessions");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + " " + sessionStr
+                + " session_id: list detail case result of a specified session");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + resultsStr + sessionStr
+                + " session_id: list detail cases of a specified"
+                + " session by the specified result.");
+    }
+
+    /**
+     * Display the help message related to package commands.
+     */
+    private void showPackageCmdHelp() {
+        final String cmdStr = CTSCommand.LIST + " " + CTSCommand.OPTION_P
+                + "/" + CTSCommand.OPTION_PACKAGE;
+        final String pkgStr = CTSCommand.OPTION_P + "/" + CTSCommand.OPTION_PACKAGE;
+
+        CUIOutputStream.println(CMD_TYPE_LEADING_SPACE + "Package:");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + ": list available packages");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE + cmdStr + " package_name: "
+                + "list contents of the package with specified name");
+
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + CTSCommand.ADD + " " + pkgStr
+                + " root: add packages from root to repository");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + CTSCommand.REMOVE + " " + pkgStr + " package_name/all: "
+                + "remove a package or all packages from repository");
+    }
+
+    /**
+     * Display the help message related to plan commands.
+     */
+    private void showPlanCmdHelp() {
+        final String lsPlanStr = CTSCommand.LIST + " " + CTSCommand.OPTION_PLAN;
+        final String addPlanStr = CTSCommand.ADD + " " + CTSCommand.OPTION_PLAN;
+        final String rmPlanStr = CTSCommand.REMOVE + " " + CTSCommand.OPTION_PLAN;
+        final String addDerivedPlanStr = CTSCommand.ADD + " " + CTSCommand.OPTION_DERIVED_PLAN;
+
+        CUIOutputStream.println(CMD_TYPE_LEADING_SPACE + "Plan:");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + lsPlanStr + ": list available plans");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + lsPlanStr + " plan_name: list contents of the plan with specified name");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + addPlanStr + " plan_name: add a new plan with specified name");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + addDerivedPlanStr + " plan_name "
+                + CTSCommand.OPTION_S + "/" + CTSCommand.OPTION_SESSION + " session_id "
+                + CTSCommand.OPTION_R + "/" + CTSCommand.OPTION_RESULT + " result_type"
+                + ": derive a plan from the given session");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + rmPlanStr + " plan_name/all: remove a plan or all plans from repository");
+        showStartSessionHelp();
+    }
+
+    /**
+     * Display the help message related to start session command.
+     */
+    private void showStartSessionHelp() {
+        final String cmdStr = CTSCommand.START + " " + CTSCommand.OPTION_PLAN;
+        final String testStr = CTSCommand.OPTION_T + "/" + CTSCommand.OPTION_TEST;
+        final String deviceStr = CTSCommand.OPTION_D + "/" + CTSCommand.OPTION_DEVICE;
+        final String pkgStr = CTSCommand.OPTION_P + "/" + CTSCommand.OPTION_PACKAGE;
+
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + " test_plan_name: run a test plan");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + " test_plan_name " + deviceStr + " device_ID"
+                +  ": run a test plan using the specified device");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + " test_plan_name " + testStr + " test_name"
+                + ": run a specific test");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + " test_plan_name " + pkgStr + " java_package_name"
+                + ": run a specific java package");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + " test_plan_name " + testStr + " test_name "
+                + deviceStr + " device_ID"
+                + ": run a specific test using the specified device");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + cmdStr + " test_plan_name " + pkgStr + " java_package_name "
+                + deviceStr + " device_ID"
+                + ": run a specific java package using the specified device");
+    }
+
+    /**
+     * Display the help message related to host commands.
+     */
+    private void showHostCmdHelp() {
+        CUIOutputStream.println(CMD_TYPE_LEADING_SPACE + "Host:");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + CTSCommand.HELP + ": show this message");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + CTSCommand.EXIT + ": exit cts command line");
+    }
+
+    /**
+     * Display the help message related to device commands.
+     */
+    private void showDeviceCmdHelp() {
+        final String deviceStr = CTSCommand.OPTION_D + "/" + CTSCommand.OPTION_DEVICE;
+
+        CUIOutputStream.println(CMD_TYPE_LEADING_SPACE + "Device:");
+        CUIOutputStream.println(CMD_OPT_LEADING_SPACE
+                + CTSCommand.LIST + " " + deviceStr + ": list available devices");
+    }
+
+    /**
+     * Process the command from user's input.
+     *
+     * @param cp Command container.
+     */
+    public void processCommand(final CommandParser cp) throws Exception {
+        String action = cp.getAction();
+
+        if (action.equals(CTSCommand.EXIT)) {
+            if (cp.getArgSize() != 1) {
+                showHelp();
+                return;
+            }
+            Log.d("exit cts host");
+            mKeepRunning = false;
+            mHost.tearDown();
+        } else if (action.equals(CTSCommand.HELP)) {
+            showHelp();
+        } else if (mCommandHistory.isHistoryCommand(action)) {
+            processHistoryCommands(cp);
+        } else if (action.equals(CTSCommand.ADD)) {
+            processAddCommand(cp);
+        } else if (action.equals(CTSCommand.START)) {
+            processStartCommand(cp);
+        } else if (action.equals(CTSCommand.REMOVE)) {
+            processRmCommand(cp);
+        } else if (action.equals(CTSCommand.LIST)) {
+            processListCommand(cp);
+        } else {
+            showHelp();
+        }
+    }
+
+    /**
+     * Process start command.
+     *
+     * @param cp Command container.
+     */
+    private void processStartCommand(CommandParser cp) throws SAXException,
+            ParserConfigurationException {
+        if (cp.containsKey(CTSCommand.OPTION_PLAN)) {
+            processStartSessionCommand(cp);
+        } else if (cp.containsKey(CTSCommand.OPTION_P)
+                || cp.containsKey(CTSCommand.OPTION_PACKAGE)) {
+            processStartPackageCommand(cp);
+        } else {
+            showHelp();
+        }
+    }
+
+    /**
+     * Process start package command.
+     *
+     * <ul>
+     *     <li> Syntax:
+     *            start --package zipped-package-file
+     * </ul>
+     * @param cp Command container.
+     */
+    private void processStartPackageCommand(CommandParser cp) {
+        try {
+            String pathName = cp.getValue(CTSCommand.OPTION_PACKAGE);
+            mHost.startZippedPackage(pathName);
+        } catch (DeviceDisconnectedException e) {
+          Log.e("Device " + e.getMessage() + " disconnected", e);
+        } catch (Exception e) {
+            Log.e("Met exception during running zipped package.", e);
+        }
+    }
+
+    /**
+     * Validate the command parameters used to activate CTS.
+     *
+     * @param cp Command container.
+     * @return If command parameters are valid, return true; else, return false.
+     */
+    public boolean validateCommandParams(CommandParser cp) {
+        if (cp == null) {
+            return false;
+        }
+
+        if (cp.getAction() == null) {
+            return true;
+        } else if (isValidCommandOption(cp, CTSCommand.START,
+                CTSCommand.OPTION_PLAN)) {
+            return true;
+        } else if (isValidCommandOption(cp, CTSCommand.START,
+                CTSCommand.OPTION_PACKAGE)) {
+            return true;
+        } else if (isValidCommandOption(cp, CTSCommand.START,
+                CTSCommand.OPTION_P)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Check if the command option is valid.
+     *
+     * @param cp CommandParser which contains the command and options.
+     * @param command Command the user typed in.
+     * @param option Option the user typed in.
+     * @return If command option valid, return true; else, return false.
+     */
+    private static boolean isValidCommandOption(CommandParser cp,
+            String command, String option) {
+        return (cp.getAction().equals(command)) && (cp.containsKey(option))
+                && (cp.getValue(option) != null)
+                && (cp.getValue(option).length() != 0);
+    }
+
+    /**
+     * Process start session command.
+     * <ul>
+     *     <li> Syntax 1:
+     *            start --plan plan-name
+     *              [ --device device-id ]
+     *              [ --test test-name ]
+     *              [ --profile profile-name ]
+     *     <li> Syntax 2:
+     *            start --plan plan-name
+     *              [ --device device-id ]
+     *              [ --package java-package-name ]
+     *              [ --profile profile-name ]
+     * </ul>
+     * @param cp container which contained start command options and values
+     *           Process the list commands.
+     */
+    private void processStartSessionCommand(CommandParser cp)
+            throws SAXException, ParserConfigurationException {
+
+        if (mHost.getDeviceList().length == 0) {
+            Log.e("No device connected", null);
+            return;
+        }
+
+        String testPlanPath = null;
+        String deviceId = null;
+        String testName = null;
+        String javaPkgName = null;
+        String testPlanName = mHost.getPlanName(cp.getValue(CTSCommand.OPTION_PLAN));
+        try {
+            if (cp.getActionValues().size() != 0 || cp.getOptionSize() < 1
+                    || cp.getOptionSize() > 3) {
+                showStartSessionHelp();
+                return;
+            }
+            testPlanName = mHost.getPlanName(cp
+                    .getValue(CTSCommand.OPTION_PLAN));
+            testPlanPath = HostConfig.getInstance().getPlanRepository()
+                    .getPlanPath(testPlanName);
+            if (testPlanPath == null) {
+                CUIOutputStream.println("Plan " + testPlanName
+                        + " is not in repository, please create it!");
+                return;
+            }
+
+            ActionType actionType = ActionType.START_NEW_SESSION;
+            if (cp.containsKey(CTSCommand.OPTION_TEST)) {
+                testName = cp.getValue(CTSCommand.OPTION_TEST);
+                if (-1 == testName.indexOf(Test.METHOD_SEPARATOR)) {
+                    Log.e("Test full name must be in the form of:"
+                            + " java_package_name.class_name#method_name.", null);
+                    return;
+                }
+                actionType = ActionType.RUN_SINGLE_TEST;
+            } else if (cp.containsKey(CTSCommand.OPTION_PACKAGE)) {
+                javaPkgName = cp.getValue(CTSCommand.OPTION_PACKAGE);
+                actionType = ActionType.RUN_SINGLE_JAVA_PACKAGE;
+            }
+
+            TestSession ts = null;
+            ArrayList<TestSession> sessionList = mHost.getSessionList(testPlanName);
+            if ((sessionList != null) && (sessionList.size() > 0)) {
+                if ((testName == null) || (testName.length() == 0)) {
+                    String mode = chooseMode(sessionList);
+                    if (CREATE_SESSION.equals(mode)) {
+                        ts = TestHost.createSession(testPlanName);
+                    }
+                }
+                if (ts == null) {
+                    ts = chooseTestSession(sessionList);
+                    deviceId = ts.getDeviceId();
+                    if ((actionType != ActionType.RUN_SINGLE_TEST)
+                            && (actionType != ActionType.RUN_SINGLE_JAVA_PACKAGE)) {
+                        actionType = ActionType.RESUME_SESSION;
+                    }
+                }
+            }
+
+            if (cp.containsKey(CTSCommand.OPTION_DEVICE)) {
+                deviceId = cp.getValue(CTSCommand.OPTION_DEVICE);
+                String[] deviceIdList = deviceId.trim().split(",");
+                if (deviceIdList.length > 1) {
+                    Log.e("Just allow choosing one device ID.", null);
+                    return;
+                }
+            }
+
+            if (deviceId == null) {
+                TestDevice td = mHost.getFirstAvailableDevice();
+                if (td == null) {
+                    // no devices attached
+                    CUIOutputStream.println("No idle devices found.");
+                    return;
+                }
+                deviceId = td.getSerialNumber();
+            }
+
+            if (!checkDeviceExists(mHost.getDeviceList(), deviceId)) {
+                CUIOutputStream.println("Can't find specified device id.  Is it attached?");
+                return;
+            }
+
+            if (ts == null) {
+                ts = TestHost.createSession(testPlanName);
+            }
+
+            mHost.startSession(ts, deviceId, testName, javaPkgName, actionType);
+        } catch (IOException e) {
+            Log.e("Can't create test session", e);
+        } catch (DeviceNotAvailableException e) {
+            CUIOutputStream.println("Test plan(" + testPlanName + ") "
+                    + e.getMessage());
+            showStartSessionHelp();
+        } catch (TestNotFoundException e) {
+            CUIOutputStream.println(e.getMessage());
+        } catch (TestPlanNotFoundException e) {
+            CUIOutputStream.println("Can't find test plan " + testPlanName);
+        } catch (IllegalTestNameException e) {
+            CUIOutputStream.println("Illegal case name: " + testName);
+        } catch (DeviceDisconnectedException e) {
+            Log.e("Device " + e.getMessage() + " disconnected ", null);
+        } catch (NoSuchAlgorithmException e) {
+            Log.e("Fail to initialise SHA-1 algorithm", e);
+        } catch (InvalidApkPathException e) {
+            Log.e(e.getMessage(), null);
+        } catch (InvalidNameSpaceException e) {
+            Log.e(e.getMessage(), null);
+        }
+    }
+
+    /**
+     * Choose test session among the available test session list.
+     *
+     * @param sessionList The available test session list.
+     * @return The test session chosen.
+     */
+    private TestSession chooseTestSession(ArrayList<TestSession> sessionList) throws IOException {
+        if ((sessionList == null) || (sessionList.size() == 0)) {
+            return null;
+        }
+
+        if (sessionList.size() == 1) {
+            return sessionList.get(0);
+        }
+
+        int index = 0;
+        String notification = "Please choose a session from the existed session(s):\n";
+        for (TestSession session : sessionList) {
+            notification += "  " + session.getId() + "  [" + index + "] \n";
+            index ++;
+        }
+
+        return sessionList.get(getUserInputId(notification, 0, index));
+    }
+
+    /**
+     * Choose  between creating a new session and choosing a session among available ones.
+     *
+     * @param sessionList The available test session list.
+     * @return If choose to create a new session, return CREATE_SESSION;
+     *         else return CHOOSE_SESSION.
+     */
+    private String chooseMode(ArrayList<TestSession> sessionList) throws IOException {
+        if (TestHost.sMode == MODE.RUN || (sessionList == null) || (sessionList.size() == 0)) {
+            // do not prompt if the test run was started from command line mode, or when
+            // there are no existing sessions
+            return CREATE_SESSION;
+        }
+
+        String planName = sessionList.get(0).getSessionLog().getTestPlanName();
+        String notification = "There are " + sessionList.size()
+            + " existing session(s) for plan " + planName + ".\n"
+            + "Create a new session or choose an existing one?\n"
+            + "  Create a new session [0]\n"
+            + "  Choose a session     [1]\n";
+
+        int indexSelected = getUserInputId(notification, 0, 2);
+        if (indexSelected == 0) {
+            return CREATE_SESSION;
+        } else {
+            return CHOOSE_SESSION;
+        }
+    }
+
+    /**
+     * Validate the specified device ID against the available device array.
+     *
+     * @param availableDevices The available device array.
+     * @param specifiedId The specified device ID list.
+     * @return true if the id is valid
+     */
+    public boolean checkDeviceExists(TestDevice[] availableDevices, String specifiedId) {
+        for (TestDevice dev : availableDevices) {
+            if (specifiedId.equals(dev.getSerialNumber())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get device ID from the device ID string against the available devices.
+     *
+     * @param availableDevices The available devices.
+     * @param idStr The device ID string.
+     * @return The device ID.
+     */
+    public int getDeviceId(TestDevice[] availableDevices, String idStr) {
+        for (int i = 0; i < availableDevices.length; i++) {
+            TestDevice dev = availableDevices[i];
+            if (idStr.equals(dev.getSerialNumber())) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Get the ID input by the against the specified range.
+     *
+     * @param notification The notification message to notify the user.
+     * @param startIndex The start index.
+     * @param endIndex The end index.
+     * @return The selected index of the ID the user chosen.
+     */
+    private int getUserInputId(String notification, int startIndex, int endIndex)
+                throws IOException {
+        int indexSelected = 0;
+        boolean success = false;
+        while (!success) {
+            String answer = readLine(notification);
+            try {
+                indexSelected = Integer.parseInt(answer);
+                if ((indexSelected >= 0) && (indexSelected < endIndex)) {
+                    success = true;
+                } else {
+                    CUIOutputStream.println("" + indexSelected
+                            + " is out of range [0," + (endIndex -1 ) + "].");
+                }
+            } catch (NumberFormatException e) {
+                CUIOutputStream.println("Invalid nuber is typed in.");
+            }
+        }
+        return indexSelected;
+    }
+
+    /**
+     * Check if the specified device ID is valid.
+     *
+     * @param numOfAvailableDevices The number of available devices.
+     * @param specifiedId The specified device ID.
+     * @return If the specified ID contained in available ID list,
+     *         return true; else, return false.
+     */
+    public boolean isValidDeviceId(int numOfAvailableDevices, int specifiedId) {
+        if (specifiedId < 0 || specifiedId >= numOfAvailableDevices) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Process list commands.
+     * <ul>
+     *     <li> Syntax 1:
+     *            ls --device
+     *     <li> Syntax 2:
+     *            ls --plan [ plan-name ]
+     *     <li> Syntax 3:
+     *            ls --package [ package-name ]
+     *     <li> Syntax 4:
+     *            ls --result
+     *              [ pass/fail/notExecuted/timeout ]
+     *              [ --session session_id ]
+     * </ul>
+     *
+     * @param cp Command container.
+     */
+    private void processListCommand(CommandParser cp) throws SAXException,
+            IOException, ParserConfigurationException {
+        if (cp.containsKey(CTSCommand.OPTION_DEVICE)) {
+            if (cp.getActionValues().size() != 0 || cp.getOptionSize() != 1) {
+                showDeviceCmdHelp();
+                return;
+            }
+            if (cp.getValue(CTSCommand.OPTION_DEVICE).equals("")) {
+                listDevices();
+            } else {
+                showDeviceCmdHelp();
+            }
+        } else if (cp.containsKey(CTSCommand.OPTION_PLAN)) {
+            if (cp.getActionValues().size() != 0 || cp.getOptionSize() != 1) {
+                showPlanCmdHelp();
+                return;
+            }
+            String planValue = cp.getValue(CTSCommand.OPTION_PLAN);
+            if (planValue.equals("")) {
+                listPlans();
+            } else {
+                listSinglePlan(mHost.getPlanName(planValue));
+            }
+        } else if (cp.containsKey(CTSCommand.OPTION_RESULT)) {
+            if (cp.getActionValues().size() != 0
+                    || (cp.getOptionSize() < 1 || cp.getOptionSize() > 2)) {
+                showResultCmdHelp();
+                return;
+            }
+            String resultValue = cp.getValue(CTSCommand.OPTION_RESULT);
+            String sessionId = cp.getValue(CTSCommand.OPTION_SESSION);
+            Integer resultCode = null;
+
+            if (sessionId != null) {
+                if (resultValue.length() != 0
+                        && !mResultCodeMap.containsKey(resultValue)) {
+                    showResultCmdHelp();
+                } else {
+                    resultCode = mResultCodeMap.get(resultValue);
+                    listSessionResult(sessionId, resultCode);
+                }
+            } else if (resultValue.length() == 0) {
+                listResults();
+            } else {
+                showHelp();
+            }
+        } else if (cp.containsKey(CTSCommand.OPTION_PACKAGE)) {
+            if (cp.getActionValues().size() != 0 || cp.getOptionSize() != 1) {
+                showPackageCmdHelp();
+                return;
+            }
+            listPackages(cp);
+        } else {
+            showHelp();
+        }
+    }
+
+    /**
+     * Process the removing commands.
+     * <ul>
+     *     <li> Syntax 1:
+     *            rm --plan [ plan-name ] [ all ]
+     *     <li> Syntax 2:
+     *            rm --package [ package-name ] [ all ]
+     * </ul>
+     *
+     * @param cp Command container.
+     */
+    private void processRmCommand(CommandParser cp) throws IOException {
+        if (cp.containsKey(CTSCommand.OPTION_PLAN)) {
+            if (cp.getActionValues().size() != 0 || cp.getOptionSize() != 1) {
+                showPlanCmdHelp();
+                return;
+            }
+
+            String planName = mHost.getPlanName(cp.getValue(CTSCommand.OPTION_PLAN));
+            if (HostConfig.ALL.equals(planName)) {
+                String prompt = "Remove all of the plans?([y/N])";
+                String answer = readLine(prompt).trim();
+                if (!isConfirmation(answer, false)) {
+                    return;
+                }
+            }
+
+            mHost.removePlans(planName);
+        } else if (cp.containsKey(CTSCommand.OPTION_PACKAGE)) {
+            if (cp.getActionValues().size() != 0 || cp.getOptionSize() != 1) {
+                showPackageCmdHelp();
+                return;
+            }
+
+            String packageName = cp.getValue(CTSCommand.OPTION_PACKAGE);
+            if (HostConfig.ALL.equals(packageName)) {
+                String prompt = "Remove all of the packages?([y/N])";
+                String answer = readLine(prompt).trim();
+                if (!isConfirmation(answer, false)) {
+                    return;
+                }
+            }
+
+            mHost.removePackages(packageName);
+        } else {
+            showHelp();
+        }
+    }
+
+    /**
+     * Check if the answer is confirmation.
+     *
+     * @param answer The answer user typed in.
+     * @param defaultResult If true, default to yes; else, default to no.
+     * @return If confirmation, return true; else, return false.
+     */
+    public static boolean isConfirmation(String answer, boolean defaultResult) {
+        if ("".equals(answer)) {
+            return defaultResult;
+        }
+
+        return ("y".equals(answer.toLowerCase()) || "yes".equals(answer.toLowerCase()));
+    }
+
+    /**
+     * Process the add commands.
+     * <ul>
+     *     <li> Syntax 1:
+     *            add --plan plan-name
+     *     <li> Syntax 2:
+     *            add --package package-name
+     * </ul>
+     *
+     * @param cp Command container.
+     */
+    private void processAddCommand(CommandParser cp) {
+        if (cp.containsKey(CTSCommand.OPTION_PLAN)) {
+            if (isValidAddPlanArguments(cp)) {
+                createPlan(cp, CTSCommand.OPTION_PLAN);
+            } else {
+                showPlanCmdHelp();
+            }
+        } else if (cp.containsKey(CTSCommand.OPTION_DERIVED_PLAN)) {
+            if (isValidDerivedPlanArguments(cp)) {
+                createPlan(cp, CTSCommand.OPTION_DERIVED_PLAN);
+            } else {
+                showPlanCmdHelp();
+            }
+        } else if (cp.containsKey(CTSCommand.OPTION_PACKAGE)) {
+            try {
+                addPackage(cp);
+            } catch (IOException e) {
+                Log.e("Can't add package", e);
+            } catch (IndexOutOfBoundsException e) {
+                Log.e("Can't add package", e);
+            } catch (NoSuchAlgorithmException e) {
+                Log.e("Can't add package", e);
+            }
+        } else {
+            showHelp();
+        }
+    }
+
+    /**
+     * Check if it's valid arguments for adding plan.
+     *
+     * @param cp The command processor.
+     * @return if valid, return true; else, return false.
+     */
+    private boolean isValidAddPlanArguments(CommandParser cp) {
+        return (cp.getArgSize() == 3) && (cp.getActionValues().size() == 0)
+                && (cp.getOptionSize() == 1);
+    }
+
+    /**
+     * Check if it's valid arguments for deriving plan.
+     *
+     * @param cp The command processor.
+     * @return if valid, return true; else, return false.
+     */
+    private boolean isValidDerivedPlanArguments(CommandParser cp) {
+        //argument size: it's at least 3, as "add --plan plan_name"
+        //action values: no option contains more than one value
+        //option size: it's at least 1, as "add --plan plan_name"
+        return (cp.getArgSize() >= 3) && (cp.getActionValues().size() == 0)
+                && (cp.getOptionSize() >= 1);
+    }
+
+    /**
+     * Process the history commands.
+     * <ul>
+     *     <li> Syntax:
+     *            history [ -e ] [ number]
+     * </ul>
+     *
+     * @param cp Command container.
+     */
+    private void processHistoryCommands(final CommandParser cp)
+            throws Exception {
+        try {
+            if ((cp.getOptionSize() == 0) && (cp.getActionValues().size() == 0)) {
+                mCommandHistory.show(mCommandHistory.size());
+            } else if (cp.containsKey(CTSCommand.OPTION_E)
+                    && (cp.getActionValues().size() == 0)) {
+                int cmdNum = 0;
+                cmdNum = Integer.parseInt(cp.getValue(CTSCommand.OPTION_E));
+                if (cmdNum >= 0 && cmdNum < mCommandHistory.size()) {
+                    String cmdLine = mCommandHistory.get(cmdNum);
+                    CommandParser cpH = CommandParser.parse(cmdLine);
+                    CUIOutputStream.printPrompt();
+                    CUIOutputStream.println(cmdLine);// print(CTS_PROMPT_SIGN
+                                                        // + cmdLine);
+                    processCommand(cpH);
+                    mCommandHistory.addCommand(cpH, cmdLine);
+                } else {
+                    if (mCommandHistory.size() > 0) {
+                        Log.e("Command index " + cmdNum
+                                + " is out of command history range [0,"
+                                + (mCommandHistory.size() - 1) + "].", null);
+                    } else {
+                        Log.e("No command exists in command history.", null);
+                    }
+                }
+            } else if ((cp.getOptionSize() == 0)
+                    && (cp.getActionValues().size() == 1)) {
+                int cmdCount = Integer.parseInt(cp.getActionValues().iterator()
+                        .next());
+                if (cmdCount < 0 || cmdCount > mCommandHistory.size()) {
+                    cmdCount = mCommandHistory.size();
+                }
+                mCommandHistory.show(cmdCount);
+            } else {
+                showHistoryCmdHelp();
+            }
+
+        } catch (NumberFormatException e) {
+            showHistoryCmdHelp();
+        }
+    }
+
+    /**
+     * List a single plan by the plan name given.
+     *
+     * @param name The plan name.
+     */
+    private void listSinglePlan(String name) throws SAXException, IOException,
+            ParserConfigurationException {
+        String planName = null;
+        for (String str : mHost.getPlanRepository().getAllPlanNames()) {
+            if (str.startsWith(name)) {
+                planName = str;
+                break;
+            }
+        }
+
+        if (planName == null) {
+            Log.e("No plan named " + name + " in repository!", null);
+            return;
+        }
+
+        String planPath = mHost.getPlanRepository().getPlanPath(planName);
+        ArrayList<String> removedPkgList = new ArrayList<String>();
+        Collection<String> pkgNames = TestPlan.getEntries(planPath, removedPkgList);
+
+        if (removedPkgList.size() != 0) {
+            CUIOutputStream.println("The following package(s) contained in plan "
+                    + planName + " have been removed:");
+            for (String pkgName : removedPkgList) {
+                CUIOutputStream.println("    " + pkgName);
+            }
+        }
+
+        if (pkgNames.size() > 0) {
+            CUIOutputStream.println("Packages of plan " + planName
+                    + " (" + pkgNames.size() + " in total):");
+            CUIOutputStream.println(LS_PLAN_SEPARATOR);
+            for (String pkgName : pkgNames) {
+                CUIOutputStream.println(pkgName);
+            }
+        }
+    }
+
+    /**
+     * Create test plan via the test session and result type given.
+     *
+     * @param name The test plan name.
+     * @param ts The test session.
+     * @param resultType The result type.
+     */
+    private void createPlanFromSession(final String name, TestSession ts, final String resultType)
+            throws FileNotFoundException, IOException, ParserConfigurationException,
+            TransformerFactoryConfigurationError, TransformerException {
+
+        HashMap<String, ArrayList<String>> selectedResult =
+            new HashMap<String, ArrayList<String>>();
+        ArrayList<String> packageNames = new ArrayList<String>();
+
+        for (TestPackage pkg : ts.getSessionLog().getTestPackages()) {
+            String pkgName = pkg.getAppPackageName();
+            ArrayList<String> excludedList = pkg.getExcludedList(resultType);
+            if (excludedList != null) {
+                packageNames.add(pkgName);
+                selectedResult.put(pkgName, excludedList);
+            }
+        }
+
+        if ((selectedResult != null) && (selectedResult.size() > 0)) {
+            TestSessionBuilder.getInstance().serialize(name, packageNames, selectedResult);
+        } else {
+            if (resultType == null) {
+                Log.i("All tests of session " + ts.getId()
+                        + " have passed execution. The plan is not created!");
+            } else {
+                Log.i("No " + resultType +  " tests of session " + ts.getId()
+                        + ". The plan is not created!");
+            }
+        }
+    }
+
+    /**
+     * Add a derived plan from a given session.
+     *
+     * @param cp Command container.
+     * @param name The plan name.
+     * @param packageNames The package name list.
+     */
+    private void addDerivedPlan(final CommandParser cp, final String name,
+            ArrayList<String> packageNames) {
+
+        try {
+            String sessionId = null;
+            String resultType = null;
+            int id = TestSession.getLastSessionId();
+
+            if (cp.containsKey(CTSCommand.OPTION_SESSION)) {
+                sessionId = cp.getValue(CTSCommand.OPTION_SESSION);
+                id = Integer.parseInt(sessionId);
+            }
+            TestSession ts = mHost.getSession(id);
+            if (ts == null) {
+                Log.e("The session ID of " + id + " doesn't exist.", null);
+                return;
+            }
+
+            if (cp.containsKey(CTSCommand.OPTION_RESULT)) {
+                resultType = cp.getValue(CTSCommand.OPTION_RESULT);
+                if (!CtsTestResult.isValidResultType(resultType)) {
+                    Log.e("The following result type is invalid: " + resultType, null);
+                    return;
+                }
+            }
+            createPlanFromSession(name, ts, resultType);
+        } catch (Exception e) {
+            Log.e("Got exception while trying to add a plan!", e);
+            return;
+        }
+    }
+
+    /**
+     * Add a plan by the plan name given.
+     *
+     * @param cp Command container.
+     * @param name The plan name.
+     * @param packageNames The package name list.
+     */
+    private void addPlan(final CommandParser cp, final String name,
+            ArrayList<String> packageNames) {
+
+        try {
+            PlanBuilder planBuilder = new PlanBuilder(packageNames);
+
+            if (mOsName.equals(OS_NAME_LINUX)) {
+                // planBuilder.setInputStream(sConsoleReader);
+            } else {
+                planBuilder.setInputStream(mCommandInput);
+            }
+
+            HashMap<String, ArrayList<String>> selectedResult = planBuilder.doSelect();
+            if (selectedResult != null) {
+                TestSessionBuilder.getInstance().serialize(name, packageNames, selectedResult);
+            } else {
+                Log.i("Selected nothing for the plan of " + name + ". The plan is not created!");
+            }
+        } catch (Exception e) {
+            Log.e("Got exception while trying to add a plan!", e);
+            return;
+        }
+    }
+
+    /**
+     * Create a plan.
+     *
+     * @param cp Command container.
+     * @param type the action type.
+     */
+    private void createPlan(final CommandParser cp, final String type) {
+        String name = null;
+        if (CTSCommand.OPTION_PLAN.equals(type)) {
+            name = cp.getValue(CTSCommand.OPTION_PLAN);
+        } else if (CTSCommand.OPTION_DERIVED_PLAN.equals(type)) {
+            name = cp.getValue(CTSCommand.OPTION_DERIVED_PLAN);
+        } else {
+            return;
+        }
+
+        if (HostUtils.isFileExist(HostConfig.getInstance().getPlanRepository()
+                .getPlanPath(name)) == true) {
+            Log.e("Plan " + name + " already exist, please use another name!", null);
+            return;
+        }
+
+        try {
+            if ((name != null) && (!name.matches("\\w+"))) {
+                CUIOutputStream.println("Only letter of the alphabet, number and '_'"
+                        + " are available for test plan name");
+                return;
+            }
+
+            ArrayList<String> packageNames =
+                HostConfig.getInstance().getCaseRepository().getPackageNames();
+            Collection<TestPackage> testPackages = HostConfig.getInstance().getTestPackages();
+            if (testPackages.size() == 0) {
+                CUIOutputStream.println("No package found in repository, please add package first!");
+                return;
+            }
+            if (CTSCommand.OPTION_PLAN.equals(type)) {
+                addPlan(cp, name, packageNames);
+            } else if (CTSCommand.OPTION_DERIVED_PLAN.equals(type)) {
+                addDerivedPlan(cp, name, packageNames);
+            }
+        } catch (Exception e) {
+            Log.e("Got exception while trying to add a plan!", e);
+            return;
+        }
+    }
+
+    /**
+     * List all of the plans in the plan repository.
+     */
+    private void listPlans() {
+        ArrayList<String> plans = mHost.getPlanRepository().getAllPlanNames();
+
+        if (plans.size() == 0) {
+            CUIOutputStream.println("No plan created!");
+        } else {
+            CUIOutputStream.println("List of plans (" + plans.size() + " in total):");
+            for (String name : plans) {
+                CUIOutputStream.println(name);
+            }
+        }
+    }
+
+    /**
+     * List detailed case result of specified session. The result can be
+     * filtered, if resultType isn't null, by the specified resultType.
+     *
+     * @param idStr the session id.
+     * @param resultType the type of result, [pass, fail, notExecuted, timeout, null].
+     */
+    private void listSessionResult(final String idStr, final Integer resultType) {
+        if (!idStr.matches("\\d+")) {
+            showResultCmdHelp();
+            return;
+        }
+
+        int sessionId = Integer.parseInt(idStr);
+
+        TestSession ts = mHost.getSession(sessionId);
+        if (null == ts) {
+            Log.e("Can't find specified session", null);
+            return;
+        }
+
+        TestSessionLog log = ts.getSessionLog();
+        CUIOutputStream.println("Result of session " + ts.getId());
+        CUIOutputStream.println("Result\t\tCase name");
+        CUIOutputStream
+                .println("==============================================================");
+        for (Test test : log.getAllResults()) {
+            CtsTestResult result = test.getResult();
+            if ((resultType != null) && (result.getResultCode() != resultType.intValue())) {
+                continue;
+            }
+            CUIOutputStream.println(result.getResultString() + "\t\t"
+                    + test.getFullName());
+        }
+    }
+
+    /**
+     * List all of the test results.
+     */
+    private void listResults() {
+        Collection<TestSession> sessions = mHost.getSessions();
+        if (sessions.isEmpty()) {
+            CUIOutputStream.println("There aren't any test results!");
+        } else {
+            CUIOutputStream.println("List of all results: ");
+            CUIOutputStream.println("Session\t\tTest result\t\t\t\t\tStart time\t\tEnd time\t"
+                    + "\tTest plan name\t");
+            CUIOutputStream.println("\t\tPass\tFail\tTimeout\tOmitted\tNotExecuted");
+
+            for (TestSession session : sessions) {
+                TestSessionLog log = session.getSessionLog();
+                int passNum = log.getTestList(
+                        CtsTestResult.CODE_PASS).size();
+                int failNum = log.getTestList(
+                        CtsTestResult.CODE_FAIL).size();
+                int omittedNum = log.getTestList(
+                        CtsTestResult.CODE_OMITTED).size();
+                int notExecutedNum = log.getTestList(
+                        CtsTestResult.CODE_NOT_EXECUTED).size();
+                int timeOutNum = log.getTestList(
+                        CtsTestResult.CODE_TIMEOUT).size();
+
+                String resStr = Long.toString(passNum) + "\t" + failNum;
+                resStr += "\t" + timeOutNum;
+                resStr += "\t" + omittedNum;
+                resStr += "\t" + notExecutedNum;
+
+                String startTimeStr =
+                    HostUtils.getFormattedTimeString(log.getStartTime().getTime(), " ", ".", ":");
+                String endTimeStr =
+                    HostUtils.getFormattedTimeString(log.getEndTime().getTime(), " ", ".", ":");
+                CUIOutputStream.println(Long.toString(session.getId()) + "\t\t"
+                        + resStr + "\t\t" + startTimeStr
+                        + "\t" + endTimeStr
+                        + "\t" + log.getTestPlanName());
+
+            }
+        }
+    }
+
+    /**
+     * Add a package by the path and package name.
+     *
+     * @param cp Command container.
+     */
+    private void addPackage(final CommandParser cp) throws IOException,
+            IndexOutOfBoundsException, NoSuchAlgorithmException {
+        if (cp.getActionValues().size() != 0 || cp.getOptionSize() != 1) {
+            showPackageCmdHelp();
+            return;
+        }
+        String pathName = cp.getValue(CTSCommand.OPTION_PACKAGE);
+        mHost.addPackage(pathName);
+    }
+
+    /**
+     * List current package in the case repository.
+     *
+     * @param cp Command container
+     */
+    private void listPackages(final CommandParser cp) {
+        // walk through the case root path
+        // and list available packages
+        String expectPackage = cp.getValue(CTSCommand.OPTION_PACKAGE);
+        String caseRoot = mHost.getCaseRepository().getRoot();
+        if (caseRoot == null) {
+            Log.e("Case repository is null", null);
+            return;
+        }
+
+        File root = new File(caseRoot);
+        if (!root.isDirectory()) {
+            Log.e("Case repository must be a directory!", null);
+            return;
+        }
+
+        Collection<TestPackage> testPackages = HostConfig.getInstance().getTestPackages();
+
+        if (testPackages.size() == 0) {
+            CUIOutputStream
+                    .println("No package available under case repository!");
+        } else {
+            if (expectPackage.equals("")) {
+                CUIOutputStream.println("Available packages ("
+                        + testPackages.size() + " in total):");
+                for (TestPackage pkg : testPackages) {
+                    CUIOutputStream.println(pkg.getAppPackageName());
+                }
+            } else {
+                List<ArrayList<String>> list = mHost.getCaseRepository()
+                        .listAvailablePackage(expectPackage);
+                ArrayList<String> packageList = list.get(0);
+                ArrayList<String> suiteList = list.get(1);
+                ArrayList<String> caseList = list.get(2);
+                ArrayList<String> testList = list.get(3);
+                if ((packageList.size() == 0) && (suiteList.size() == 0)
+                        && (caseList.size() == 0) && (testList.size() == 0)) {
+                    CUIOutputStream
+                            .println("Not available test package, suite, cases or tests: "
+                                    + expectPackage);
+                } else {
+                    if (packageList.size() != 0) {
+                        CUIOutputStream.println(
+                                "Test packages (" + packageList.size() + " in total):");
+                        for (String packageName : packageList) {
+                            CUIOutputStream.println(packageName);
+                        }
+                    }
+                    if (suiteList.size() != 0) {
+                        CUIOutputStream.println(
+                                "Test suites (" + suiteList.size() + " in total):");
+                        for (String suiteName : suiteList) {
+                            CUIOutputStream.println(suiteName);
+                        }
+                    }
+                    if (caseList.size() != 0) {
+                        CUIOutputStream.println("Test cases (" + caseList.size() + " in total):");
+                        for (String caseName : caseList) {
+                            CUIOutputStream.println(caseName);
+                        }
+                    }
+                    if (testList.size() != 0) {
+                        CUIOutputStream.println("Tests (" + testList.size() + " in total):");
+                        for (String testName : testList) {
+                            CUIOutputStream.println(testName);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * List all of the devices connected.
+     */
+    private void listDevices() {
+        String[] deviceNames = mHost.listDevices();
+        if (deviceNames.length == 0) {
+            CUIOutputStream.println("No device connected.");
+            return;
+        }
+
+        CUIOutputStream.println("Id\t\tDevice Name\t\tStatus");
+
+        for (int i = 0; i < deviceNames.length; i++) {
+            CUIOutputStream.println(i + "\t\t" + deviceNames[i]);
+        }
+    }
+}