/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Android SUTAgent code.
 *
 * The Initial Developer of the Original Code is
 * Bob Moss.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *  Bob Moss <bmoss@mozilla.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

package com.mozilla.SUTAgentAndroid.service;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.zip.Adler32;
import java.util.zip.CheckedInputStream;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import com.mozilla.SUTAgentAndroid.NtpMessage;
import com.mozilla.SUTAgentAndroid.R;
import com.mozilla.SUTAgentAndroid.SUTAgentAndroid;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Debug;
import android.os.Environment;
import android.os.StatFs;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;

public class DoCommand {

    String lineSep = System.getProperty("line.separator");
    Process    pProc;
    OutputStream sutIn;
    InputStream    sutErr;
    InputStream    sutOut;
    AlertLooperThread alrt = null;
    ContextWrapper    contextWrapper = null;

    String    currentDir = "/";
    String    sErrorPrefix = "##AGENT-WARNING## ";
    boolean bTraceOn = false;

    String ffxProvider = "org.mozilla.ffxcp";
    String fenProvider = "org.mozilla.fencp";

    private final String prgVersion = "SUTAgentAndroid Version 1.03";

    public enum Command
        {
        RUN ("run"),
        EXEC ("exec"),
        ENVRUN ("envrun"),
        KILL ("kill"),
        PS ("ps"),
        DEVINFO ("info"),
        OS ("os"),
        ID ("id"),
        UPTIME ("uptime"),
        SETTIME ("settime"),
        SYSTIME ("systime"),
        SCREEN ("screen"),
        MEMORY ("memory"),
        POWER ("power"),
        PROCESS ("process"),
        GETAPPROOT ("getapproot"),
        TESTROOT ("testroot"),
        ALRT ("alrt"),
        DISK ("disk"),
        CP ("cp"),
        TIME ("time"),
        HASH ("hash"),
        CD ("cd"),
        CAT ("cat"),
        CWD ("cwd"),
        MV ("mv"),
        PUSH ("push"),
        PULL ("pull"),
        RM ("rm"),
        PRUNE ("rmdr"),
        MKDR ("mkdr"),
        DIRWRITABLE ("dirw"),
        ISDIR ("isdir"),
        DEAD ("dead"),
        MEMS ("mems"),
        LS ("ls"),
        TMPD ("tmpd"),
        PING ("ping"),
        REBT ("rebt"),
        UNZP ("unzp"),
        ZIP ("zip"),
        CLOK ("clok"),
        STAT ("stat"),
        QUIT ("quit"),
        EXIT ("exit"),
        HELP ("help"),
        FTPG ("ftpg"),
        FTPP ("ftpp"),
        INST ("inst"),
        UPDT ("updt"),
        UNINST ("uninst"),
        TEST ("test"),
        DBG ("dbg"),
        TRACE ("trace"),
        VER ("ver"),
        TZGET ("tzget"),
        TZSET ("tzset"),
        ADB ("adb"),
        UNKNOWN ("unknown");

        private final String theCmd;

        Command(String theCmd) { this.theCmd = theCmd; }

        public String theCmd() {return theCmd;}

        public static Command getCmd(String sCmd)
            {
            Command retCmd = UNKNOWN;
            for (Command cmd : Command.values())
                {
                if (cmd.theCmd().equalsIgnoreCase(sCmd))
                    {
                    retCmd = cmd;
                    break;
                    }
                }
            return (retCmd);
            }
        }

    public DoCommand(ContextWrapper service)
        {
        this.contextWrapper = service;
        }

    public String processCommand(String theCmdLine, PrintWriter out, BufferedInputStream in, OutputStream cmdOut)
        {
        String     strReturn = "";
        Command    cCmd = null;
        Command cSubCmd = null;

        if (bTraceOn)
            ((ASMozStub)this.contextWrapper).SendToDataChannel(theCmdLine);

        String [] Argv = parseCmdLine2(theCmdLine);

        int Argc = Argv.length;

        cCmd = Command.getCmd(Argv[0]);

        switch(cCmd)
            {
            case TRACE:
                if (Argc == 2)
                    bTraceOn = (Argv[1].equalsIgnoreCase("on") ? true : false);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for trace command!";
                break;

            case VER:
                strReturn = prgVersion;
                break;

            case CLOK:
                strReturn = GetClok();
                break;

            case TZGET:
                strReturn = GetTimeZone();
                break;

            case TZSET:
                if (Argc == 2)
                    strReturn = SetTimeZone(Argv[1]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for settz command!";
                break;

            case UPDT:
                if (Argc >= 2)
                    strReturn = StrtUpdtOMatic(Argv[1], Argv[2], (Argc > 3 ? Argv[3] : null), (Argc > 4 ? Argv[4] : null));
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for updt command!";
                break;

            case SETTIME:
                strReturn = SetSystemTime(Argv[1], (Argc > 2 ? Argv[2] : null), cmdOut);
                break;

            case CWD:
                try {
                    strReturn = new java.io.File(currentDir).getCanonicalPath();
                    }
                catch (IOException e)
                    {
                    e.printStackTrace();
                    }
                break;

            case CD:
                if (Argc == 2)
                    strReturn = changeDir(Argv[1]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for cd command!";
                break;

            case LS:
                strReturn = PrintDir(((Argc > 1) ? Argv[1] : currentDir));
                break;

            case GETAPPROOT:
                if (Argc == 2)
                    strReturn = GetAppRoot(Argv[1]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for getapproot command!";
                break;

            case ISDIR:
                if (Argc == 2)
                    strReturn = isDirectory(Argv[1]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for isdir command!";
                break;

            case TESTROOT:
                strReturn = GetTestRoot();
                break;

            case DEAD:
                if (Argc == 2)
                    strReturn = (IsProcessDead(Argv[1]) ? (Argv[1] + " is hung or unresponsive") : (Argv[1] + " is ok"));
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for dead command!";
                break;

            case PS:
                strReturn = GetProcessInfo();
                break;

            case PULL:
                if (Argc >= 2) {
                    long lOff = 0;
                    long lLen = -1;
                    if (Argc > 2) {
                        try {
                            lOff = Long.parseLong(Argv[2].trim());
                            System.out.println("offset = " + lOff);
                        } catch (NumberFormatException nfe) {
                            lOff = 0;
                            System.out.println("NumberFormatException: " + nfe.getMessage());
                        }
                    }
                    if (Argc == 4) {
                        try {
                            lLen = Long.parseLong(Argv[3].trim());
                            System.out.println("length = " + lLen);
                        } catch (NumberFormatException nfe) {
                            lLen = -1;
                            System.out.println("NumberFormatException: " + nfe.getMessage());
                        }
                    }
                    strReturn = Pull(Argv[1], lOff, lLen, cmdOut);
                } else {
                    strReturn = sErrorPrefix + "Wrong number of arguments for pull command!";
                }
                break;

            case PUSH:
                if (Argc == 3)
                    {
                    long lArg = 0;
                    try
                        {
                        lArg = Long.parseLong(Argv[2].trim());
                        System.out.println("long l = " + lArg);
                        }
                    catch (NumberFormatException nfe)
                        {
                        System.out.println("NumberFormatException: " + nfe.getMessage());
                        }

                    strReturn = Push(Argv[1], in, lArg);
                    }
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for push command!";
                break;

            case INST:
                if (Argc >= 2)
                    strReturn = InstallApp(Argv[1], cmdOut);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for inst command!";
                break;

            case UNINST:
                if (Argc >= 2)
                    strReturn = UnInstallApp(Argv[1], cmdOut);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for inst command!";
                break;

            case ALRT:
                if (Argc > 1)
                    {
                    if (Argv[1].contentEquals("on"))
                        {
                        String sTitle = "Agent Alert";
                        String sMsg = "The Agent Alert System has been activated!";
                        if (Argc == 3) {
                            sTitle = Argv[2];
                            sMsg = "";
                        } else if (Argc == 4) {
                            sTitle = Argv[2];
                            sMsg = Argv[3];
                        }
                        StartAlert(sTitle, sMsg);
                        }
                    else
                        {
                        StopAlert();
                        }
                    }
                else
                    {
                    strReturn = sErrorPrefix + "Wrong number of arguments for alrt command!";
                    }
                break;

            case REBT:
                if (Argc >= 1)
                    strReturn = RunReboot(cmdOut, (Argc > 1 ? Argv[1] : null), (Argc > 2 ? Argv[2] : null));
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for rebt command!";
//                RunReboot(cmdOut);
                break;

            case TMPD:
                strReturn = GetTmpDir();
                break;

            case DEVINFO:
                if (Argc == 1)
                    {
                    strReturn += SUTAgentAndroid.sUniqueID;
                    strReturn += "\n";
                    strReturn += GetOSInfo();
                    strReturn += "\n";
                    strReturn += GetSystemTime();
                    strReturn += "\n";
                    strReturn += GetUptime();
                    strReturn += "\n";
                    strReturn += GetScreenInfo();
                    strReturn += "\n";
                    strReturn += GetMemoryInfo();
                    strReturn += "\n";
                    strReturn += GetPowerInfo();
                    strReturn += "\n";
                    strReturn += GetProcessInfo();
                    }
                else
                    {
                    cSubCmd = Command.getCmd(Argv[1]);
                    switch(cSubCmd)
                        {
                        case ID:
                            strReturn = SUTAgentAndroid.sUniqueID;
                            break;

                        case SCREEN:
                            strReturn = GetScreenInfo();
                            break;

                        case PROCESS:
                            strReturn = GetProcessInfo();
                            break;

                        case OS:
                            strReturn = GetOSInfo();
                            break;

                        case SYSTIME:
                            strReturn = GetSystemTime();
                            break;

                        case UPTIME:
                            strReturn = GetUptime();
                            break;

                        case MEMORY:
                            strReturn = GetMemoryInfo();
                            break;

                        case POWER:
                            strReturn += GetPowerInfo();
                            break;

                        default:
                            break;
                        }
                    }
                break;

            case STAT:
                if (Argc == 2)
                    strReturn = StatProcess(Argv[1]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for ping command!";
                break;

            case PING:
                if (Argc == 2)
                    strReturn = SendPing(Argv[1], cmdOut);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for ping command!";
                break;

            case HASH:
                if (Argc == 2)
                    strReturn = HashFile(Argv[1]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for hash command!";
                break;

            case PRUNE:
                if (Argc == 2)
                    strReturn = PruneDir(Argv[1]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for prune command!";
                break;

            case FTPG:
                if (Argc == 4)
                    strReturn = FTPGetFile(Argv[1], Argv[2], Argv[3], cmdOut);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for ftpg command!";
                break;

            case CAT:
                if (Argc == 2)
                    strReturn = Cat(Argv[1], cmdOut);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for cat command!";
                break;

            case DIRWRITABLE:
                if (Argc == 2)
                    strReturn = IsDirWritable(Argv[1]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for dirwritable command!";
                break;

            case TIME:
                if (Argc == 2)
                    strReturn = PrintFileTimestamp(Argv[1]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for time command!";
                break;

            case MKDR:
                if (Argc == 2)
                    strReturn = MakeDir(Argv[1]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for mkdr command!";
                break;

            case RM:
                if (Argc == 2)
                    strReturn = RemoveFile(Argv[1]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for rm command!";
                break;

            case MV:
                if (Argc == 3)
                    strReturn = Move(Argv[1], Argv[2]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for mv command!";
                break;

            case CP:
                if (Argc == 3)
                    strReturn = CopyFile(Argv[1], Argv[2]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for cp command!";
                break;

            case QUIT:
            case EXIT:
                strReturn = Argv[0];
                break;

            case DBG:
                Debug.waitForDebugger();
                strReturn = "waitForDebugger on";
                break;

            case ADB:
                if (Argc == 2) {
                    if (Argv[1].contains("ip") || Argv[1].contains("usb")) {
                        strReturn = SetADB(Argv[1]);
                    } else {
                        strReturn = sErrorPrefix + "Unrecognized argument for adb command!";
                    }
                } else {
                    strReturn = sErrorPrefix + "Wrong number of arguments for adb command!";
                }
                break;

            case TEST:
                long lFreeMemory = Runtime.getRuntime().freeMemory();
                long lTotMemory = Runtime.getRuntime().totalMemory();
                long lMaxMemory = Runtime.getRuntime().maxMemory();


                if (lFreeMemory > 0) {
                    strReturn = "Max memory: " + lMaxMemory + "\nTotal Memory: " + lTotMemory + "\nFree memory: " + lFreeMemory;
                    break;
                }

                ContentResolver cr = contextWrapper.getContentResolver();
                Uri ffxFiles = null;

                if (Argv[1].contains("fennec")) {
                    ffxFiles = Uri.parse("content://" + fenProvider + "/dir");
                } else if (Argv[1].contains("firefox")) {
                    ffxFiles = Uri.parse("content://" + ffxProvider + "/dir");
                }

//                Uri ffxFiles = Uri.parse("content://org.mozilla.fencp/file");
                String[] columns = new String[] {
                        "_id",
                        "isdir",
                        "filename",
                        "length"
                    };
//                String[] columns = new String[] {
//                        "_id",
//                        "chunk"
//                     };
                Cursor myCursor = cr.query(    ffxFiles,
                                            columns,                         // Which columns to return
                                            (Argc > 1 ? Argv[1] : null),    // Which rows to return (all rows)
                                            null,                           // Selection arguments (none)
                                            null);                            // Put the results in ascending order by name
/*
                if (myCursor != null) {
                    int nRows = myCursor.getCount();
                    String [] colNames = myCursor.getColumnNames();
                    int    nID = 0;
                    int nBytesRecvd = 0;

                    for (int lcv = 0; lcv < nRows; lcv++) {
                        if  (myCursor.moveToPosition(lcv)) {
                            nID = myCursor.getInt(0);
                            byte [] buf = myCursor.getBlob(1);
                            if (buf != null) {
                                nBytesRecvd += buf.length;
                                strReturn += new String(buf);
                                buf = null;
                            }
                        }
                    }
                    strReturn += "[eof - " + nBytesRecvd + "]";
                    myCursor.close();
                }

*/
                if (myCursor != null)
                    {
                    int nRows = myCursor.getCount();
                    int    nID = 0;
                    String sFileName = "";
                    long lFileSize = 0;
                    boolean bIsDir = false;

                    for (int lcv = 0; lcv < nRows; lcv++)
                        {
                        if  (myCursor.moveToPosition(lcv))
                            {
                            nID = myCursor.getInt(0);
                            bIsDir = (myCursor.getInt(1) == 1 ? true : false);
                            sFileName = myCursor.getString(2);
                            lFileSize = myCursor.getLong(3);
                            strReturn += "" + nID + "\t" + (bIsDir ? "<dir> " : "      ") + sFileName + "\t" + lFileSize + "\n";
                            }
                        }
                    myCursor.close();
                    }
                break;

            case EXEC:
            case ENVRUN:
                if (Argc >= 2)
                    {
                    String [] theArgs = new String [Argc - 1];

                    for (int lcv = 1; lcv < Argc; lcv++)
                        {
                        theArgs[lcv - 1] = Argv[lcv];
                        }

                    strReturn = StartPrg2(theArgs, cmdOut);
                    }
                else
                    {
                    strReturn = sErrorPrefix + "Wrong number of arguments for " + Argv[0] + " command!";
                    }
                break;

            case RUN:
                if (Argc >= 2)
                    {
                    String [] theArgs = new String [Argc - 1];

                    for (int lcv = 1; lcv < Argc; lcv++)
                        {
                        theArgs[lcv - 1] = Argv[lcv];
                        }

                    if (Argv[1].contains("/") || Argv[1].contains("\\") || !Argv[1].contains("."))
                        strReturn = StartPrg(theArgs, cmdOut);
                    else
                        strReturn = StartJavaPrg(theArgs, null);
                    }
                else
                    {
                    strReturn = sErrorPrefix + "Wrong number of arguments for " + Argv[0] + " command!";
                    }
                break;

            case KILL:
                if (Argc == 2)
                    strReturn = KillProcess(Argv[1], cmdOut);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for kill command!";
                break;

            case DISK:
                strReturn = GetDiskInfo((Argc == 2 ? Argv[1] : "/"));
                break;

            case UNZP:
                strReturn = Unzip(Argv[1], (Argc == 3 ? Argv[2] : ""));
                break;

            case ZIP:
                strReturn = Zip(Argv[1], (Argc == 3 ? Argv[2] : ""));
                break;

            case HELP:
                strReturn = PrintUsage();
                break;

            default:
                strReturn = sErrorPrefix + "[" + Argv[0] + "] command";
                if (Argc > 1)
                    {
                    strReturn += " with arg(s) =";
                    for (int lcv = 1; lcv < Argc; lcv++)
                        {
                        strReturn += " [" + Argv[lcv] + "]";
                        }
                    }
                strReturn += " is currently not implemented.";
                break;
            }

        return(strReturn);
        }

    private void SendNotification(String tickerText, String expandedText) {
        NotificationManager notificationManager = (NotificationManager)contextWrapper.getSystemService(Context.NOTIFICATION_SERVICE);
        int icon = R.drawable.ateamlogo;
        long when = System.currentTimeMillis();

        Notification notification = new Notification(icon, tickerText, when);

        notification.flags |= (Notification.FLAG_INSISTENT | Notification.FLAG_AUTO_CANCEL);
        notification.defaults |= Notification.DEFAULT_SOUND;
        notification.defaults |= Notification.DEFAULT_VIBRATE;
        notification.defaults |= Notification.DEFAULT_LIGHTS;

        Context context = contextWrapper.getApplicationContext();

        // Intent to launch an activity when the extended text is clicked
        Intent intent2 = new Intent(contextWrapper, SUTAgentAndroid.class);
        PendingIntent launchIntent = PendingIntent.getActivity(context, 0, intent2, 0);

        notification.setLatestEventInfo(context, tickerText, expandedText, launchIntent);

        notificationManager.notify(1959, notification);
    }

private void CancelNotification()
    {
    NotificationManager notificationManager = (NotificationManager)contextWrapper.getSystemService(Context.NOTIFICATION_SERVICE);
    notificationManager.cancel(1959);
    }

    public void StartAlert(String sTitle, String sMsg)
        {
        // start the alert message
        SendNotification(sTitle, sMsg);
        }

    public void StopAlert()
        {
        CancelNotification();
        }

    public String [] parseCmdLine2(String theCmdLine)
        {
        String    cmdString;
        String    workingString;
        String    workingString2;
        String    workingString3;
        List<String> lst = new ArrayList<String>();
        int nLength = 0;
        int nFirstSpace = -1;

        // Null cmd line
        if (theCmdLine == null)
            {
            String [] theArgs = new String [1];
            theArgs[0] = new String("");
            return(theArgs);
            }
        else
            {
            nLength = theCmdLine.length();
            nFirstSpace = theCmdLine.indexOf(' ');
            }

        if (nFirstSpace == -1)
            {
            String [] theArgs = new String [1];
            theArgs[0] = new String(theCmdLine);
            return(theArgs);
            }

        // Get the command
        cmdString = new String(theCmdLine.substring(0, nFirstSpace));
        lst.add(cmdString);

        // Jump past the command and trim
        workingString = (theCmdLine.substring(nFirstSpace + 1, nLength)).trim();

        while ((nLength = workingString.length()) > 0)
            {
            int nEnd = 0;
            int    nStart = 0;

            // if we have a quote
            if (workingString.startsWith("\""))
                {
                // point to the first non quote char
                nStart = 1;
                // find the matching quote
                nEnd = workingString.indexOf('"', nStart);

                char prevChar;

                while(nEnd != -1)
                    {
                    // check to see if the quotation mark has been escaped
                    prevChar = workingString.charAt(nEnd - 1);
                    if (prevChar == '\\')
                        {
                        // if escaped, point past this quotation mark and find the next
                        nEnd++;
                        if (nEnd < nLength)
                            nEnd = workingString.indexOf('"', nEnd);
                        else
                            nEnd = -1;
                        }
                    else
                        break;
                    }

                // there isn't one
                if (nEnd == -1)
                    {
                    // point at the quote
                    nStart = 0;
                    // so find the next space
                    nEnd = workingString.indexOf(' ', nStart);
                    // there isn't one of those either
                    if (nEnd == -1)
                        nEnd = nLength;    // Just grab the rest of the cmdline
                    }
                }
            else // no quote so find the next space
                {
                nEnd = workingString.indexOf(' ', nStart);
                // there isn't one of those
                if (nEnd == -1)
                    nEnd = nLength;    // Just grab the rest of the cmdline
                }

            // get the substring
            workingString2 = workingString.substring(nStart, nEnd);

            // if we have escaped quotes
            if (workingString2.contains("\\\""))
                {
                do
                    {
                    // replace escaped quote with embedded quote
                    workingString3 = workingString2.replace("\\\"", "\"");
                    workingString2 = workingString3;
                    }
                while(workingString2.contains("\\\""));
                }

            // add it to the list
            lst.add(new String(workingString2));

            // if we are dealing with a quote
            if (nStart > 0)
                nEnd++; //  point past the end one

            // jump past the substring and trim it
            workingString = (workingString.substring(nEnd)).trim();
            }

        // ok we're done package up the results
        int nItems = lst.size();

        String [] theArgs = new String [nItems];

        for (int lcv = 0; lcv < nItems; lcv++)
            {
            theArgs[lcv] = lst.get(lcv);
            }

        return(theArgs);
        }

    public String [] parseCmdLine(String theCmdLine) {
        String    cmdString;
        String    workingString;
        String    workingString2;
        List<String> lst = new ArrayList<String>();
        int nLength = 0;
        int nFirstSpace = -1;

        // Null cmd line
        if (theCmdLine == null)
            {
            String [] theArgs = new String [1];
            theArgs[0] = new String("");
            return(theArgs);
            }
        else
            {
            nLength = theCmdLine.length();
            nFirstSpace = theCmdLine.indexOf(' ');
            }

        if (nFirstSpace == -1)
            {
            String [] theArgs = new String [1];
            theArgs[0] = new String(theCmdLine);
            return(theArgs);
            }

        // Get the command
        cmdString = new String(theCmdLine.substring(0, nFirstSpace));
        lst.add(cmdString);

        // Jump past the command and trim
        workingString = (theCmdLine.substring(nFirstSpace + 1, nLength)).trim();

        while ((nLength = workingString.length()) > 0)
            {
            int nEnd = 0;
            int    nStart = 0;

            // if we have a quote
            if (workingString.startsWith("\""))
                {
                // point to the first non quote char
                nStart = 1;
                // find the matching quote
                nEnd = workingString.indexOf('"', nStart);
                // there isn't one
                if (nEnd == -1)
                    {
                    // point at the quote
                    nStart = 0;
                    // so find the next space
                    nEnd = workingString.indexOf(' ', nStart);
                    // there isn't one of those either
                    if (nEnd == -1)
                        nEnd = nLength;    // Just grab the rest of the cmdline
                    }
                else
                    {
                    nStart = 0;
                    nEnd++;
                    }
                }
            else // no quote so find the next space
                {
                nEnd = workingString.indexOf(' ', nStart);

                // there isn't one of those
                if (nEnd == -1)
                    nEnd = nLength;    // Just grab the rest of the cmdline
                }

            // get the substring
            workingString2 = workingString.substring(nStart, nEnd);

            // add it to the list
            lst.add(new String(workingString2));

            // jump past the substring and trim it
            workingString = (workingString.substring(nEnd)).trim();
            }

        int nItems = lst.size();

        String [] theArgs = new String [nItems];

        for (int lcv = 0; lcv < nItems; lcv++)
            {
            theArgs[lcv] = lst.get(lcv);
            }

        return(theArgs);
        }

    public String fixFileName(String fileName)
        {
        String    sRet = "";
        String    sTmpFileName = "";

        sRet = fileName.replace('\\', '/');

        if (sRet.startsWith("/"))
            sTmpFileName = sRet;
        else
            sTmpFileName = currentDir + "/" + sRet;

        sRet = sTmpFileName.replace('\\', '/');
        sTmpFileName = sRet;
        sRet = sTmpFileName.replace("//", "/");

        return(sRet);
        }

    public String AddFilesToZip(ZipOutputStream out, String baseDir, String relDir)
    {
        final int             BUFFER     = 2048;
        String                sRet    = "";
        String                 curDir     = "";
        String                relFN    = "";
        BufferedInputStream origin = null;
        byte                 data[] = new byte[BUFFER];

        if (relDir.length() > 0)
            curDir = baseDir + "/" + relDir;
        else
            curDir = baseDir;

        File f = new File(curDir);

        if (f.isFile())
            {
            try {
                relFN = ((relDir.length() > 0) ? relDir + "/" + f.getName() : f.getName());
                System.out.println("Adding: "+relFN);
                sRet += "Adding: "+    relFN + lineSep;
                FileInputStream fi = new FileInputStream(curDir);
                origin = new BufferedInputStream(fi, BUFFER);
                ZipEntry entry = new ZipEntry(relFN);
                out.putNextEntry(entry);
                int count;
                while((count = origin.read(data, 0, BUFFER)) != -1)
                    {
                    out.write(data, 0, count);
                    }
                origin.close();
                }
            catch(Exception e)
                {
                e.printStackTrace();
                }

            return(sRet);
            }

        String    files[] = f.list();

        if (files != null)
            {
            try {
                for(int i = 0; i < files.length; i++)
                    {
                    f = new File(curDir + "/" + files[i]);
                    if (f.isDirectory())
                        {
                        if (relDir.length() > 0)
                            sRet += AddFilesToZip(out, baseDir, relDir + "/" + files[i]);
                        else
                            sRet += AddFilesToZip(out, baseDir, files[i]);
                        }
                    else
                        {
                        relFN = ((relDir.length() > 0) ? relDir + "/" + files[i] : files[i]);
                        System.out.println("Adding: "+relFN);
                        sRet += "Adding: "+    relFN + lineSep;
                        FileInputStream fi = new FileInputStream(curDir + "/" + files[i]);
                        origin = new BufferedInputStream(fi, BUFFER);
                        ZipEntry entry = new ZipEntry(relFN);
                        out.putNextEntry(entry);
                        int count;
                        while((count = origin.read(data, 0, BUFFER)) != -1)
                            {
                            out.write(data, 0, count);
                            }
                        origin.close();
                        }
                    }
                }
            catch(Exception e)
                {
                e.printStackTrace();
                }
            }

        return(sRet);
    }

    public String Zip(String zipFileName, String srcName)
        {
        String    fixedZipFileName = fixFileName(zipFileName);
        String    fixedSrcName = fixFileName(srcName);
        String sRet = "";

        try {
            FileOutputStream dest = new FileOutputStream(fixedZipFileName);
            CheckedOutputStream checksum = new CheckedOutputStream(dest, new Adler32());
            ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(checksum));
            out.setMethod(ZipOutputStream.DEFLATED);

            sRet += AddFilesToZip(out, fixedSrcName, "");

            out.close();
            System.out.println("checksum:                   "+checksum.getChecksum().getValue());
            sRet += "checksum:                   "+checksum.getChecksum().getValue();
            }
        catch(Exception e)
            {
            e.printStackTrace();
            }

        return(sRet);
    }

    public String Unzip(String zipFileName, String dstDirectory)
        {
        String     sRet = "";
        String    fixedZipFileName = fixFileName(zipFileName);
        String    fixedDstDirectory = fixFileName(dstDirectory);
        String    dstFileName = "";
        int        nNumExtracted = 0;
        boolean bRet = false;

        try {
            final int BUFFER = 2048;
            BufferedOutputStream dest = null;
            ZipFile zipFile = new ZipFile(fixedZipFileName);
            int nNumEntries = zipFile.size();
            zipFile.close();

            FileInputStream fis = new FileInputStream(fixedZipFileName);
            CheckedInputStream checksum = new CheckedInputStream(fis, new Adler32());
            ZipInputStream zis = new ZipInputStream(new BufferedInputStream(checksum));
            ZipEntry entry;

            byte [] data = new byte[BUFFER];

            while((entry = zis.getNextEntry()) != null)
                {
                System.out.println("Extracting: " + entry);
                int count;
                if (fixedDstDirectory.length() > 0)
                    dstFileName = fixedDstDirectory + entry.getName();
                else
                    dstFileName = entry.getName();

                String tmpDir = dstFileName.substring(0, dstFileName.lastIndexOf('/'));
                File tmpFile = new File(tmpDir);
                if (!tmpFile.exists())
                    {
                    bRet = tmpFile.mkdirs();
                    }
                else
                    bRet = true;

                if (bRet)
                    {
                    // if we aren't just creating a directory
                    if (dstFileName.lastIndexOf('/') != (dstFileName.length() - 1))
                        {
                        // write out the file
                        FileOutputStream fos = new FileOutputStream(dstFileName);
                        dest = new BufferedOutputStream(fos, BUFFER);
                        while ((count = zis.read(data, 0, BUFFER)) != -1)
                            {
                            dest.write(data, 0, count);
                            }
                        dest.flush();
                        dest.close();
                        dest = null;
                        fos.close();
                        fos = null;
                        }
                    nNumExtracted++;
                    }
                else
                    sRet += " - failed" + lineSep;
                }

            data = null;
            zis.close();
            System.out.println("Checksum:          "+checksum.getChecksum().getValue());
            sRet += "Checksum:          "+checksum.getChecksum().getValue();
            sRet += lineSep + nNumExtracted + " of " + nNumEntries + " sucessfully extracted";
            }
        catch(Exception e)
            {
            e.printStackTrace();
            }

        return(sRet);
        }

    public String StatProcess(String string)
        {
        String sRet = "";
        ActivityManager aMgr = (ActivityManager) contextWrapper.getSystemService(Activity.ACTIVITY_SERVICE);
        int    [] nPids = new int [1];

        nPids[0] = Integer.parseInt(string);

        android.os.Debug.MemoryInfo[] mi = aMgr.getProcessMemoryInfo(nPids);

        sRet  = "Dalvik Private Dirty pages         " + mi[0].dalvikPrivateDirty     + " kb\n";
        sRet += "Dalvik Proportional Set Size       " + mi[0].dalvikPss              + " kb\n";
        sRet += "Dalvik Shared Dirty pages          " + mi[0].dalvikSharedDirty      + " kb\n\n";
        sRet += "Native Private Dirty pages heap    " + mi[0].nativePrivateDirty     + " kb\n";
        sRet += "Native Proportional Set Size heap  " + mi[0].nativePss              + " kb\n";
        sRet += "Native Shared Dirty pages heap     " + mi[0].nativeSharedDirty      + " kb\n\n";
        sRet += "Other Private Dirty pages          " + mi[0].otherPrivateDirty      + " kb\n";
        sRet += "Other Proportional Set Size        " + mi[0].otherPss               + " kb\n";
        sRet += "Other Shared Dirty pages           " + mi[0].otherSharedDirty       + " kb\n\n";
        sRet += "Total Private Dirty Memory         " + mi[0].getTotalPrivateDirty() + " kb\n";
        sRet += "Total Proportional Set Size Memory " + mi[0].getTotalPss()          + " kb\n";
        sRet += "Total Shared Dirty Memory          " + mi[0].getTotalSharedDirty()  + " kb";


        return(sRet);
        }

    public String GetTestRoot()
        {
        String    sRet = null;

        if (Environment.getExternalStorageState().equalsIgnoreCase(Environment.MEDIA_MOUNTED))
            {
            sRet = Environment.getExternalStorageDirectory().getAbsolutePath();
            }
        else
            {
            sRet = GetTmpDir();
            }

        return(sRet);
        }

    public String GetAppRoot(String AppName)
        {
        String sRet = sErrorPrefix + " internal error [no context]";
        Context ctx = contextWrapper.getApplicationContext();

        if (ctx != null)
            {
            try {
                Context appCtx = ctx.createPackageContext(AppName, 0);
                ContextWrapper appCtxW = new ContextWrapper(appCtx);
                sRet = appCtxW.getPackageResourcePath();
                appCtxW = null;
                appCtx = null;
                ctx = null;
                System.gc();
                }
            catch (NameNotFoundException e)
                {
                e.printStackTrace();
                }
            }
        return(sRet);
        }

    public String isDirectory(String sDir)
        {
        String    sRet = sErrorPrefix + sDir + " does not exist";
        String    tmpDir    = fixFileName(sDir);
        String [] theArgs = new String [3];
        int    nFiles = 0;

        if (tmpDir.contains("org.mozilla.fennec") || tmpDir.contains("org.mozilla.firefox")) {
            ContentResolver cr = contextWrapper.getContentResolver();
            Uri ffxFiles = Uri.parse("content://" + (tmpDir.contains("fennec") ? fenProvider : ffxProvider) + "/dir");

            String[] columns = new String[] {
                    "_id",
                    "isdir",
                    "filename",
                    "length"
                };

            Cursor myCursor = cr.query(    ffxFiles,
                                        columns,     // Which columns to return
                                        tmpDir,     // Which rows to return (all rows)
                                        null,       // Selection arguments (none)
                                        null);        // Order clause (none)
            if (myCursor != null) {
                nFiles = myCursor.getCount();

                // If no entries the dir is empty
                if (nFiles > 0) {
                    if  (myCursor.moveToPosition(0)) {
                        sRet = ((myCursor.getLong(myCursor.getColumnIndex("isdir")) == 1) ? "TRUE" : "FALSE");
                    }
                }
                myCursor.close();
            }
        } else {
            File tmpFile = new java.io.File(tmpDir);

            if (tmpFile.exists()) {
                sRet = (tmpFile.isDirectory() ? "TRUE" : "FALSE");
            }
            else {
                try {
                    theArgs[0] = "su";
                    theArgs[1] = "-c";
                    theArgs[2] = "ls -l " + sDir;

                    pProc = Runtime.getRuntime().exec(theArgs);
                    RedirOutputThread outThrd = new RedirOutputThread(pProc, null);
                    outThrd.start();
                    outThrd.join(5000);
                    sRet = outThrd.strOutput;
                    if (!sRet.contains("No such file or directory") && sRet.startsWith("l"))
                        sRet = "FALSE";
                }
                catch (IOException e) {
                    sRet = e.getMessage();
                    e.printStackTrace();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        return(sRet);
        }


    public String changeDir(String newDir)
        {
        String    tmpDir    = fixFileName(newDir);
        String    sRet = sErrorPrefix + "Couldn't change directory to " + tmpDir;
        int    nFiles = 0;

        if (tmpDir.contains("org.mozilla.fennec") || tmpDir.contains("org.mozilla.firefox")) {
            ContentResolver cr = contextWrapper.getContentResolver();
            Uri ffxFiles = Uri.parse("content://" + (tmpDir.contains("fennec") ? fenProvider : ffxProvider) + "/dir");

            String[] columns = new String[] {
                    "_id",
                    "isdir",
                    "filename"
                };

            Cursor myCursor = cr.query(    ffxFiles,
                                        columns,     // Which columns to return
                                        tmpDir,     // Which rows to return (all rows)
                                        null,       // Selection arguments (none)
                                        null);        // Order clause (none)
            if (myCursor != null) {
                nFiles = myCursor.getCount();

                if (nFiles > 0) {
                    if  (myCursor.moveToPosition(0)) {
                        if (myCursor.getLong(myCursor.getColumnIndex("isdir")) == 1) {
                            currentDir = myCursor.getString(myCursor.getColumnIndex("filename"));
                            sRet = "";
                        }
                    }
                } else {
                    sRet = sErrorPrefix + tmpDir + " is not a valid directory";
                }
                myCursor.close();
            }
        } else {
            File tmpFile = new java.io.File(tmpDir);

            if (tmpFile.exists()) {
                try {
                    if (tmpFile.isDirectory()) {
                        currentDir = tmpFile.getCanonicalPath();
                        sRet = "";
                    }
                else
                    sRet = sErrorPrefix + tmpDir + " is not a valid directory";
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return(sRet);
        }

    static final String HEXES = "0123456789abcdef";

    public static String getHex( byte [] raw )
        {
        if ( raw == null )
            {
            return null;
            }

        final StringBuilder hex = new StringBuilder( 2 * raw.length );
        for ( final byte b : raw )
            {
            hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
            }
        return hex.toString();
        }

    public String HashFile(String fileName)
        {
        String            sTmpFileName = fixFileName(fileName);
        String            sRet         = sErrorPrefix + "Couldn't calculate hash for file " + sTmpFileName;
        byte[]             buffer         = new byte [4096];
        int                nRead         = 0;
        long             lTotalRead     = 0;
        MessageDigest    digest         = null;

        try {
            digest = java.security.MessageDigest.getInstance("MD5");
            }
        catch (NoSuchAlgorithmException e)
            {
            e.printStackTrace();
            }

        if (sTmpFileName.contains("org.mozilla.fennec") || sTmpFileName.contains("org.mozilla.firefox")) {
            ContentResolver cr = contextWrapper.getContentResolver();
            Uri ffxFiles = null;

            ffxFiles = Uri.parse("content://" + (sTmpFileName.contains("fennec") ? fenProvider : ffxProvider) + "/file");

            String[] columns = new String[] {
                    "_id",
                    "chunk"
                    };

            Cursor myCursor = cr.query(    ffxFiles,
                                        columns,         // Which columns to return
                                        sTmpFileName,   // Which rows to return (all rows)
                                        null,           // Selection arguments (none)
                                        null);            // Order clause (none)
            if (myCursor != null) {
                int nRows = myCursor.getCount();
                int nBytesRecvd = 0;

                for (int lcv = 0; lcv < nRows; lcv++) {
                    if  (myCursor.moveToPosition(lcv)) {
                        byte [] buf = myCursor.getBlob(1);
                        if (buf != null) {
                            nBytesRecvd += buf.length;
                            digest.update(buf, 0, buf.length);
                            lTotalRead += nRead;
                            buf = null;
                        }
                    }
                }
                myCursor.close();
                byte [] hash = digest.digest();

                sRet = getHex(hash);
            }
        } else {
            try {
                FileInputStream srcFile  = new FileInputStream(sTmpFileName);
                while((nRead = srcFile.read(buffer)) != -1) {
                    digest.update(buffer, 0, nRead);
                    lTotalRead += nRead;
                }
                srcFile.close();
                byte [] hash = digest.digest();

                sRet = getHex(hash);
            }
            catch (FileNotFoundException e) {
                sRet += " file not found";
                e.printStackTrace();
            }
            catch (IOException e) {
                sRet += " io exception";
                e.printStackTrace();
            }
        }
        return(sRet);
    }

    public String RemoveFile(String fileName)
        {
        String    sTmpFileName = fixFileName(fileName);
        String    sRet = sErrorPrefix + "Couldn't delete file " + sTmpFileName;

        if (sTmpFileName.contains("org.mozilla.fennec") || sTmpFileName.contains("org.mozilla.firefox")) {
            ContentResolver cr = contextWrapper.getContentResolver();
            Uri ffxFiles = Uri.parse("content://" + (sTmpFileName.contains("fennec") ? fenProvider : ffxProvider) + "/file");
            if (cr.delete(ffxFiles, sTmpFileName, null) == 1) {
                sRet = "deleted " + sTmpFileName;
            }
        } else {
            File f = new File(sTmpFileName);

            if (f.delete())
                sRet = "deleted " + sTmpFileName;
        }

        return(sRet);
        }

    public String PruneDir(String sDir)
        {
        String    sRet = "";
        int nFiles = 0;
        String sSubDir = null;
        String    sTmpDir = fixFileName(sDir);

        if (sTmpDir.contains("org.mozilla.fennec") || sTmpDir.contains("org.mozilla.firefox")) {
            ContentResolver cr = contextWrapper.getContentResolver();
            Uri ffxFiles = Uri.parse("content://" + (sTmpDir.contains("fennec") ? fenProvider : ffxProvider) + "/dir");
            if (cr.delete(ffxFiles, sTmpDir, null) > 0) {
                sRet = "deleted " + sTmpDir;
            }
        } else {
            File dir = new File(sTmpDir);

            if (dir.isDirectory()) {
                sRet = "Deleting file(s) from " + sTmpDir;

                File [] files = dir.listFiles();
                if (files != null) {
                    if ((nFiles = files.length) > 0) {
                        for (int lcv = 0; lcv < nFiles; lcv++) {
                            if (files[lcv].isDirectory()) {
                                sSubDir = files[lcv].getAbsolutePath();
                                sRet += "\n" + PruneDir(sSubDir);
                            }
                            else {
                                if (files[lcv].delete()) {
                                sRet += "\n\tDeleted " + files[lcv].getName();
                                }
                                else {
                                    sRet += "\n\tUnable to delete " + files[lcv].getName();
                                }
                            }
                        }
                    }
                    else
                        sRet += "\n\t<empty>";
                }

                if (dir.delete()) {
                    sRet += "\nDeleting directory " + sTmpDir;
                }
                else {
                    sRet += "\nUnable to delete directory " + sTmpDir;
                }
            }
            else {
                sRet += sErrorPrefix + sTmpDir + " is not a directory";
            }
        }

        return(sRet);
        }

    public String PrintDir(String sDir)
        {
        String    sRet = "";
        int nFiles = 0;
        String    sTmpDir = fixFileName(sDir);

        if (sTmpDir.contains("org.mozilla.fennec") || sTmpDir.contains("org.mozilla.firefox")) {
            ContentResolver cr = contextWrapper.getContentResolver();
            Uri ffxFiles = null;

            ffxFiles = Uri.parse("content://" + (sTmpDir.contains("fennec") ? fenProvider : ffxProvider) + "/dir");

            String[] columns = new String[] {
                    "_id",
                    "isdir",
                    "filename",
                    "length"
                };

            Cursor myCursor = cr.query(    ffxFiles,
                                        columns,     // Which columns to return
                                        sTmpDir,    // Which rows to return (all rows)
                                        null,       // Selection arguments (none)
                                        null);        // Order clause (none)
            if (myCursor != null) {
                nFiles = myCursor.getCount();

                // If only one entry and the index is -1 this is not a directory
                int nNdx = myCursor.getColumnIndex("_id");
                // If no entries the dir is empty
                if (nFiles == 1) {
                    sRet = "<empty>";
                } else {
                    // Show the entries
                    for (int lcv = 1; lcv < nFiles; lcv++) {
                        if  (myCursor.moveToPosition(lcv)) {
                            if ((lcv == 0) && (myCursor.getLong(nNdx) == -1)) {
                                sRet = sErrorPrefix + sTmpDir + " is not a directory";
                            } else {
                                sRet += myCursor.getString(2);
                                if (lcv < (nFiles - 1))
                                    sRet += "\n";
                            }
                        }
                    }
                }
                myCursor.close();
            }
        } else {
            File dir = new File(sTmpDir);

            if (dir.isDirectory()) {
                File [] files = dir.listFiles();

                if (files != null) {
                    if ((nFiles = files.length) > 0) {
                        for (int lcv = 0; lcv < nFiles; lcv++) {
                            sRet += files[lcv].getName();
                            if (lcv < (nFiles - 1)) {
                                sRet += "\n";
                            }
                        }
                    }
                    else {
                        sRet = "<empty>";
                    }
                }
            }
            else {
                sRet = sErrorPrefix + sTmpDir + " is not a directory";
            }
        }
        return(sRet);
    }

    public String Move(String sTmpSrcFileName, String sTmpDstFileName) {
        String sRet = sErrorPrefix + "Could not move " + sTmpSrcFileName + " to " + sTmpDstFileName;
        String sTmp = CopyFile(sTmpSrcFileName, sTmpDstFileName);
        if (sTmp.contains(" copied to ")) {
            sTmp = RemoveFile(sTmpSrcFileName);
            if (sTmp.startsWith("deleted ")) {
                sRet = sTmpSrcFileName + " moved to " + sTmpDstFileName;
            }
        }

        return(sRet);
    }

    public String CopyFile(String sTmpSrcFileName, String sTmpDstFileName) {
        String sRet = sErrorPrefix + "Could not copy " + sTmpSrcFileName + " to " + sTmpDstFileName;
        ContentValues cv = null;
        File destFile = null;
        Uri ffxSrcFiles = null;
        Uri ffxDstFiles = null;
        FileInputStream srcFile  = null;
        FileOutputStream dstFile  = null;
        byte[] buffer = new byte [4096];
        int    nRead = 0;
        long lTotalRead = 0;
        long lTotalWritten = 0;
        ContentResolver crIn = null;
        ContentResolver crOut = null;

        if (sTmpSrcFileName.contains("org.mozilla.fennec") || sTmpSrcFileName.contains("org.mozilla.firefox")) {
            ffxSrcFiles = Uri.parse("content://" + (sTmpSrcFileName.contains("fennec") ? fenProvider : ffxProvider) + "/file");
            crIn = contextWrapper.getContentResolver();
        } else {
            try {
                srcFile  = new FileInputStream(sTmpSrcFileName);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }

        if (sTmpDstFileName.contains("org.mozilla.fennec") || sTmpDstFileName.contains("org.mozilla.firefox")) {
            ffxDstFiles = Uri.parse("content://" + (sTmpDstFileName.contains("fennec") ? fenProvider : ffxProvider) + "/file");
            crOut = contextWrapper.getContentResolver();
            cv = new ContentValues();
        } else {
            try {
                dstFile  = new FileOutputStream(sTmpDstFileName);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }

        if (srcFile != null) {
            try {
                while((nRead = srcFile.read(buffer)) != -1)    {
                    lTotalRead += nRead;
                    if (dstFile != null) {
                        dstFile.write(buffer, 0, nRead);
                        dstFile.flush();
                    } else {
                        cv.put("length", nRead);
                        cv.put("chunk", buffer);
                        if (crOut.update(ffxDstFiles, cv, sTmpDstFileName, null) == 0)
                            break;
                        lTotalWritten += nRead;
                    }
                }

                srcFile.close();

                if (dstFile != null) {
                    dstFile.flush();
                    dstFile.close();

                    destFile = new File(sTmpDstFileName);
                    lTotalWritten = destFile.length();
                }

                if (lTotalWritten == lTotalRead) {
                    sRet = sTmpSrcFileName + " copied to " + sTmpDstFileName;
                }
                else {
                    sRet = sErrorPrefix + "Failed to copy " + sTmpSrcFileName + " [length = " + lTotalWritten + "] to " + sTmpDstFileName + " [length = " + lTotalRead + "]";
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        } else {
            String[] columns = new String[] {
                    "_id",
                    "chunk",
                    "length"
                    };

            Cursor myCursor = crIn.query(ffxSrcFiles,
                                        columns,             // Which columns to return
                                        sTmpSrcFileName,       // Which rows to return (all rows)
                                        null,               // Selection arguments (none)
                                        null);                // Order clause (none)
            if (myCursor != null) {
                int nRows = myCursor.getCount();

                byte [] buf = null;

                for (int lcv = 0; lcv < nRows; lcv++) {
                    if  (myCursor.moveToPosition(lcv)) {
                        buf = myCursor.getBlob(myCursor.getColumnIndex("chunk"));
                        if (buf != null) {
                            nRead = buf.length;
                            try {
                                lTotalRead += nRead;
                                if (dstFile != null) {
                                    dstFile.write(buffer, 0, nRead);
                                    dstFile.flush();
                                } else {
                                    cv.put("length", nRead);
                                    cv.put("chunk", buffer);
                                    if (crOut.update(ffxDstFiles, cv, sTmpDstFileName, null) == 0)
                                        break;
                                    lTotalWritten += nRead;
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            buf = null;
                        }
                    }
                }

                if (nRows == -1) {
                    sRet = sErrorPrefix + sTmpSrcFileName + ",-1\nNo such file or directory";
                }
                else {
                    myCursor.close();

                    if (dstFile != null) {
                        try {
                            dstFile.flush();
                            dstFile.close();

                            destFile = new File(sTmpDstFileName);
                            lTotalWritten = destFile.length();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    if (lTotalWritten == lTotalRead) {
                        sRet = sTmpSrcFileName + " copied to " + sTmpDstFileName;
                    }
                    else {
                        sRet = sErrorPrefix + "Failed to copy " + sTmpSrcFileName + " [length = " + lTotalWritten + "] to " + sTmpDstFileName + " [length = " + lTotalRead + "]";
                    }
                }
            }
            else {
                sRet = sErrorPrefix + sTmpSrcFileName + ",-1\nUnable to access file (internal error)";
            }
        }

        return (sRet);
    }

    public String IsDirWritable(String sDir)
        {
        String    sTmpDir = fixFileName(sDir);
        String sRet = sErrorPrefix + "[" + sTmpDir + "] is not a directory";

        if (sTmpDir.contains("org.mozilla.fennec") || sTmpDir.contains("org.mozilla.firefox")) {
            ContentResolver cr = contextWrapper.getContentResolver();
            Uri ffxFiles = null;

            ffxFiles = Uri.parse("content://" + (sTmpDir.contains("fennec") ? fenProvider : ffxProvider) + "/dir");

            String[] columns = new String[] {
                    "_id",
                    "isdir",
                    "filename",
                    "length",
                    "writable"
                };

            Cursor myCursor = cr.query(    ffxFiles,
                                        columns,     // Which columns to return
                                        sTmpDir,    // Which rows to return (all rows)
                                        null,       // Selection arguments (none)
                                        null);        // Order clause (none)
            if (myCursor != null) {
                if (myCursor.getCount() > 0) {
                    if (myCursor.moveToPosition(0)) {
                        if (myCursor.getLong(myCursor.getColumnIndex("isdir")) == 1) {
                            sRet = "[" + sTmpDir + "] " + ((myCursor.getLong(myCursor.getColumnIndex("writable")) == 1) ? "is" : "is not") + " writable";
                        }
                    }
                }
            }
        } else {
            File dir = new File(sTmpDir);

            if (dir.isDirectory()) {
                sRet = "[" + sTmpDir + "] " + (dir.canWrite() ? "is" : "is not") + " writable";
            } else {
                sRet = sErrorPrefix + "[" + sTmpDir + "] is not a directory";
            }
        }
        return(sRet);
    }

    public String Push(String fileName, BufferedInputStream bufIn, long lSize)
    {
        byte []                buffer             = new byte [8192];
        int                    nRead            = 0;
        long                lRead            = 0;
        String                sTmpFileName     = fixFileName(fileName);
        FileOutputStream     dstFile            = null;
        ContentResolver     cr                 = null;
        ContentValues        cv                = null;
        Uri                 ffxFiles         = null;
        String                sRet            = sErrorPrefix + "Push failed!";

        try {
            if (sTmpFileName.contains("org.mozilla.fennec") || sTmpFileName.contains("org.mozilla.firefox")) {
                cr = contextWrapper.getContentResolver();
                ffxFiles = Uri.parse("content://" + (sTmpFileName.contains("fennec") ? fenProvider : ffxProvider) + "/file");
                cv = new ContentValues();
            }
            else {
                dstFile = new FileOutputStream(sTmpFileName, false);
            }

            while((nRead != -1) && (lRead < lSize))
                {
                nRead = bufIn.read(buffer);
                if (nRead != -1) {
                    if (dstFile != null) {
                        dstFile.write(buffer, 0, nRead);
                        dstFile.flush();
                    }
                    else {
                        cv.put("offset", lRead);
                        cv.put("length", nRead);
                        cv.put("chunk", buffer);
                        cr.update(ffxFiles, cv, sTmpFileName, null);
                    }
                    lRead += nRead;
                }
            }

            if (dstFile != null) {
                dstFile.flush();
                dstFile.close();
            }

            if (lRead == lSize)    {
                sRet = HashFile(sTmpFileName);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        buffer = null;

        return(sRet);
    }

    public String FTPGetFile(String sServer, String sSrcFileName, String sDstFileName, OutputStream out)
        {
        byte[] buffer = new byte [4096];
        int    nRead = 0;
        long lTotalRead = 0;
        String sRet = sErrorPrefix + "FTP Get failed for " + sSrcFileName;
        String strRet = "";
        int    reply = 0;
        FileOutputStream outStream = null;
        String    sTmpDstFileName = fixFileName(sDstFileName);

        FTPClient ftp = new FTPClient();
        try
            {
            ftp.connect(sServer);
            reply = ftp.getReplyCode();
            if(FTPReply.isPositiveCompletion(reply))
                {
                ftp.login("anonymous", "b@t.com");
                reply = ftp.getReplyCode();
                if(FTPReply.isPositiveCompletion(reply))
                    {
                    ftp.enterLocalPassiveMode();
                    if (ftp.setFileType(FTP.BINARY_FILE_TYPE))
                        {
                        File dstFile = new File(sTmpDstFileName);
                        outStream = new FileOutputStream(dstFile);
                        FTPFile [] ftpFiles = ftp.listFiles(sSrcFileName);
                        if (ftpFiles.length > 0)
                            {
                            long lFtpSize = ftpFiles[0].getSize();
                            if (lFtpSize <= 0)
                                lFtpSize = 1;

                            InputStream ftpIn = ftp.retrieveFileStream(sSrcFileName);
                            while ((nRead = ftpIn.read(buffer)) != -1)
                                {
                                lTotalRead += nRead;
                                outStream.write(buffer, 0, nRead);
                                strRet = "\r" + lTotalRead + " of " + lFtpSize + " bytes received " + ((lTotalRead * 100) / lFtpSize) + "% completed";
                                out.write(strRet.getBytes());
                                out.flush();
                                }
                            ftpIn.close();
                            @SuppressWarnings("unused")
                            boolean bRet = ftp.completePendingCommand();
                            outStream.flush();
                            outStream.close();
                            strRet = ftp.getReplyString();
                            reply = ftp.getReplyCode();
                            }
                        else
                            {
                            strRet = sRet;
                            }
                        }
                    ftp.logout();
                    ftp.disconnect();
                    sRet = "\n" + strRet;
                    }
                else
                    {
                    ftp.disconnect();
                    System.err.println("FTP server refused login.");
                    }
                }
            else
                {
                ftp.disconnect();
                System.err.println("FTP server refused connection.");
                }
            }
        catch (SocketException e)
            {
            sRet = e.getMessage();
            strRet = ftp.getReplyString();
            reply = ftp.getReplyCode();
            sRet += "\n" + strRet;
            e.printStackTrace();
            }
        catch (IOException e)
            {
            sRet = e.getMessage();
            strRet = ftp.getReplyString();
            reply = ftp.getReplyCode();
            sRet += "\n" + strRet;
            e.printStackTrace();
            }
        return (sRet);
    }

    public String Pull(String fileName, long lOffset, long lLength, OutputStream out)
        {
        String    sTmpFileName = fixFileName(fileName);
        String    sRet = sErrorPrefix + "Could not read the file " + sTmpFileName;
        byte[]    buffer = new byte [4096];
        int        nRead = 0;
        long    lSent = 0;

        if (sTmpFileName.contains("org.mozilla.fennec") || sTmpFileName.contains("org.mozilla.firefox")) {
            ContentResolver cr = contextWrapper.getContentResolver();
            Uri ffxFiles = null;

            ffxFiles = Uri.parse("content://" + (sTmpFileName.contains("fennec") ? fenProvider : ffxProvider) + "/file");

            String[] columns = new String[] {
                    "_id",
                    "chunk",
                    "length"
                    };

            String [] args = new String [2];
            args[0] = Long.toString(lOffset);
            args[1] = Long.toString(lLength);

            Cursor myCursor = cr.query(    ffxFiles,
                                        columns,         // Which columns to return
                                        sTmpFileName,   // Which rows to return (all rows)
                                        args,           // Selection arguments (none)
                                        null);            // Order clause (none)
            if (myCursor != null) {
                int nRows = myCursor.getCount();
                long lFileLength = 0;

                for (int lcv = 0; lcv < nRows; lcv++) {
                    if  (myCursor.moveToPosition(lcv)) {
                        if (lcv == 0) {
                            lFileLength = myCursor.getLong(2);
                            String sTmp = sTmpFileName + "," + lFileLength + "\n";
                            try {
                                out.write(sTmp.getBytes());
                            } catch (IOException e) {
                                e.printStackTrace();
                                break;
                            }
                        }

                        if (lLength != 0) {
                            byte [] buf = myCursor.getBlob(1);
                            if (buf != null) {
                                nRead = buf.length;
                                try {
                                    if ((lSent + nRead) <= lFileLength)    {
                                        out.write(buf,0,nRead);
                                        lSent += nRead;
                                    }
                                    else {
                                        nRead = (int) (lFileLength - lSent);
                                        out.write(buf,0,nRead);
                                        Log.d("pull warning", "more bytes read than expected");
                                        break;
                                    }
                                } catch (IOException e) {
                                    e.printStackTrace();
                                    sRet = sErrorPrefix + "Could not write to out " + sTmpFileName;
                                }
                                buf = null;
                            }
                        }
                    }
                }
                if (nRows == 0) {
                    String sTmp = sTmpFileName + "," + lFileLength + "\n";
                    try {
                        out.write(sTmp.getBytes());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (nRows == -1) {
                    sRet = sErrorPrefix + sTmpFileName + ",-1\nNo such file or directory";
                }
                else {
                    myCursor.close();
                    sRet = "";
                }
            }
            else {
                sRet = sErrorPrefix + sTmpFileName + ",-1\nUnable to access file (internal error)";
            }
        }
        else {
            try {
                File f = new File(sTmpFileName);
                long lFileLength = f.length();
                FileInputStream fin = new FileInputStream(f);
                if (lFileLength == 0) {
                    while ((nRead = fin.read(buffer)) != -1) {
                        lFileLength += nRead;
                    }
                    fin.close();
                    fin = new FileInputStream(f);
                }

                // lLength == -1 return everything between lOffset and eof
                // lLength == 0 return file length
                // lLength > 0 return lLength bytes
                if (lLength == -1) {
                    lFileLength = lFileLength - lOffset;
                } else if (lLength == 0) {
                    // just return the file length
                } else {
                    lFileLength = ((lLength <= (lFileLength - lOffset)) ? lLength : (lFileLength - lOffset));
                }

                String sTmp = sTmpFileName + "," + lFileLength + "\n";
                out.write(sTmp.getBytes());
                if (lLength != 0) {
                    if  (lOffset > 0) {
                        fin.skip(lOffset);
                    }
                    while ((nRead = fin.read(buffer)) != -1) {
                        if ((lSent + nRead) <= lFileLength)    {
                            out.write(buffer,0,nRead);
                            lSent += nRead;
                        }
                        else {
                            nRead = (int) (lFileLength - lSent);
                            out.write(buffer,0,nRead);
                            if (lLength != -1)
                                Log.d("pull warning", "more bytes read than sent");
                            break;
                        }
                    }
                }
                fin.close();
                out.flush();
                sRet = "";
                }
            catch (FileNotFoundException e)    {
                sRet = sErrorPrefix + sTmpFileName + ",-1\nNo such file or directory";
            }
            catch (IOException e) {
                sRet = e.toString();
            }
        }
        return (sRet);
    }

    public String Cat(String fileName, OutputStream out)
        {
        String    sTmpFileName = fixFileName(fileName);
        String    sRet = sErrorPrefix + "Could not read the file " + sTmpFileName;
        byte[]    buffer = new byte [4096];
        int        nRead = 0;

        if (sTmpFileName.contains("org.mozilla.fennec") || sTmpFileName.contains("org.mozilla.firefox")) {
            ContentResolver cr = contextWrapper.getContentResolver();
            Uri ffxFiles = null;

            ffxFiles = Uri.parse("content://" + (sTmpFileName.contains("fennec") ? fenProvider : ffxProvider) + "/file");

            String[] columns = new String[] {
                    "_id",
                    "chunk"
                    };

            Cursor myCursor = cr.query(    ffxFiles,
                                        columns,         // Which columns to return
                                        sTmpFileName,   // Which rows to return (all rows)
                                        null,           // Selection arguments (none)
                                        null);            // Order clause (none)
            if (myCursor != null) {
                int nRows = myCursor.getCount();
                int nBytesRecvd = 0;

                for (int lcv = 0; lcv < nRows; lcv++) {
                    if  (myCursor.moveToPosition(lcv)) {
                        byte [] buf = myCursor.getBlob(1);
                        if (buf != null) {
                            nBytesRecvd += buf.length;
                            try {
                                out.write(buf);
                                sRet = "";
                            } catch (IOException e) {
                                e.printStackTrace();
                                sRet = sErrorPrefix + "Could not write to out " + sTmpFileName;
                            }
                            buf = null;
                        }
                    }
                }
                if (nRows == 0) {
                    sRet = "";
                }

                myCursor.close();
            }
        } else {
            try {
                FileInputStream fin = new FileInputStream(sTmpFileName);
                while ((nRead = fin.read(buffer)) != -1) {
                    out.write(buffer,0,nRead);
                }
                fin.close();
                out.flush();
                sRet = "";
                }
            catch (FileNotFoundException e) {
                sRet = sErrorPrefix + sTmpFileName + " No such file or directory";
            }
            catch (IOException e) {
                sRet = e.toString();
            }
        }
        return (sRet);
    }

    public String MakeDir(String sDir)
        {
        String    sTmpDir = fixFileName(sDir);
        String sRet = sErrorPrefix + "Could not create the directory " + sTmpDir;
        if (sTmpDir.contains("org.mozilla.fennec") || sTmpDir.contains("org.mozilla.firefox")) {
            ContentResolver cr = contextWrapper.getContentResolver();
            Uri ffxFiles = Uri.parse("content://" + (sTmpDir.contains("fennec") ? fenProvider : ffxProvider) + "/dir");
            ContentValues cv = new ContentValues();

            if (cr.update(ffxFiles, cv, sTmpDir, null) == 1) {
                sRet = sDir + " successfully created";
            }
        }
        else {
            File dir = new File(sTmpDir);

            if (dir.mkdirs())
                sRet = sDir + " successfully created";
        }

        return (sRet);
        }
    // move this to SUTAgentAndroid.java
    public String GetScreenInfo()
        {
        String sRet = "";
        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager wMgr = (WindowManager) contextWrapper.getSystemService(Context.WINDOW_SERVICE);
        wMgr.getDefaultDisplay().getMetrics(metrics);
        sRet = "X:" + metrics.widthPixels + " Y:" + metrics.heightPixels;
        return (sRet);
        }
    // move this to SUTAgentAndroid.java
    public int [] GetScreenXY()
        {
            int [] nRetXY = new int [2];
            DisplayMetrics metrics = new DisplayMetrics();
            WindowManager wMgr = (WindowManager) contextWrapper.getSystemService(Context.WINDOW_SERVICE);
            wMgr.getDefaultDisplay().getMetrics(metrics);
            nRetXY[0] = metrics.widthPixels;
            nRetXY[1] = metrics.heightPixels;
            return(nRetXY);
        }

    public String SetADB(String sWhat) {
        String sRet = "";
        String sTmp = "";
        String [] theArgs = new String [3];

        theArgs[0] = "su";
        theArgs[1] = "-c";

        if (sWhat.contains("ip")) {
            theArgs[2] = "setprop service.adb.tcp.port 5555";
        } else {
            theArgs[2] = "setprop service.adb.tcp.port -1";
        }

        try {
            pProc = Runtime.getRuntime().exec(theArgs);
            RedirOutputThread outThrd = new RedirOutputThread(pProc, null);
            outThrd.start();
            outThrd.join(5000);
            sTmp = outThrd.strOutput;
            Log.e("ADB", sTmp);
            if (outThrd.nExitCode == 0) {
                theArgs[2] = "stop adbd";
                pProc = Runtime.getRuntime().exec(theArgs);
                outThrd = new RedirOutputThread(pProc, null);
                outThrd.start();
                outThrd.join(5000);
                sTmp = outThrd.strOutput;
                Log.e("ADB", sTmp);
                if (outThrd.nExitCode == 0) {
                    theArgs[2] = "start adbd";
                    pProc = Runtime.getRuntime().exec(theArgs);
                    outThrd = new RedirOutputThread(pProc, null);
                    outThrd.start();
                    outThrd.join(5000);
                    sTmp = outThrd.strOutput;
                    Log.e("ADB", sTmp);
                    if (outThrd.nExitCode == 0) {
                        sRet = "Successfully set adb to " + sWhat + "\n";
                    } else {
                        sRet = sErrorPrefix + "Failed to start adbd\n";
                    }
                } else {
                    sRet = sErrorPrefix + "Failed to stop adbd\n";
                }
            } else {
                sRet = sErrorPrefix + "Failed to setprop service.adb.tcp.port 5555\n";
            }

        }
    catch (IOException e)
        {
        sRet = e.getMessage();
        e.printStackTrace();
        }
    catch (InterruptedException e)
        {
        e.printStackTrace();
        }
        return(sRet);
    }

    public String KillProcess(String sProcName, OutputStream out)
        {
        String sTmp = "";
        String [] theArgs = new String [3];

        theArgs[0] = "su";
        theArgs[1] = "-c";
        theArgs[2] = "kill";

        String sRet = sErrorPrefix + "Unable to kill " + sProcName + "\n";
        ActivityManager aMgr = (ActivityManager) contextWrapper.getSystemService(Activity.ACTIVITY_SERVICE);
        List <ActivityManager.RunningAppProcessInfo> lProcesses = aMgr.getRunningAppProcesses();
        int lcv = 0;
        String strProcName = "";
        int    nPID = 0;

        for (lcv = 0; lcv < lProcesses.size(); lcv++)
            {
            if (lProcesses.get(lcv).processName.contains(sProcName))
                {
                strProcName = lProcesses.get(lcv).processName;
                nPID = lProcesses.get(lcv).pid;
                sRet = sErrorPrefix + "Failed to kill " + nPID + " " + strProcName + "\n";

                theArgs[2] += " " + nPID;

                try
                    {
                    pProc = Runtime.getRuntime().exec(theArgs);
                    RedirOutputThread outThrd = new RedirOutputThread(pProc, null);
                    outThrd.start();
                    outThrd.join(15000);
                    sTmp = outThrd.strOutput;
                    Log.e("KILLPROCESS", sTmp);
                    if (outThrd.nExitCode == 0) {
                        sRet = "Successfully killed " + nPID + " " + strProcName + "\n";
                        nPID = 0;
                        break;
                    } else {
                        sRet = sErrorPrefix + "Failed to kill " + nPID + " " + strProcName + "\n";
                    }
                    }
                catch (IOException e)
                    {
                    sRet = e.getMessage();
                    e.printStackTrace();
                    }
                catch (InterruptedException e)
                    {
                    e.printStackTrace();
                    }

                // Give the messages a chance to be processed
                try {
                    Thread.sleep(2000);
                    }
                catch (InterruptedException e)
                    {
                    e.printStackTrace();
                    }
                break;
                }
            }

        if (nPID > 0)
            {
            sRet = "Successfully killed " + nPID + " " + strProcName + "\n";
            lProcesses = aMgr.getRunningAppProcesses();
            for (lcv = 0; lcv < lProcesses.size(); lcv++)
                {
                if (lProcesses.get(lcv).processName.contains(sProcName))
                    {
                    sRet = sErrorPrefix + "Unable to kill " + nPID + " " + strProcName + "\n";
                    break;
                    }
                }
            }

        return (sRet);
        }

    public boolean IsProcessDead(String sProcName)
        {
        boolean bRet = false;
        ActivityManager aMgr = (ActivityManager) contextWrapper.getSystemService(Activity.ACTIVITY_SERVICE);
        List <ActivityManager.ProcessErrorStateInfo> lProcesses = aMgr.getProcessesInErrorState();
        int lcv = 0;

        if (lProcesses != null)
            {
            for (lcv = 0; lcv < lProcesses.size(); lcv++)
                {
                if (lProcesses.get(lcv).processName.contentEquals(sProcName) &&
                    lProcesses.get(lcv).condition != ActivityManager.ProcessErrorStateInfo.NO_ERROR)
                    {
                    bRet = true;
                    break;
                    }
                }
            }

        return (bRet);
        }

    public String GetProcessInfo()
        {
        String sRet = "";
        ActivityManager aMgr = (ActivityManager) contextWrapper.getSystemService(Activity.ACTIVITY_SERVICE);
        List <ActivityManager.RunningAppProcessInfo> lProcesses = aMgr.getRunningAppProcesses();
        int    nProcs = lProcesses.size();
        int lcv = 0;
        String strProcName = "";
        int    nPID = 0;
        int nUser = 0;

        for (lcv = 0; lcv < nProcs; lcv++)
            {
            strProcName = lProcesses.get(lcv).processName;
            nPID = lProcesses.get(lcv).pid;
            nUser = lProcesses.get(lcv).uid;
            sRet += nUser + "\t" + nPID + "\t" + strProcName;
            if (lcv < (nProcs - 1))
                sRet += "\n";
            }

        return (sRet);
        }

    public String GetOSInfo()
        {
        String sRet = "";

        sRet = Build.DISPLAY;

        return (sRet);
        }

    public String GetPowerInfo()
        {
        String sRet = "";

        sRet = "Power status:\n  AC power " + SUTAgentAndroid.sACStatus + "\n";
        sRet += "  Battery charge " + SUTAgentAndroid.sPowerStatus + "\n";
        sRet += "  Remaining charge:      " + SUTAgentAndroid.nChargeLevel + "%\n";
        sRet += "  Battery Temperature:   " + (((float)(SUTAgentAndroid.nBatteryTemp))/10) + " (c)\n";
        return (sRet);
        }

    // todo
    public String GetDiskInfo(String sPath)
        {
        String sRet = "";
        StatFs statFS = new StatFs(sPath);

        int nBlockCount = statFS.getBlockCount();
        int nBlockSize = statFS.getBlockSize();
        int nBlocksAvail = statFS.getAvailableBlocks();
        int nBlocksFree = statFS.getFreeBlocks();

        sRet = "total:     " + (nBlockCount * nBlockSize) + "\nfree:      " + (nBlocksFree * nBlockSize) + "\navailable: " + (nBlocksAvail * nBlockSize);

        return (sRet);
        }

    public String GetMemoryInfo()
        {
        String sRet = "PA:" + GetMemoryConfig();
        return (sRet);
        }

    public long GetMemoryConfig()
        {
        ActivityManager aMgr = (ActivityManager) contextWrapper.getSystemService(Activity.ACTIVITY_SERVICE);
        ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
        aMgr.getMemoryInfo(outInfo);
        long lMem = outInfo.availMem;

        return (lMem);
        }

    public String UpdateCallBack(String sFileName)
        {
        String sRet = sErrorPrefix + "No file specified";
        String sIP = "";
        String sPort = "";
        int nEnd = 0;
        int nStart = 0;

        if ((sFileName == null) || (sFileName.length() == 0))
            return(sRet);

        Context ctx = contextWrapper.getApplicationContext();
        try {
            FileInputStream fis = ctx.openFileInput(sFileName);
            int nBytes = fis.available();
            if (nBytes > 0)
                {
                byte [] buffer = new byte [nBytes + 1];
                int nRead = fis.read(buffer, 0, nBytes);
                fis.close();
                ctx.deleteFile(sFileName);
                if (nRead > 0)
                    {
                    String sBuffer = new String(buffer);
                    nEnd = sBuffer.indexOf(',');
                    if (nEnd > 0)
                        {
                        sIP = (sBuffer.substring(0, nEnd)).trim();
                        nStart = nEnd + 1;
                        nEnd = sBuffer.indexOf('\r', nStart);
                        if (nEnd > 0)
                            {
                            sPort = (sBuffer.substring(nStart, nEnd)).trim();
                            Thread.sleep(5000);
                            sRet = RegisterTheDevice(sIP, sPort, sBuffer.substring(nEnd + 1));
                            }
                        }
                    }
                }
            }
        catch (FileNotFoundException e)
            {
            sRet = sErrorPrefix + "Nothing to do";
            }
        catch (IOException e)
            {
            sRet = sErrorPrefix + "Couldn't send info to " + sIP + ":" + sPort;
            }
        catch (InterruptedException e)
            {
            e.printStackTrace();
            }
        return(sRet);
        }

    public String RegisterTheDevice(String sSrvr, String sPort, String sData)
        {
        String sRet = "";
        String line = "";

//        Debug.waitForDebugger();

        if (sSrvr != null && sPort != null && sData != null)
            {
            try
                {
                int nPort = Integer.parseInt(sPort);
                Socket socket = new Socket(sSrvr, nPort);
                PrintWriter out = new PrintWriter(socket.getOutputStream(), false);
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                out.println(sData);
                if ( out.checkError() == false )
                    {
                    socket.setSoTimeout(30000);
                    while (socket.isInputShutdown() == false)
                        {
                        line = in.readLine();

                        if (line != null)
                            {
                            line = line.toLowerCase();
                            sRet += line;
                            // ok means we're done
                            if (line.contains("ok"))
                                break;
                            }
                        else
                            {
                            // end of stream reached
                            break;
                            }
                        }
                    }
                out.close();
                in.close();
                socket.close();
                }
            catch(NumberFormatException e)
                {
                sRet += "reg NumberFormatException thrown [" + e.getLocalizedMessage() + "]";
                e.printStackTrace();
                }
            catch (UnknownHostException e)
                {
                sRet += "reg UnknownHostException thrown [" + e.getLocalizedMessage() + "]";
                e.printStackTrace();
                }
            catch (IOException e)
                {
                sRet += "reg IOException thrown [" + e.getLocalizedMessage() + "]";
                e.printStackTrace();
                }
            }
        return(sRet);
        }

    public String GetInternetData(String sHost, String sPort, String sURL)
        {
        String sRet = "";
        String sNewURL = "";
        HttpClient httpClient = new DefaultHttpClient();
        try
            {
            sNewURL = "http://" + sHost + ((sPort.length() > 0) ? (":" + sPort) : "") + sURL;

            HttpGet request = new HttpGet(sNewURL);
            HttpResponse response = httpClient.execute(request);
            int status = response.getStatusLine().getStatusCode();
            // we assume that the response body contains the error message
            if (status != HttpStatus.SC_OK)
                {
                ByteArrayOutputStream ostream = new ByteArrayOutputStream();
                response.getEntity().writeTo(ostream);
                Log.e("HTTP CLIENT", ostream.toString());
                }
            else
                {
                InputStream content = response.getEntity().getContent();
                byte [] data = new byte [2048];
                int nRead = content.read(data);
                sRet = new String(data, 0, nRead);
                content.close(); // this will also close the connection
                }
            }
        catch (IllegalArgumentException e)
            {
            sRet = e.getLocalizedMessage();
            e.printStackTrace();
            }
        catch (ClientProtocolException e)
            {
            sRet = e.getLocalizedMessage();
            e.printStackTrace();
            }
        catch (IOException e)
            {
            sRet = e.getLocalizedMessage();
            e.printStackTrace();
            }

        return(sRet);
        }

    public String GetTimeZone()
        {
        String    sRet = "";
        TimeZone tz;

        tz = TimeZone.getDefault();
        Date now = new Date();
        sRet = tz.getDisplayName(tz.inDaylightTime(now), TimeZone.LONG);

        return(sRet);
        }

    public String SetTimeZone(String sTimeZone)
        {
        String            sRet = "Unable to set timezone to " + sTimeZone;
        TimeZone         tz = null;
        AlarmManager     amgr = null;

        if ((sTimeZone.length() > 0) && (sTimeZone.startsWith("GMT")))
            {
            amgr = (AlarmManager) contextWrapper.getSystemService(Context.ALARM_SERVICE);
            if (amgr != null)
                amgr.setTimeZone(sTimeZone);
            }
        else
            {
            String [] zoneNames = TimeZone.getAvailableIDs();
            int nNumMatches = zoneNames.length;
            int    lcv = 0;

            for (lcv = 0; lcv < nNumMatches; lcv++)
                {
                if (zoneNames[lcv].equalsIgnoreCase(sTimeZone))
                    break;
                }

            if (lcv < nNumMatches)
                {
                amgr = (AlarmManager) contextWrapper.getSystemService(Context.ALARM_SERVICE);
                if (amgr != null)
                    amgr.setTimeZone(zoneNames[lcv]);
                }
            }

        if (amgr != null)
            {
            tz = TimeZone.getDefault();
            Date now = new Date();
            sRet = tz.getDisplayName(tz.inDaylightTime(now), TimeZone.LONG);
            }

        return(sRet);
        }

    public String GetSystemTime()
        {
        String sRet = "";
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss:SSS");
        sRet = sdf.format(cal.getTime());

        return (sRet);
        }

    public String SetSystemTime(String sDate, String sTime, OutputStream out) {
        String sRet = "";
        String sM = "";
        String sMillis = "";
        String [] theArgs = new String [3];

        theArgs[0] = "su";
        theArgs[1] = "-c";

        if (((sDate != null) && (sTime != null)) &&
            (sDate.contains("/") || sDate.contains(".")) &&
            (sTime.contains(":"))) {
            int year = Integer.parseInt(sDate.substring(0,4));
            int month = Integer.parseInt(sDate.substring(5,7));
            int day = Integer.parseInt(sDate.substring(8,10));

            int hour = Integer.parseInt(sTime.substring(0,2));
            int mins = Integer.parseInt(sTime.substring(3,5));
            int secs = Integer.parseInt(sTime.substring(6,8));

            Calendar cal = new GregorianCalendar(TimeZone.getDefault());
            cal.set(year, month - 1, day, hour, mins, secs);
            long lMillisecs = cal.getTime().getTime();

            sM = Long.toString(lMillisecs);
            sMillis = sM.substring(0, sM.length() - 3) + "." + sM.substring(sM.length() - 3);

        } else if ((sDate != null) && (sTime == null) && sDate.contains(".")) {
            String serverName = sDate;
//            String serverName = "us.pool.ntp.org";
            sRet = "NTP Server: " + serverName + lineSep;
            // Send request
            DatagramSocket socket;

            try {
                socket = new DatagramSocket();
                InetAddress address = InetAddress.getByName(serverName);
                byte[] buf = new NtpMessage().toByteArray();
                DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 123);

                // Set the transmit timestamp *just* before sending the packet
                // ToDo: Does this actually improve performance or not?
                NtpMessage.encodeTimestamp(packet.getData(), 40, (System.currentTimeMillis()/1000.0) + 2208988800.0);

                socket.send(packet);

                // Get response
                System.out.println("NTP request sent, waiting for response...\n");
                packet = new DatagramPacket(buf, buf.length);
                socket.receive(packet);

                // Immediately record the incoming timestamp since 00:00 1-JAN-1900 in secs.
                double destinationTimestamp = (System.currentTimeMillis()/1000.0) + 2208988800.0;

                // Process response
                NtpMessage msg = new NtpMessage(packet.getData());

                // Corrected, according to RFC2030 errata
                double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) - (msg.transmitTimestamp-msg.receiveTimestamp);

                double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) + (msg.transmitTimestamp - destinationTimestamp)) / 2;

                // convert base of timestamp from 00:00 1900/01/01 to 00:00:00 1970/01/01
                double utc = msg.transmitTimestamp - (2208988800.0);

                // convert from secs to ms
                long lNewMillisecs = (long)(utc * 1000.0);

                // create a date object using the timestamp this will take into account the timezone and daylight savings settings
                Date dt = new Date(lNewMillisecs);

                sRet += "  Time: " + new SimpleDateFormat("yyyy/MM/dd hh:mm:ss:SSS").format(dt) + lineSep;

                // get the timestamp
                long lMillisecs = dt.getTime();

                // format the timestamp as required for the date command
                sM = Long.toString(lMillisecs);
                sMillis = sM.substring(0, sM.length() - 3) + "." + sM.substring(sM.length() - 3);

                socket.close();
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (UnknownHostException e) {
                e.printStackTrace();
                sRet = sErrorPrefix + "Unknown host";
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            sRet += "Invalid argument(s)";
        }

        // if we have an argument
        if (sMillis.length() > 0) {
            theArgs[2] = "date -u " + sMillis;

            try {
                pProc = Runtime.getRuntime().exec(theArgs);
                RedirOutputThread outThrd = new RedirOutputThread(pProc, null);
                outThrd.start();
                outThrd.join(10000);
                sRet += GetSystemTime();
            } catch (IOException e) {
                sRet = e.getMessage();
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return (sRet);
    }

    public String GetClok()
        {
        long lMillisecs = System.currentTimeMillis();
        String sRet = "";

        if (lMillisecs > 0)
            sRet = Long.toString(lMillisecs);

        return(sRet);
        }

    public String GetUptime()
        {
        String sRet = "";
        long lHold = 0;
        long lUptime = SystemClock.elapsedRealtime();
        int    nDays = 0;
        int    nHours = 0;
        int nMinutes = 0;
        int nSecs = 0;
        int nMilliseconds = 0;

        if (lUptime > 0)
            {
            nDays = (int)(lUptime / (24L * 60L * 60L * 1000L));
            lHold = lUptime % (24L * 60L * 60L * 1000L);
            nHours = (int)(lHold / (60L * 60L * 1000L));
            lHold %= 60L * 60L * 1000L;
            nMinutes = (int)(lHold / (60L * 1000L));
            lHold %= 60L * 1000L;
            nSecs = (int)(lHold / 1000L);
            nMilliseconds = (int)(lHold % 1000);
            sRet = "" + nDays + " days " + nHours + " hours " + nMinutes + " minutes " + nSecs + " seconds " + nMilliseconds + " ms";
            }

        return (sRet);
        }

    public String NewKillProc(String sProcId, OutputStream out)
        {
        String sRet = "";
        String [] theArgs = new String [3];

        theArgs[0] = "su";
        theArgs[1] = "-c";
        theArgs[2] = "kill " + sProcId;

        try
            {
            pProc = Runtime.getRuntime().exec(theArgs);
            RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
            outThrd.start();
            outThrd.join(5000);
            }
        catch (IOException e)
            {
            sRet = e.getMessage();
            e.printStackTrace();
            }
        catch (InterruptedException e)
            {
            e.printStackTrace();
            }

        return(sRet);
        }

    public String SendPing(String sIPAddr, OutputStream out)
        {
        String sRet = "";
        String [] theArgs = new String [4];

        theArgs[0] = "ping";
        theArgs[1] = "-c";
        theArgs[2] = "3";
        theArgs[3] = sIPAddr;

        try
            {
            pProc = Runtime.getRuntime().exec(theArgs);
            RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
            outThrd.start();
            outThrd.join(5000);
            if (out == null)
                sRet = outThrd.strOutput;
            }
        catch (IOException e)
            {
            sRet = e.getMessage();
            e.printStackTrace();
            }
        catch (InterruptedException e)
            {
            e.printStackTrace();
            }

        return (sRet);
        }

    public String GetTmpDir()
    {
        String     sRet = "";
        Context ctx = contextWrapper.getApplicationContext();
        File dir = ctx.getFilesDir();
        ctx = null;
        try {
            sRet = dir.getCanonicalPath();
            }
        catch (IOException e)
            {
            e.printStackTrace();
            }
        return(sRet);
    }

    public String PrintFileTimestamp(String sFile)
        {
        String     sRet = "";
        String    sTmpFileName = fixFileName(sFile);
        long    lModified = -1;

        if (sTmpFileName.contains("org.mozilla.fennec") || sTmpFileName.contains("org.mozilla.firefox")) {
            ContentResolver cr = contextWrapper.getContentResolver();
            Uri ffxFiles = Uri.parse("content://" + (sTmpFileName.contains("fennec") ? fenProvider : ffxProvider) + "/dir");

            String[] columns = new String[] {
                    "_id",
                    "isdir",
                    "filename",
                    "length",
                    "ts"
                };

            Cursor myCursor = cr.query(    ffxFiles,
                                        columns,         // Which columns to return
                                        sTmpFileName,   // Which rows to return (all rows)
                                        null,           // Selection arguments (none)
                                        null);            // Order clause (none)
            if (myCursor != null) {
                if (myCursor.getCount() > 0) {
                    if (myCursor.moveToPosition(0)) {
                        lModified = myCursor.getLong(myCursor.getColumnIndex("ts"));
                    }
                }
                myCursor.close();
            }
        }
        else {
            File theFile = new File(sTmpFileName);

            if (theFile.exists()) {
                lModified = theFile.lastModified();
            }
        }

        if (lModified != -1) {
            Date dtModified = new Date(lModified);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss:SSS");
            sRet = "Last modified: " + sdf.format(dtModified);
        }
        else {
            sRet = sErrorPrefix + "[" + sTmpFileName + "] doesn't exist";
        }

        return(sRet);
        }

    public String GetIniData(String sSection, String sKey, String sFile)
        {
        String sRet = "";
        String sComp = "";
        String sLine = "";
        boolean bFound = false;
        BufferedReader in = null;
        String    sTmpFileName = fixFileName(sFile);

        try {
            in = new BufferedReader(new FileReader(sTmpFileName));
            sComp = "[" + sSection + "]";
            while ((sLine = in.readLine()) != null)
                {
                if (sLine.equalsIgnoreCase(sComp))
                    {
                    bFound = true;
                    break;
                    }
                }

            if (bFound)
                {
                sComp = (sKey + " =").toLowerCase();
                while ((sLine = in.readLine()) != null)
                    {
                    if (sLine.toLowerCase().contains(sComp))
                        {
                        String [] temp = null;
                        temp = sLine.split("=");
                        if (temp != null)
                            {
                            if (temp.length > 1)
                                sRet = temp[1].trim();
                            }
                        break;
                        }
                    }
                }
            in.close();
            }
        catch (FileNotFoundException e)
            {
            sComp = e.toString();
            }
        catch (IOException e)
            {
            sComp = e.toString();
            }
        return (sRet);
        }

    public String RunReboot(OutputStream out, String sCallBackIP, String sCallBackPort)
        {
        String sRet = "";
        Context ctx = contextWrapper.getApplicationContext();
        String [] theArgs = new String [3];

        theArgs[0] = "su";
        theArgs[1] = "-c";
        theArgs[2] = "reboot";
        try {
            if ((sCallBackIP != null) && (sCallBackPort != null) &&
                (sCallBackIP.length() > 0) && (sCallBackPort.length() > 0))    {
                FileOutputStream fos = ctx.openFileOutput("update.info", Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE);
                String sBuffer = sCallBackIP + "," + sCallBackPort + "\rSystem rebooted\r";
                fos.write(sBuffer.getBytes());
                fos.flush();
                fos.close();
                fos = null;
            }
        } catch (FileNotFoundException e) {
            sRet = sErrorPrefix + "Callback file creation error [rebt] call failed " + e.getMessage();
            e.printStackTrace();
        } catch (IOException e) {
            sRet = sErrorPrefix + "Callback file error [rebt] call failed " + e.getMessage();
            e.printStackTrace();
        }

        try {
            // Tell all of the data channels we are rebooting
            ((ASMozStub)this.contextWrapper).SendToDataChannel("Rebooting ...");

            pProc = Runtime.getRuntime().exec(theArgs);
            RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
            outThrd.start();
            outThrd.join(10000);
        } catch (IOException e) {
            sRet = e.getMessage();
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return (sRet);
        }

    public String UnInstallApp(String sApp, OutputStream out)
        {
        String sRet = "";
        String [] theArgs = new String [3];

        theArgs[0] = "su";
        theArgs[1] = "-c";
        theArgs[2] = "pm uninstall " + sApp + ";reboot;exit";

        try
            {
            pProc = Runtime.getRuntime().exec(theArgs);

            RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
            outThrd.start();
            try {
                outThrd.join(60000);
                int nRet = pProc.exitValue();
                sRet = "\nuninst complete [" + nRet + "]";
                }
            catch (IllegalThreadStateException itse) {
                itse.printStackTrace();
                sRet = "\nuninst command timed out";
                }
            }
        catch (IOException e)
            {
            sRet = e.getMessage();
            e.printStackTrace();
            }
        catch (InterruptedException e)
            {
            e.printStackTrace();
            }

        return (sRet);
    }

    public String InstallApp(String sApp, OutputStream out)
        {
        String sRet = "";
        String [] theArgs = new String [3];
        File    srcFile = new File(sApp);

        theArgs[0] = "su";
        theArgs[1] = "-c";
        theArgs[2] = "mv " + GetTmpDir() + "/" + srcFile.getName() + " /data/local/tmp/" + srcFile.getName() + ";exit";

        sRet = CopyFile(sApp, GetTmpDir() + "/" + srcFile.getName());
        try {
            out.write(sRet.getBytes());
            out.flush();
            }
        catch (IOException e1)
            {
            e1.printStackTrace();
            }

        try
            {
            pProc = Runtime.getRuntime().exec(theArgs);

            RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
            outThrd.start();
            try {
                outThrd.join(90000);
                int nRet = pProc.exitValue();
                sRet = "\nmove complete [" + nRet + "]";
                }
            catch (IllegalThreadStateException itse) {
                itse.printStackTrace();
                sRet = "\nmove command timed out";
            }
            try
                {
                out.write(sRet.getBytes());
                out.flush();
                }
            catch (IOException e1)
                {
                e1.printStackTrace();
                }

            theArgs[2] = "chmod 666 /data/local/tmp/" + srcFile.getName() + ";exit";
            pProc = Runtime.getRuntime().exec(theArgs);
            RedirOutputThread outThrd2 = new RedirOutputThread(pProc, out);
            outThrd2.start();
            try {
                outThrd2.join(10000);
                int nRet2 = pProc.exitValue();
                sRet = "\npermission change complete [" + nRet2 + "]\n";
                }
            catch (IllegalThreadStateException itse) {
                itse.printStackTrace();
                sRet = "\npermission change timed out";
            }
            try {
                out.write(sRet.getBytes());
                out.flush();
                }
            catch (IOException e1)
                {
                e1.printStackTrace();
                }

            theArgs[2] = "pm install -r /data/local/tmp/" + srcFile.getName() + " Cleanup" + ";exit";
            pProc = Runtime.getRuntime().exec(theArgs);
            RedirOutputThread outThrd3 = new RedirOutputThread(pProc, out);
            outThrd3.start();
            try {
                outThrd3.join(60000);
                int nRet3 = pProc.exitValue();
                sRet = "\ninstallation complete [" + nRet3 + "]";
                }
            catch (IllegalThreadStateException itse) {
                itse.printStackTrace();
                sRet = "\npm install command timed out";
            }
            try {
                out.write(sRet.getBytes());
                out.flush();
                }
            catch (IOException e1)
                {
                e1.printStackTrace();
                }

            theArgs[2] = "rm /data/local/tmp/" + srcFile.getName() + ";exit";
            pProc = Runtime.getRuntime().exec(theArgs);
            RedirOutputThread outThrd4 = new RedirOutputThread(pProc, out);
            outThrd4.start();
            try {
                outThrd4.join(60000);
                int nRet4 = pProc.exitValue();
                sRet = "\ntmp file removed [" + nRet4 + "]";
                }
            catch (IllegalThreadStateException itse) {
                itse.printStackTrace();
                sRet = "\nrm command timed out";
            }
            try {
                out.write(sRet.getBytes());
                out.flush();
                }
            catch (IOException e1)
                {
                e1.printStackTrace();
                }
            sRet = "\nSuccess";
            }
        catch (IOException e)
            {
            sRet = e.getMessage();
            e.printStackTrace();
            }
        catch (InterruptedException e)
            {
            e.printStackTrace();
            }

        return (sRet);
        }

    public String StrtUpdtOMatic(String sPkgName, String sPkgFileName, String sCallBackIP, String sCallBackPort)
        {
        String sRet = "";

        Context ctx = contextWrapper.getApplicationContext();
        PackageManager pm = ctx.getPackageManager();

        Intent prgIntent = new Intent();
        prgIntent.setPackage("com.mozilla.watcher");

        try {
            PackageInfo pi = pm.getPackageInfo("com.mozilla.watcher", PackageManager.GET_SERVICES | PackageManager.GET_INTENT_FILTERS);
            ServiceInfo [] si = pi.services;
            for (int i = 0; i < si.length; i++)
                {
                ServiceInfo s = si[i];
                if (s.name.length() > 0)
                    {
                    prgIntent.setClassName(s.packageName, s.name);
                    break;
                    }
                }
            }
        catch (NameNotFoundException e)
            {
            e.printStackTrace();
            sRet = sErrorPrefix + "watcher is not properly installed";
            return(sRet);
            }

        prgIntent.putExtra("command", "updt");
        prgIntent.putExtra("pkgName", sPkgName);
        prgIntent.putExtra("pkgFile", sPkgFileName);
        prgIntent.putExtra("reboot", true);

        try
            {
            if ((sCallBackIP != null) && (sCallBackPort != null) &&
                (sCallBackIP.length() > 0) && (sCallBackPort.length() > 0))
                {
                FileOutputStream fos = ctx.openFileOutput("update.info", Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE);
                String sBuffer = sCallBackIP + "," + sCallBackPort + "\rupdate started " + sPkgName + " " + sPkgFileName + "\r";
                fos.write(sBuffer.getBytes());
                fos.flush();
                fos.close();
                fos = null;
                prgIntent.putExtra("outFile", ctx.getFilesDir() + "/update.info");
                }
            else {
                if (prgIntent.hasExtra("outFile")) {
                    System.out.println("outFile extra unset from intent");
                    prgIntent.removeExtra("outFile");
                }
            }

            ComponentName cn = contextWrapper.startService(prgIntent);
            if (cn != null)
                sRet = "exit";
            else
                sRet = sErrorPrefix + "Unable to use watcher service";
            }
        catch(ActivityNotFoundException anf)
            {
            sRet = sErrorPrefix + "Activity Not Found Exception [updt] call failed";
            anf.printStackTrace();
            }
        catch (FileNotFoundException e)
            {
            sRet = sErrorPrefix + "File creation error [updt] call failed";
            e.printStackTrace();
            }
        catch (IOException e)
            {
            sRet = sErrorPrefix + "File error [updt] call failed";
            e.printStackTrace();
            }

        ctx = null;

        return (sRet);
        }

    public String StartJavaPrg(String [] sArgs, Intent preIntent)
        {
        String sRet = "";
        String sArgList = "";
        String sUrl = "";
//        String sRedirFileName = "";
        Intent prgIntent = null;

        Context ctx = contextWrapper.getApplicationContext();
        PackageManager pm = ctx.getPackageManager();

        if (preIntent == null)
            prgIntent = new Intent();
        else
            prgIntent = preIntent;

        prgIntent.setPackage(sArgs[0]);
        prgIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        try {
            PackageInfo pi = pm.getPackageInfo(sArgs[0], PackageManager.GET_ACTIVITIES | PackageManager.GET_INTENT_FILTERS);
            ActivityInfo [] ai = pi.activities;
            for (int i = 0; i < ai.length; i++)
                {
                ActivityInfo a = ai[i];
                if (a.name.length() > 0)
                    {
                    prgIntent.setClassName(a.packageName, a.name);
                    break;
                    }
                }
            }
        catch (NameNotFoundException e)
            {
            e.printStackTrace();
            }

        if (sArgs.length > 1)
            {
            if (sArgs[0].contains("android.browser"))
                prgIntent.setAction(Intent.ACTION_VIEW);
            else
                prgIntent.setAction(Intent.ACTION_MAIN);

            if (sArgs[0].contains("fennec"))
                {
                sArgList = "";
                sUrl = "";

                for (int lcv = 1; lcv < sArgs.length; lcv++)
                    {
                    if (sArgs[lcv].contains("://"))
                        {
                        prgIntent.setAction(Intent.ACTION_VIEW);
                        sUrl = sArgs[lcv];
                        }
                    else
                        {
                        if (sArgs[lcv].equals(">"))
                            {
                            lcv++;
                            if (lcv < sArgs.length)
                                lcv++;
//                                sRedirFileName = sArgs[lcv++];
                            }
                        else
                            sArgList += " " + sArgs[lcv];
                        }
                    }

                if (sArgList.length() > 0)
                    prgIntent.putExtra("args", sArgList.trim());

                if (sUrl.length() > 0)
                    prgIntent.setData(Uri.parse(sUrl.trim()));
                }
            else
                {
                for (int lcv = 1; lcv < sArgs.length; lcv++)
                    sArgList += " " + sArgs[lcv];

                prgIntent.setData(Uri.parse(sArgList.trim()));
                }
            }
        else
            {
            prgIntent.setAction(Intent.ACTION_MAIN);
            }

        try
            {
            contextWrapper.startActivity(prgIntent);
            FindProcThread findProcThrd = new FindProcThread(contextWrapper, sArgs[0]);
            findProcThrd.start();
            findProcThrd.join(7000);
            if (!findProcThrd.bFoundIt && !findProcThrd.bStillRunning) {
                sRet = "Unable to start " + sArgs[0] + "";
                }
            }
        catch(ActivityNotFoundException anf)
            {
            anf.printStackTrace();
            }
        catch (InterruptedException e) {
            e.printStackTrace();
        }

        ctx = null;
        return (sRet);
        }

    public String StartPrg(String [] progArray, OutputStream out)
        {
        String sRet = "";
        int    lcv = 0;

        try
            {
            pProc = Runtime.getRuntime().exec(progArray);
            RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
            outThrd.start();
            while (lcv < 30) {
                try {
                    outThrd.join(10000);
                    int nRetCode = pProc.exitValue();
                    sRet = "return code [" + nRetCode + "]";
                    break;
                    }
                catch (IllegalThreadStateException itse) {
                    lcv++;
                    itse.printStackTrace();
                    }
                }
            }
        catch (IOException e)
            {
            e.printStackTrace();
            }
        catch (InterruptedException e)
            {
            e.printStackTrace();
            sRet = "Timed out!";
            }

        return (sRet);
        }

    public String StartPrg2(String [] progArray, OutputStream out)
        {
        String sRet = "";

        int    nArraySize = 0;
        int    nArgs = progArray.length - 1; // 1st arg is the environment string
        int    lcv    = 0;
        int    temp = 0;

        String sEnvString = progArray[0];

        if (!sEnvString.contains("=") && (sEnvString.length() > 0))
            {
            if (sEnvString.contains("/") || sEnvString.contains("\\") || !sEnvString.contains("."))
                sRet = StartPrg(progArray, out);
            else
                sRet = StartJavaPrg(progArray, null);
            return(sRet);
            }

        // Set up command line args stripping off the environment string
        String [] theArgs = new String [nArgs];
        for (lcv = 0; lcv < nArgs; lcv++)
            {
            theArgs[lcv] = progArray[lcv + 1];
            }

        try
            {
            String [] envStrings = sEnvString.split(",");
            Map<String, String> newEnv = new HashMap<String, String>();

            for (lcv = 0; lcv < envStrings.length; lcv++)
                {
                temp = envStrings[lcv].indexOf("=");
                if (temp > 0)
                    {
                    newEnv.put(    envStrings[lcv].substring(0, temp),
                                envStrings[lcv].substring(temp + 1, envStrings[lcv].length()));
                    }
                }

            Map<String, String> sysEnv = System.getenv();

            nArraySize = sysEnv.size();

            for (Map.Entry<String, String> entry : newEnv.entrySet())
                {
                if (!sysEnv.containsKey(entry.getKey()))
                    {
                    nArraySize++;
                    }
                }

            String[] envArray = new String[nArraySize];

            int        i = 0;
            int        offset;
            String    sKey = "";
            String     sValue = "";

            for (Map.Entry<String, String> entry : sysEnv.entrySet())
                {
                sKey = entry.getKey();
                if (newEnv.containsKey(sKey))
                    {
                    sValue = newEnv.get(sKey);
                    if ((offset = sValue.indexOf("$" + sKey)) != -1)
                        {
                        envArray[i++] = sKey +
                                        "=" +
                                        sValue.substring(0, offset) +
                                        entry.getValue() +
                                        sValue.substring(offset + sKey.length() + 1);
                        }
                    else
                        envArray[i++] = sKey + "=" + sValue;
                    newEnv.remove(sKey);
                    }
                else
                    envArray[i++] = entry.getKey() + "=" + entry.getValue();
                }

            for (Map.Entry<String, String> entry : newEnv.entrySet())
                {
                envArray[i++] = entry.getKey() + "=" + entry.getValue();
                }

            if (theArgs[0].contains("/") || theArgs[0].contains("\\") || !theArgs[0].contains("."))
                {
                pProc = Runtime.getRuntime().exec(theArgs, envArray);

                RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
                outThrd.start();

                lcv = 0;

                while (lcv < 30) {
                    try {
                        outThrd.join(10000);
                        int nRetCode = pProc.exitValue();
                        sRet = "return code [" + nRetCode + "]";
                        lcv = 30;
                        }
                    catch (IllegalThreadStateException itse) {
                        lcv++;
                        itse.printStackTrace();
                        }
                    }
                }
            else
                {
                Intent preIntent = new Intent();
                for (lcv = 0; lcv < envArray.length; lcv++)
                    {
                    preIntent.putExtra("env" + lcv, envArray[lcv]);
                    }
                sRet = StartJavaPrg(theArgs, preIntent);
                }
            }
        catch(UnsupportedOperationException e)
            {
            if (e != null)
                e.printStackTrace();
            }
        catch(ClassCastException e)
            {
            if (e != null)
                e.printStackTrace();
            }
        catch(IllegalArgumentException e)
            {
            if (e != null)
                e.printStackTrace();
            }
        catch(NullPointerException e)
            {
            if (e != null)
                e.printStackTrace();
            }
        catch (IOException e)
            {
            e.printStackTrace();
            }
        catch (InterruptedException e)
            {
            e.printStackTrace();
            sRet = "Timed out!";
            }

        return (sRet);
        }

    private String PrintUsage()
        {
        String sRet =
            "run [cmdline]                - start program no wait\n" +
            "exec [env pairs] [cmdline]   - start program no wait optionally pass env\n" +
            "                               key=value pairs (comma separated)\n" +
            "kill [program name]          - kill program no path\n" +
            "killall                      - kill all processes started\n" +
            "ps                           - list of running processes\n" +
            "info                         - list of device info\n" +
            "        [os]                 - os version for device\n" +
            "        [id]                 - unique identifier for device\n" +
            "        [uptime]             - uptime for device\n" +
            "        [systime]            - current system time\n" +
            "        [screen]             - width, height and bits per pixel for device\n" +
            "        [memory]             - physical, free, available, storage memory\n" +
            "                               for device\n" +
            "        [processes]          - list of running processes see 'ps'\n" +
            "deadman timeout              - set the duration for the deadman timer\n" +
            "alrt [on/off]                - start or stop sysalert behavior\n" +
            "disk [arg]                   - prints disk space info\n" +
            "cp file1 file2               - copy file1 to file2\n" +
            "time file                    - timestamp for file\n" +
            "hash file                    - generate hash for file\n" +
            "cd directory                 - change cwd\n" +
            "cat file                     - cat file\n" +
            "cwd                          - display cwd\n" +
            "mv file1 file2               - move file1 to file2\n" +
            "push filename                - push file to device\n" +
            "rm file                      - delete file\n" +
            "rmdr directory               - delete directory even if not empty\n" +
            "mkdr directory               - create directory\n" +
            "dirw directory               - tests whether the directory is writable\n" +
            "isdir directory              - test whether the directory exists\n" +
            "stat processid               - stat process\n" +
            "dead processid               - print whether the process is alive or hung\n" +
            "mems                         - dump memory stats\n" +
            "ls                           - print directory\n" +
            "tmpd                         - print temp directory\n" +
            "ping [hostname/ipaddr]       - ping a network device\n" +
            "unzp zipfile destdir         - unzip the zipfile into the destination dir\n" +
            "zip zipfile src              - zip the source file/dir into zipfile\n" +
            "rebt                         - reboot device\n" +
            "inst /path/filename.apk      - install the referenced apk file\n" +
            "uninst packagename           - uninstall the referenced package\n" +
            "updt pkgname pkgfile         - unpdate the referenced package\n" +
            "clok                         - the current device time expressed as the" +
            "                               number of millisecs since epoch\n" +
            "settime date time            - sets the device date and time\n" +
            "                               (YYYY/MM/DD HH:MM:SS)\n" +
            "tzset timezone               - sets the device timezone format is\n" +
            "                               GMTxhh:mm x = +/- or a recognized Olsen string\n" +
            "tzget                        - returns the current timezone set on the device\n" +
            "rebt                         - reboot device\n" +
            "adb ip|usb                   - set adb to use tcp/ip on port 5555 or usb\n" +
            "quit                         - disconnect SUTAgent\n" +
            "exit                         - close SUTAgent\n" +
            "ver                          - SUTAgent version\n" +
            "help                         - you're reading it";
        return (sRet);
        }
}
