/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

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.io.RandomAccessFile;
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.StringTokenizer;
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.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.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Surface;
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 static final int DEFAULT_STARTPRG_TIMEOUT_SECONDS = 300;

    public final String prgVersion = "SUTAgentAndroid Version 1.20";

    public enum Command
        {
        RUN ("run"),
        EXEC ("exec"),
        EXECSU ("execsu"),
        EXECCWD ("execcwd"),
        EXECCWDSU ("execcwdsu"),
        EXECEXT ("execext"),
        ENVRUN ("envrun"),
        KILL ("kill"),
        PS ("ps"),
        DEVINFO ("info"),
        OS ("os"),
        ID ("id"),
        UPTIME ("uptime"),
        UPTIMEMILLIS ("uptimemillis"),
        SUTUPTIMEMILLIS ("sutuptimemillis"),
        SETTIME ("settime"),
        SYSTIME ("systime"),
        SCREEN ("screen"),
        ROTATION ("rotation"),
        MEMORY ("memory"),
        POWER ("power"),
        PROCESS ("process"),
        SUTUSERINFO ("sutuserinfo"),
        TEMPERATURE ("temperature"),
        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"),
        UNINSTALL ("uninstall"),
        TEST ("test"),
        DBG ("dbg"),
        TRACE ("trace"),
        VER ("ver"),
        TZGET ("tzget"),
        TZSET ("tzset"),
        ADB ("adb"),
        CHMOD ("chmod"),
        TOPACTIVITY ("activity"),
        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());
                        } catch (NumberFormatException nfe) {
                            lOff = 0;
                            System.out.println("NumberFormatException: " + nfe.getMessage());
                        }
                    }
                    if (Argc == 4) {
                        try {
                            lLen = Long.parseLong(Argv[3].trim());
                        } 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());
                        }
                    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, true);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for uninst command!";
                break;

            case UNINSTALL:
                if (Argc >= 2)
                    strReturn = UnInstallApp(Argv[1], cmdOut, false);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for uninstall 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 += GetUptimeMillis();
                    strReturn += "\n";
                    strReturn += GetSutUptimeMillis();
                    strReturn += "\n";
                    strReturn += GetScreenInfo();
                    strReturn += "\n";
                    strReturn += GetRotationInfo();
                    strReturn += "\n";
                    strReturn += GetMemoryInfo();
                    strReturn += "\n";
                    strReturn += GetPowerInfo();
                    strReturn += "\n";
                    strReturn += GetTemperatureInfo();
                    strReturn += "\n";
                    strReturn += GetProcessInfo();
                    strReturn += "\n";
                    strReturn += GetSutUserInfo();
                    strReturn += "\n";
                    strReturn += GetDiskInfo("/data");
                    strReturn += "\n";
                    strReturn += GetDiskInfo("/system");
                    strReturn += "\n";
                    strReturn += GetDiskInfo("/mnt/sdcard");
                    }
                else
                    {
                    cSubCmd = Command.getCmd(Argv[1]);
                    switch(cSubCmd)
                        {
                        case ID:
                            strReturn = SUTAgentAndroid.sUniqueID;
                            break;

                        case SCREEN:
                            strReturn = GetScreenInfo();
                            break;

                        case ROTATION:
                            strReturn = GetRotationInfo();
                            break;

                        case PROCESS:
                            strReturn = GetProcessInfo();
                            break;

                        case OS:
                            strReturn = GetOSInfo();
                            break;

                        case SYSTIME:
                            strReturn = GetSystemTime();
                            break;

                        case UPTIME:
                            strReturn = GetUptime();
                            break;

                        case UPTIMEMILLIS:
                            strReturn = GetUptimeMillis();
                            break;

                        case SUTUPTIMEMILLIS:
                            strReturn = GetSutUptimeMillis();
                            break;

                        case MEMORY:
                            strReturn = GetMemoryInfo();
                            break;

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

                        case SUTUSERINFO:
                            strReturn += GetSutUserInfo();
                            break;

                        case TEMPERATURE:
                            strReturn += GetTemperatureInfo();
                            break;

                        case DISK:
                            strReturn += "\n";
                            strReturn += GetDiskInfo("/data");
                            strReturn += "\n";
                            strReturn += GetDiskInfo("/system");
                            strReturn += "\n";
                            strReturn += GetDiskInfo("/mnt/sdcard");
                            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, null, false, DEFAULT_STARTPRG_TIMEOUT_SECONDS);
                    }
                else
                    {
                    strReturn = sErrorPrefix + "Wrong number of arguments for " + Argv[0] + " command!";
                    }
                break;

            case EXECSU:
                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, null, true, DEFAULT_STARTPRG_TIMEOUT_SECONDS);
                    }
                else
                    {
                    strReturn = sErrorPrefix + "Wrong number of arguments for " + Argv[0] + " command!";
                    }
                break;

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

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

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

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

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

                    strReturn = StartPrg2(theArgs, cmdOut, Argv[1], true, DEFAULT_STARTPRG_TIMEOUT_SECONDS);
                    }
                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, false, DEFAULT_STARTPRG_TIMEOUT_SECONDS);
                    else
                        strReturn = StartJavaPrg(theArgs, null);
                    }
                else
                    {
                    strReturn = sErrorPrefix + "Wrong number of arguments for " + Argv[0] + " command!";
                    }
                break;

            case EXECEXT:
                // An "extended" exec command with format:
                //    execext [su] [cwd=<path>] [t=<timeout in seconds>] arg1 ...
                if (Argc >= 2)
                    {
                    boolean su = false;
                    String cwd = null;
                    int timeout = DEFAULT_STARTPRG_TIMEOUT_SECONDS;
                    int extra;
                    for (extra = 1; extra < Argc; extra++)
                        {
                        if (Argv[extra].equals("su"))
                            {
                            su = true;
                            }
                        else if (Argv[extra].startsWith("cwd="))
                            {
                            cwd = Argv[extra].substring(4);
                            }
                        else if (Argv[extra].startsWith("t="))
                            {
                            timeout = Integer.parseInt(Argv[extra].substring(2));
                            if (timeout < 1 || timeout > 4*60*60)
                                {
                                Log.e("SUTAgentAndroid", 
                                  "invalid execext timeout "+Argv[extra].substring(2)+"; using default instead");
                                timeout = DEFAULT_STARTPRG_TIMEOUT_SECONDS;
                                }
                            }
                        else
                            {
                            break;
                            }
                        }

                    if (extra < Argc)
                        {
                        String [] theArgs = new String [Argc - extra];
                        for (int lcv = extra; lcv < Argc; lcv++)
                            {
                            theArgs[lcv - extra] = Argv[lcv];
                            }

                        strReturn = StartPrg2(theArgs, cmdOut, cwd, su, timeout);
                        }
                    else
                        {
                        strReturn = sErrorPrefix + "No regular arguments for " + Argv[0] + " command!";
                        }
                    }
                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 CHMOD:
                if (Argc == 2)
                    strReturn = ChmodDir(Argv[1]);
                else
                    strReturn = sErrorPrefix + "Wrong number of arguments for chmod command!";
                break;

            case TOPACTIVITY:
                strReturn = TopActivity();
                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("\"") || workingString.startsWith("'"))
                {
                char quoteChar = '"';
                if (workingString.startsWith("\'"))
                    quoteChar = '\'';

                // point to the first non quote char
                nStart = 1;
                // find the matching quote
                nEnd = workingString.indexOf(quoteChar, 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(quoteChar, 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, convert them into standard ones
            while (workingString2.contains("\\\"") || workingString2.contains("\\'"))
                {
                    workingString2 = workingString2.replace("\\\"", "\"");
                    workingString2 = workingString2.replace("\\'", "'");
                }

            // 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 + " successfully 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 void FixDataLocalPermissions()
        {
        String chmodResult;
        File localDir = new java.io.File("/data/local");
        if (!localDir.canWrite()) {
            chmodResult = ChmodDir("/data/local");
            Log.i("SUTAgentAndroid", "Changed permissions on /data/local to make it writable: " + chmodResult);
        }
        File tmpDir = new java.io.File("/data/local/tmp");
        if (tmpDir.exists() && !tmpDir.isDirectory()) {
            if (!tmpDir.delete()) {
                Log.e("SUTAgentAndroid", "Could not delete file /data/local/tmp");
            }
        }
        if (!tmpDir.exists() && !tmpDir.mkdirs()) {
            Log.e("SUTAgentAndroid", "Could not create directory /data/local/tmp");
        }
        chmodResult = ChmodDir("/data/local/tmp");
        Log.i("SUTAgentAndroid", "Changed permissions on /data/local/tmp to make it writable: " + chmodResult);
        }

    private Boolean _SetTestRoot(String testroot)
        {
        String isWritable = IsDirWritable(testroot);
        if (isWritable.contains(sErrorPrefix) || isWritable.contains("is not writable")) {
            Log.w("SUTAgentAndroid", isWritable);
            Log.w("SUTAgentAndroid", "Unable to set device root to " + testroot);
            return false;
        }

        Log.i("SUTAgentAndroid", "Set device root to " + testroot);
        SUTAgentAndroid.sTestRoot = testroot;
        return true;
        }

    public void SetTestRoot(String testroot)
        {
        Boolean success = false;
        if (!testroot.equals("")) {
            // Device specified the required testroot.
            success = _SetTestRoot(testroot);
            if (!success) {
                Log.e("SUTAgentAndroid", "Unable to set device root to " + testroot);
            }
        } else {
            // Detect the testroot.
            // Attempt external storage.
            success = _SetTestRoot(Environment.getExternalStorageDirectory().getAbsolutePath());
            if (!success) {
                Log.e("SUTAgentAndroid", "Cannot access world writeable test root");
            }
        }
        if (!success) {
            SUTAgentAndroid.sTestRoot = sErrorPrefix + " unable to determine test root";
        }
        }

    public String GetTestRoot()
        {
        if (SUTAgentAndroid.sTestRoot.equals("")) {
            SetTestRoot("");
        }
        return SUTAgentAndroid.sTestRoot;
        }

    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.getApplicationInfo().dataDir;
                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);
        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 {
                    pProc = Runtime.getRuntime().exec(this.getSuArgs("ls -l " + sDir));
                    RedirOutputThread outThrd = new RedirOutputThread(pProc, null);
                    outThrd.start();
                    outThrd.joinAndStopRedirect(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";
            }
            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 GetRotationInfo()
        {
            WindowManager wMgr = (WindowManager) contextWrapper.getSystemService(Context.WINDOW_SERVICE);
            int nRotationDegrees = 0; // default
            switch(wMgr.getDefaultDisplay().getRotation())
                {
                case Surface.ROTATION_90:
                    nRotationDegrees = 90;
                    break;
                case Surface.ROTATION_180:
                    nRotationDegrees = 180;
                    break;
                case Surface.ROTATION_270:
                    nRotationDegrees = 270;
                    break;
                }
            return "ROTATION:" + nRotationDegrees;
        }

    public String SetADB(String sWhat) {
        String sRet = "";
        String sTmp = "";
        String sCmd;

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

        try {
            pProc = Runtime.getRuntime().exec(this.getSuArgs(sCmd));
            RedirOutputThread outThrd = new RedirOutputThread(pProc, null);
            outThrd.start();
            outThrd.joinAndStopRedirect(5000);
            sTmp = outThrd.strOutput;
            Log.e("ADB", sTmp);
            if (outThrd.nExitCode == 0) {
                pProc = Runtime.getRuntime().exec(this.getSuArgs("stop adbd"));
                outThrd = new RedirOutputThread(pProc, null);
                outThrd.start();
                outThrd.joinAndStopRedirect(5000);
                sTmp = outThrd.strOutput;
                Log.e("ADB", sTmp);
                if (outThrd.nExitCode == 0) {
                    pProc = Runtime.getRuntime().exec(this.getSuArgs("start adbd"));
                    outThrd = new RedirOutputThread(pProc, null);
                    outThrd.start();
                    outThrd.joinAndStopRedirect(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 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 sFoundProcName = "";
        int nProcs = 0;
        boolean bFoundAppProcess = false;

        if (lProcesses != null)
            nProcs = lProcesses.size();

        for (lcv = 0; lcv < nProcs; lcv++)
            {
            if (lProcesses.get(lcv).processName.contains(sProcName))
                {
                sFoundProcName = lProcesses.get(lcv).processName;
                bFoundAppProcess = true;

                try
                    {
                    pProc = Runtime.getRuntime().exec(this.getSuArgs("kill " + lProcesses.get(lcv).pid));
                    RedirOutputThread outThrd = new RedirOutputThread(pProc, null);
                    outThrd.start();
                    outThrd.joinAndStopRedirect(15000);
                    String sTmp = outThrd.strOutput;
                    Log.e("KILLPROCESS", sTmp);
                    }
                catch (IOException e)
                    {
                    sRet = e.getMessage();
                    e.printStackTrace();
                    }
                catch (InterruptedException e)
                    {
                    e.printStackTrace();
                    }
                }
            }

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

            sRet = "Successfully killed " + sProcName + "\n";
            lProcesses = aMgr.getRunningAppProcesses();
            nProcs = 0;
            if (lProcesses != null)
                nProcs = lProcesses.size();
            for (lcv = 0; lcv < nProcs; lcv++)
                {
                if (lProcesses.get(lcv).processName.contains(sProcName))
                    {
                    sRet = sErrorPrefix + "Unable to kill " + sProcName + " (couldn't kill " + sFoundProcName +")\n";
                    break;
                    }
                }
            }
        else
            {
            // To kill processes other than Java applications - processes
            // like xpcshell - a different strategy is necessary: use ps
            // to find the process' PID.
            try
                {
                pProc = Runtime.getRuntime().exec("ps");
                RedirOutputThread outThrd = new RedirOutputThread(pProc, null);
                outThrd.start();
                outThrd.joinAndStopRedirect(10000);
                String sTmp = outThrd.strOutput;
                StringTokenizer stokLines = new StringTokenizer(sTmp, "\n");
                while(stokLines.hasMoreTokens())
                    {
                    String sLine = stokLines.nextToken();
                    StringTokenizer stokColumns = new StringTokenizer(sLine, " \t\n");
                    stokColumns.nextToken();
                    String sPid = stokColumns.nextToken();
                    stokColumns.nextToken();
                    stokColumns.nextToken();
                    stokColumns.nextToken();
                    stokColumns.nextToken();
                    stokColumns.nextToken();
                    stokColumns.nextToken();
                    String sName = null;
                    if (stokColumns.hasMoreTokens())
                        {
                        sName = stokColumns.nextToken();
                        if (sName.contains(sProcName))
                            {
                            NewKillProc(sPid, out);
                            sRet = "Successfully killed " + sName + "\n";
                            }
                        }
                    }
                }
            catch (Exception e)
                {
                e.printStackTrace();
                }
            }

        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 = 0;
        int lcv = 0;
        String strProcName = "";
        int    nPID = 0;
        int nUser = 0;

        if (lProcesses != null) 
            nProcs = lProcesses.size();

        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);
        }

    public String GetSutUserInfo()
        {
        String sRet = "";
        try {
            // based on patch in https://bugzilla.mozilla.org/show_bug.cgi?id=811763
            Context context = contextWrapper.getApplicationContext();
            Object userManager = context.getSystemService("user");
            if (userManager != null) {
                // if userManager is non-null that means we're running on 4.2+ and so the rest of this
                // should just work
                Object userHandle = android.os.Process.class.getMethod("myUserHandle", (Class[])null).invoke(null);
                Object userSerial = userManager.getClass().getMethod("getSerialNumberForUser", userHandle.getClass()).invoke(userManager, userHandle);
                sRet += "User Serial:" + userSerial.toString();
            }
        } catch (Exception e) {
            // Guard against any unexpected failures
            e.printStackTrace();
        }

        return sRet;
        }

    public String GetTemperatureInfo()
        {
        String sTempVal = "unknown";
        String sDeviceFile = "/sys/bus/platform/devices/temp_sensor_hwmon.0/temp1_input";
        try {
            pProc = Runtime.getRuntime().exec(this.getSuArgs("cat " + sDeviceFile));
            RedirOutputThread outThrd = new RedirOutputThread(pProc, null);
            outThrd.start();
            outThrd.joinAndStopRedirect(5000);
            String output = outThrd.strOutput;
            // this only works on pandas (with the temperature sensors turned
            // on), other platforms we just get a file not found error... we'll
            // just return "unknown" for that case
            try {
                sTempVal = String.valueOf(Integer.parseInt(output.trim()) / 1000.0);
            } catch (NumberFormatException e) {
                // not parsed! probably not a panda
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "Temperature: " + sTempVal;
        }

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

        long nBlockCount = statFS.getBlockCount();
        long nBlockSize = statFS.getBlockSize();
        long nBlocksAvail = statFS.getAvailableBlocks();
        // Free is often the same as Available, but can include reserved
        // blocks that are not available to normal applications.
        // long nBlocksFree = statFS.getFreeBlocks();

        sRet = sPath + ": " + (nBlockCount * nBlockSize) + " total, " + (nBlocksAvail * nBlockSize) + " available";

        return (sRet);
        }

    public String GetMemoryInfo()
        {
        String sRet = "PA:" + GetMemoryConfig() + ", FREE: " + GetMemoryUsage();
        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 long GetMemoryUsage()
        {

        String load = "";
        try {
            RandomAccessFile reader = new RandomAccessFile("/proc/meminfo", "r");
            load = reader.readLine(); // Read in the MemTotal
            load = reader.readLine(); // Read in the MemFree
        } catch (IOException ex) {
            return (0);
        }

        String[] toks = load.split(" ");
        int i = 1;
        for (i=1; i < toks.length; i++) {
            String val = toks[i].trim();
            if (!val.equals("")) {
                break;
            }
        }
        if (i <= toks.length) {
            long lMem = Long.parseLong(toks[i].trim());
            return (lMem * 1024);
        }
        return (0);
        }

    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 = "";

        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 {
            sRet += "Invalid argument(s)";
        }

        // if we have an argument
        if (sMillis.length() > 0) {
            try {
                pProc = Runtime.getRuntime().exec(this.getSuArgs("date -u " + sMillis));
                RedirOutputThread outThrd = new RedirOutputThread(pProc, null);
                outThrd.start();
                outThrd.joinAndStopRedirect(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 GetUptimeMillis()
        {
        return Long.toString(SystemClock.uptimeMillis());
        }
 
    public String GetSutUptimeMillis()
        {
        long now = System.currentTimeMillis();
        return "SUTagent running for "+Long.toString(now - SUTAgentAndroid.nCreateTimeMillis)+" ms";
        }
 
    public String NewKillProc(String sProcId, OutputStream out)
        {
        String sRet = "";

        try
            {
            pProc = Runtime.getRuntime().exec("kill "+sProcId);
            RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
            outThrd.start();
            outThrd.joinAndStopRedirect(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.joinAndStopRedirect(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();

        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(this.getSuArgs("reboot"));
            RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
            outThrd.start();
            outThrd.joinAndStopRedirect(10000);
        } catch (IOException e) {
            sRet = e.getMessage();
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return (sRet);
        }

    private String [] getSuArgs(String cmdString)
        {
        String [] theArgs = new String [3];
        theArgs[0] = "su";
        theArgs[1] = "-c";
        // as a security measure, ICS and later resets LD_LIBRARY_PATH. reset
        // it here when executing the command
        theArgs[2] = "LD_LIBRARY_PATH=/vendor/lib:/system/lib " + cmdString;
        return theArgs;
        }

    public String UnInstallApp(String sApp, OutputStream out, boolean reboot)
        {
        String sRet = "";

        try
            {
            if (reboot == true) {
                pProc = Runtime.getRuntime().exec(this.getSuArgs("pm uninstall " + sApp + ";reboot;exit"));
            } else {
                pProc = Runtime.getRuntime().exec(this.getSuArgs("pm uninstall " + sApp + ";exit"));
            }

            RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
            outThrd.start();
            try {
                outThrd.joinAndStopRedirect(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 = "";
        File    srcFile = new File(sApp);

        try
            {
            // on android 4.2 and above, we want to pass the "-d" argument to pm so that version
            // downgrades are allowed... (option unsupported in earlier versions)
            String sPmCmd;

            if (android.os.Build.VERSION.SDK_INT >= 17) { // JELLY_BEAN_MR1
                sPmCmd = "pm install -r -d " + sApp + " Cleanup;exit";
            } else {
                sPmCmd = "pm install -r " + sApp + " Cleanup;exit";
            }
            pProc = Runtime.getRuntime().exec(this.getSuArgs(sPmCmd));
            RedirOutputThread outThrd3 = new RedirOutputThread(pProc, out);
            outThrd3.start();
            try {
                outThrd3.joinAndStopRedirect(60000);
                int nRet3 = pProc.exitValue();
                if (nRet3 == 0) {
                    sRet = "\ninstallation complete [0]\n";
                }
                else {
                    sRet = "\nFailure pm install [" + nRet3 + "]\n";
                }
                }
            catch (IllegalThreadStateException itse) {
                itse.printStackTrace();
                sRet = "\nFailure pm install command timed out\n";
            }
            try {
                out.write(sRet.getBytes());
                out.flush();
                }
            catch (IOException e1)
                {
                e1.printStackTrace();
                }
            }
        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);

        // Get the main activity for this package
        try {
            final ComponentName c = pm.getLaunchIntentForPackage(sArgs[0]).getComponent();
            prgIntent.setClassName(c.getPackageName(), c.getClassName());
        } catch (Exception e) {
            e.printStackTrace();
            return "Unable to find main activity for package: " + sArgs[0];
        }

        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") || sArgs[0].contains("firefox"))
                {
                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, boolean startAsRoot, int timeoutSeconds)
        {
        String sRet = "";
        int    lcv = 0;

        try {
            if (startAsRoot)
                {
                    // we need to requote the program string here, in case
                    // there's spaces or other characters which need quoting
                    // before being passed to su
                    List<String> quotedProgList = new ArrayList<String>();
                    for (String arg : progArray)
                        {
                            String quotedArg = arg;
                            quotedArg = quotedArg.replace("\"", "\\\"");
                            quotedArg = quotedArg.replace("\'", "\\\'");
                            if (quotedArg.contains(" "))
                                {
                                    quotedArg = "\"" + quotedArg + "\"";
                                }
                            quotedProgList.add(quotedArg);
                        }
                    pProc = Runtime.getRuntime().exec(this.getSuArgs(TextUtils.join(" ", quotedProgList)));
                }
            else
                {
                    pProc = Runtime.getRuntime().exec(progArray);
                }
            RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
            outThrd.start();
            try {
                outThrd.join(timeoutSeconds * 1000);
                int nRetCode = pProc.exitValue();
                sRet = "return code [" + nRetCode + "]";
                }
            catch (IllegalThreadStateException itse) {
                }
            outThrd.stopRedirect();
            }
        catch (IOException e)
            {
            e.printStackTrace();
            }
        catch (InterruptedException e)
            {
            e.printStackTrace();
            sRet = "Timed out!";
            }

        return (sRet);
        }

    public String StartPrg2(String [] progArray, OutputStream out, String cwd, boolean startAsRoot, int timeoutSeconds)
        {
        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, startAsRoot, timeoutSeconds);
            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("."))
                {
                if (cwd != null)
                    {
                    File f = new File(cwd);
                    pProc = Runtime.getRuntime().exec(theArgs, envArray, f);
                    }
                else
                    {
                    pProc = Runtime.getRuntime().exec(theArgs, envArray);
                    }

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

                try {
                    outThrd.join(timeoutSeconds * 1000);
                    int nRetCode = pProc.exitValue();
                    sRet = "return code [" + nRetCode + "]";
                    }
                catch (IllegalThreadStateException itse) {
                    }
                outThrd.stopRedirect();
                }
            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);
        }

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

        File dir = new File(sTmpDir);

        if (dir.isDirectory()) {
            sRet = "Changing permissions for " + 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" + ChmodDir(sSubDir);
                        }
                        else {
                            // set the new file's permissions to rwxrwxrwx, if possible
                            try {
                                Process pProc = Runtime.getRuntime().exec(this.getSuArgs("chmod 777 "+files[lcv]));
                                RedirOutputThread outThrd = new RedirOutputThread(pProc, null);
                                outThrd.start();
                                outThrd.joinAndStopRedirect(5000);
                                sRet += "\n\tchmod " + files[lcv].getName() + " ok";
                            } catch (InterruptedException e) {
                                sRet += "\n\ttimeout waiting for chmod " + files[lcv].getName();
                            } catch (IOException e) {
                                sRet += "\n\tunable to chmod " + files[lcv].getName();
                            }
                        }
                    }
                }
                else
                    sRet += "\n\t<empty>";
                }
            }

        // set the new directory's (or file's) permissions to rwxrwxrwx, if possible
        try {
            Process pProc = Runtime.getRuntime().exec(this.getSuArgs("chmod 777 "+sTmpDir));
            RedirOutputThread outThrd = new RedirOutputThread(pProc, null);
            outThrd.start();
            outThrd.joinAndStopRedirect(5000);
            sRet += "\n\tchmod " + sTmpDir + " ok";
        } catch (InterruptedException e) {
            sRet += "\n\ttimeout waiting for chmod " + sTmpDir;
        } catch (IOException e) {
            sRet += "\n\tunable to chmod " + sTmpDir;
        }

        return(sRet);
        }

    public String TopActivity()
        {
        String sRet = "";
        ActivityManager aMgr = (ActivityManager) contextWrapper.getSystemService(Activity.ACTIVITY_SERVICE);
        List< ActivityManager.RunningTaskInfo > taskInfo = null;
        ComponentName componentInfo = null;
        if (aMgr != null)
            {
            taskInfo = aMgr.getRunningTasks(1);
            }
        if (taskInfo != null)
            {
            // topActivity: "The activity component at the top of the history stack of the task.
            // This is what the user is currently doing."
            componentInfo = taskInfo.get(0).topActivity;
            }
        if (componentInfo != null)
            {
            sRet = componentInfo.getPackageName();
            }
        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" +
            "execcwd <dir> [env pairs] [cmdline] - start program from specified directory\n" +
            "execsu [env pairs] [cmdline]    - start program as privileged user\n" +
            "execcwdsu <dir> [env pairs] [cmdline] - start program from specified directory as privileged user\n" +
            "execext [su] [cwd=<dir>] [t=<timeout>] [env pairs] [cmdline] - start program with extended options\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" +
            "        [uptimemillis]          - uptime for device in milliseconds\n" +
            "        [sutuptimemillis]       - uptime for SUT in milliseconds\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" +
            "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" +
            "chmod directory|file            - change permissions of directory and contents (or file) to 777\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 and reboot\n" +
            "uninstall packagename           - uninstall the referenced package without a reboot\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" +
            "activity                        - print package name of top (foreground) activity\n" +
            "quit                            - disconnect SUTAgent\n" +
            "exit                            - close SUTAgent\n" +
            "ver                             - SUTAgent version\n" +
            "help                            - you're reading it";
        return (sRet);
        }
}
