/* 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.watcher;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
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.PackageManager.NameNotFoundException;
import android.os.BatteryManager;
import android.os.Debug;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
import android.widget.Toast;
import android.os.Environment;

public class WatcherService extends Service
{
    private final String prgVersion = "Watcher Version 1.16";
    private final String LOGTAG = "Watcher";

    String sErrorPrefix = "##Installer Error## ";
    String currentDir = "/";
    String sPingTarget = "";
    long lDelay = 60000;
    long lPeriod = 300000;
    int nMaxStrikes = 0; // maximum number of tries before we consider network unreachable (0 means don't check)
    boolean bStartSUTAgent = true;
    boolean bStartedTimer = false;

    Process pProc;
    Context myContext = null;
    Timer myTimer = null;
    private PowerManager.WakeLock pwl = null;
    public static final int NOTIFICATION_ID = 1964;
    boolean bInstalling = false;

    @SuppressWarnings("unchecked")
    private static final Class<?>[] mSetForegroundSignature = new Class[] {
        boolean.class};
    @SuppressWarnings("unchecked")
    private static final Class<?>[] mStartForegroundSignature = new Class[] {
        int.class, Notification.class};
    @SuppressWarnings("unchecked")
    private static final Class<?>[] mStopForegroundSignature = new Class[] {
        boolean.class};

    private NotificationManager mNM;
    private Method mSetForeground;
    private Method mStartForeground;
    private Method mStopForeground;
    private Object[] mSetForegroundArgs = new Object[1];
    private Object[] mStartForegroundArgs = new Object[2];
    private Object[] mStopForegroundArgs = new Object[1];


    private IWatcherService.Stub stub = new IWatcherService.Stub() {
        public int UpdateApplication(String sAppName, String sFileName, String sOutFile, int bReboot) throws RemoteException
            {
            return UpdtApp(sAppName, sFileName, sOutFile, bReboot);
            }
    };

    @Override
    public IBinder onBind(Intent arg0) {
        return stub;
    }

    @Override
    public void onCreate()
        {
        super.onCreate();

        Log.i(LOGTAG, prgVersion);

        myContext = this;

        getKeyGuardAndWakeLock();

        File dir = getFilesDir();
        File iniFile = new File(dir, "watcher.ini");
        String sIniFile = iniFile.getAbsolutePath();
        String sHold = "";

        Log.i(LOGTAG, String.format("Loading settings from %s", sIniFile));
        this.sPingTarget = GetIniData("watcher", "PingTarget", sIniFile, "www.mozilla.org");
        sHold = GetIniData("watcher", "delay", sIniFile, "60000");
        this.lDelay = Long.parseLong(sHold.trim());
        sHold = GetIniData("watcher", "period", sIniFile,"300000");
        this.lPeriod = Long.parseLong(sHold.trim());
        sHold = GetIniData("watcher", "strikes", sIniFile,"0");
        this.nMaxStrikes = Integer.parseInt(sHold.trim());
        Log.i(LOGTAG, String.format("Pinging %s after a delay of %s sec, period of %s sec, max number of failed attempts is %s (if max # of failed attempts is 0, then no checking)",
             this.sPingTarget, this.lDelay / 1000.0, this.lPeriod / 1000.0, nMaxStrikes));

        sHold = GetIniData("watcher", "StartSUTAgent", sIniFile, "true");
        this.bStartSUTAgent = Boolean.parseBoolean(sHold.trim());

        sHold = GetIniData("watcher", "stayon", sIniFile,"0");
        int nStayOn = Integer.parseInt(sHold.trim());
        
        try {
            if (nStayOn != 0) {
                if (!Settings.System.putInt(getContentResolver(), Settings.System.STAY_ON_WHILE_PLUGGED_IN, BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB)) {
                    doToast("Screen couldn't be set to Always On [stay on while plugged in]");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            String sExcept = e.getMessage();
            doToast("Screen couldn't be set to Always On [exception " + sExcept + "]");
        }

        doToast("WatcherService created");
        }

    public String GetIniData(String sSection, String sKey, String sFile, String sDefault)
        {
        String sRet = sDefault;
        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);
        }

    private void handleCommand(Intent intent)
        {
        // Note: intent can be null "if the service is being restarted after its process
        // has gone away". In this case, we will consider that to be equivalent to a start
        // http://developer.android.com/reference/android/app/Service.html#onStartCommand%28android.content.Intent,%20int,%20int%29

        String sCmd = "start";
        if (intent != null)
            {
            sCmd = intent.getStringExtra("command");
            }

        if (sCmd != null)
            {
            if (sCmd.equalsIgnoreCase("updt"))
                {
                String sPkgName = intent.getStringExtra("pkgName");
                String sPkgFile = intent.getStringExtra("pkgFile");
                String sOutFile = intent.getStringExtra("outFile");
                boolean bReboot = intent.getBooleanExtra("reboot", true);
                int nReboot = bReboot ? 1 : 0;
                Log.i(LOGTAG, "WatcherService updating " + sPkgName + " using file " + sPkgFile);

                UpdateApplication worker = new UpdateApplication(sPkgName, sPkgFile, sOutFile, nReboot);
                }
            else if (sCmd.equalsIgnoreCase("start"))
                {
                if (!this.bStartedTimer) 
                    {
                    doToast("WatcherService started");
                    myTimer = new Timer();
                    Date startSchedule = new Date(System.currentTimeMillis() + lDelay);
                    myTimer.schedule(new MyTime(), startSchedule, lPeriod);
                    this.bStartedTimer = true;
                    }
                }
            else
                {
                doToast("WatcherService unknown command");
                }
            }
        else
            doToast("WatcherService created");
        }

    public void writeVersion() {
        PrintWriter pw = null;
        String appPath = getApplicationContext().getFilesDir().getAbsolutePath();
        String versionPath = appPath + "/version.txt";
        Log.i(LOGTAG, "writing version string to: " + versionPath);
        try {
            pw = new PrintWriter(new FileWriter(versionPath, true));
            pw.println(this.prgVersion);
        } catch (IOException ioe) {
            Log.e(LOGTAG, "Exception writing version: " + this.prgVersion + " to file: " + versionPath);
        } finally {
            if (pw != null) {
                pw.close();
            }
        }
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Log.i(LOGTAG, "onStart");
        writeVersion();
        handleCommand(intent);
        return;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(LOGTAG, "onStartCommand");
        writeVersion();
        handleCommand(intent);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        doToast("WatcherService destroyed");
        if (pwl != null)
            pwl.release();
        stopForegroundCompat(R.string.foreground_service_started);
    }

    @Override
    public void onLowMemory() {
        Log.e(LOGTAG, "onLowMemory");
        System.gc();
    }

    @Override
    public void onTrimMemory(int level) {
        Log.e(LOGTAG, "onTrimMemory: "+level);
        System.gc();
    }

    protected void getKeyGuardAndWakeLock()
        {
        // Fire off a thread to do some work that we shouldn't do directly in the UI thread
        Thread t = new Thread() {
            public void run() {
                Log.i(LOGTAG, "worker thread started");
                // Keep phone from locking or remove lock on screen
                KeyguardManager km = (KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE);
                if (km != null)
                    {
                    KeyguardManager.KeyguardLock kl = km.newKeyguardLock("watcher");
                    if (kl != null)
                        {
                        kl.disableKeyguard();
                        Log.i(LOGTAG, "keyguard disabled");
                        }
                    }

                // No sleeping on the job
                PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
                if (pm != null)
                    {
                    pwl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "watcher");
                    if (pwl != null)
                        {
                        pwl.acquire();
                        Log.i(LOGTAG, "wake lock acquired");
                        }
                    }

                Class<?> serviceClass = null;
                try {
                    serviceClass = Class.forName("com.mozilla.watcher.WatcherService");
                    }
                catch (Exception e)
                    {
                    Log.e(LOGTAG, "unable to find service class: "+e.toString());
                    return;
                    }
                mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
                try {
                    mStartForeground = serviceClass.getMethod("startForeground", mStartForegroundSignature);
                    mStopForeground = serviceClass.getMethod("stopForeground", mStopForegroundSignature);
                    }
                catch (NoSuchMethodException e)
                    {
                    // Might be running on an older platform.
                    mStartForeground = mStopForeground = null;
                    Log.w(LOGTAG, "unable to find start/stopForeground method(s) -- older platform?");
                    }
                try {
                    mSetForeground = serviceClass.getMethod("setForeground", mSetForegroundSignature);
                    }
                catch (NoSuchMethodException e) 
                    {
                    mSetForeground = null;
                    Log.e(LOGTAG, "unable to find setForeground method!");
                    }
                Notification notification = new Notification();
                startForegroundCompat(R.string.foreground_service_started, notification);
                }
            };
        t.start();
        }

    /**
     * This is a wrapper around the new startForeground method, using the older
     * APIs if it is not available.
     */
    void startForegroundCompat(int id, Notification notification) {
        // If we have the new startForeground API, then use it.
        if (mStartForeground != null) {
            mStartForegroundArgs[0] = Integer.valueOf(id);
            mStartForegroundArgs[1] = notification;
            try {
                mStartForeground.invoke(this, mStartForegroundArgs);
                Log.i(LOGTAG, "startForeground invoked");
            } catch (InvocationTargetException e) {
                // Should not happen.
                Log.e(LOGTAG, "Unable to invoke startForeground", e);
            } catch (IllegalAccessException e) {
                // Should not happen.
                Log.e(LOGTAG, "Unable to invoke startForeground", e);
            }
            return;
        }

        // Fall back on the old API.
        if  (mSetForeground != null) {
            try {
                mSetForegroundArgs[0] = Boolean.TRUE;
                mSetForeground.invoke(this, mSetForegroundArgs);
                Log.i(LOGTAG, "setForeground(TRUE) invoked");
            } catch (IllegalArgumentException e) {
                Log.e(LOGTAG, "Unable to invoke setForeground", e);
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                Log.e(LOGTAG, "Unable to invoke setForeground", e);
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                Log.e(LOGTAG, "Unable to invoke setForeground", e);
                e.printStackTrace();
            }
        }
        mNM.notify(id, notification);
    }

    /**
     * This is a wrapper around the new stopForeground method, using the older
     * APIs if it is not available.
     */
    void stopForegroundCompat(int id) {
        // If we have the new stopForeground API, then use it.
        if (mStopForeground != null) {
            mStopForegroundArgs[0] = Boolean.TRUE;
            try {
                mStopForeground.invoke(this, mStopForegroundArgs);
                Log.i(LOGTAG, "stopForeground invoked");
            } catch (InvocationTargetException e) {
                // Should not happen.
                Log.e(LOGTAG, "Unable to invoke stopForeground", e);
            } catch (IllegalAccessException e) {
                // Should not happen.
                Log.e(LOGTAG, "Unable to invoke stopForeground", e);
            }
            return;
        }

        // Fall back on the old API.  Note to cancel BEFORE changing the
        // foreground state, since we could be killed at that point.
        mNM.cancel(id);
        if  (mSetForeground != null) {
            try {
                mSetForegroundArgs[0] = Boolean.FALSE;
                mSetForeground.invoke(this, mSetForegroundArgs);
                Log.i(LOGTAG, "setForeground(FALSE) invoked");
            } catch (IllegalArgumentException e) {
                Log.e(LOGTAG, "Unable to invoke setForeground", e);
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                Log.e(LOGTAG, "Unable to invoke setForeground", e);
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                Log.e(LOGTAG, "Unable to invoke setForeground", e);
                e.printStackTrace();
            }
        }
    }

    public void doToast(String sMsg)
        {
        Log.i(LOGTAG, sMsg);
        Toast toast = Toast.makeText(this, sMsg, Toast.LENGTH_LONG);
        toast.setGravity(Gravity.TOP|Gravity.CENTER_HORIZONTAL, 0, 100);
        toast.show();
        }

    public void CheckMem() 
        {
        System.gc();
        long lFreeMemory = Runtime.getRuntime().freeMemory();
        long lTotMemory = Runtime.getRuntime().totalMemory();
        long lMaxMemory = Runtime.getRuntime().maxMemory();

        Log.i(LOGTAG, "Free: " + lFreeMemory + "Total: " + lTotMemory + "Max: " + lMaxMemory);
        }

    public int UpdtApp(String sPkgName, String sPkgFileName, String sOutFile, int bReboot)
        {
        int nRet = 1;
        int lcv = 0;
        String sRet = "";

        FileOutputStream f = null;

        try {
            Log.i(LOGTAG, "Step 1: Kill " + sPkgName + " if running");
            while (!IsProcessDead(sPkgName) && (lcv < 5)) {
                if (KillProcess(sPkgName, null).startsWith("Successfully"))
                    break;
                else
                    lcv++;
                Thread.sleep(2000);
            }

            CheckMem();

            if ((sOutFile != null) && (sOutFile.length() > 0)) {
                File outFile = new File(sOutFile);
                if (outFile.exists() && outFile.canWrite()) {
                    f = new FileOutputStream(outFile, true);
                } else {
                    Log.e(LOGTAG, "File not found or cannot write to " + sOutFile);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            Log.e(LOGTAG, "Couldn't open " + sOutFile + " " + e.getLocalizedMessage());
            e.printStackTrace();
        } catch (SecurityException e) {
            Log.e(LOGTAG, "Exception message " + e.getLocalizedMessage());
            e.printStackTrace();
        }

        if ((sPkgName != null) && (sPkgName.length() > 0))
            {
            Log.i(LOGTAG, "Step 2: Uninstall " + sPkgName);
            sRet = UnInstallApp(sPkgName, null);
            CheckMem();
            if ((sRet.length() > 0) && (f != null))
                {
                try {
                    f.write(sRet.getBytes());
                    f.flush();
                    }
                catch (IOException e)
                    {
                    e.printStackTrace();
                    }
                }
            }

        if ((sPkgFileName != null) && (sPkgFileName.length() > 0))
            {
            Log.i(LOGTAG, "Step 3: Install " + sPkgFileName);
            sRet = InstallApp(sPkgFileName, null);
            Log.i(LOGTAG, "" + sRet);
            CheckMem();
            if ((sRet.length() > 0) && (f != null))
                {
                try {
                    f.write(sRet.getBytes());
                    f.flush();
                    f.close();
                    }
                catch (IOException e)
                    {
                    e.printStackTrace();
                    }
                }
            }

        if (bReboot > 0)
            RunReboot(null);

        return(nRet);
        }

    public boolean GetProcessInfo(String sProcName)
        {
        boolean bRet = false;
        ActivityManager aMgr = (ActivityManager) getApplicationContext().getSystemService(Activity.ACTIVITY_SERVICE);
        List <ActivityManager.RunningAppProcessInfo> lProcesses = aMgr.getRunningAppProcesses();
        int    nProcs = 0;
        int lcv = 0;
        String strProcName = "";

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

        for (lcv = 0; lcv < nProcs; lcv++)
            {
            strProcName = lProcesses.get(lcv).processName;
            if (strProcName.contains(sProcName))
                {
                bRet = true;
                break;
                }
            }
        return (bRet);
        }

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

        theArgs[0] = "su";
        theArgs[1] = "-c";
        theArgs[2] = "reboot";
        Log.i(LOGTAG, "Running reboot!");

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

        return (sRet);
        }

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

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

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

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

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

                theArgs[2] += " " + nPID;

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

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

        if (nPID > 0)
            {
            sRet = "Successfully killed " + nPID + " " + strProcName + "\n";
            lProcesses = aMgr.getRunningAppProcesses();
            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 " + nPID + " " + strProcName + "\n";
                    break;
                    }
                }
            }

        return (sRet);
        }

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

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

    public boolean IsProcessDead(String sProcName)
        {
        boolean bRet = true;
        ActivityManager aMgr = (ActivityManager) getSystemService(Activity.ACTIVITY_SERVICE);
        List <ActivityManager.RunningAppProcessInfo> lProcesses = aMgr.getRunningAppProcesses(); //    .getProcessesInErrorState();
        int lcv = 0;

        if (lProcesses != null)
            {
            for (lcv = 0; lcv < lProcesses.size(); lcv++)
                {
                if (lProcesses.get(lcv).processName.contentEquals(sProcName))
                    {
                    bRet = false;
                    break;
                    }
                }
            }

        return (bRet);
        }

    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 UnInstallApp(String sApp, OutputStream out)
        {
        String sRet = "";
        try
            {
            pProc = Runtime.getRuntime().exec(this.getSuArgs("pm uninstall " + sApp + ";exit"));

            RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
            outThrd.start();
            outThrd.join(60000);
            int nRet = pProc.exitValue();
            sRet = "\nuninst complete [" + nRet + "]";
            }
        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 InstallApp(String sApp, OutputStream out)
        {
        String sRet = "";
        String sHold = "";
        try
            {
            pProc = Runtime.getRuntime().exec(this.getSuArgs("pm install -r " + sApp + " Cleanup;exit"));

            RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
            outThrd.start();
            outThrd.join(180000);
            int nRet = pProc.exitValue();
            sRet += "\ninstall complete [" + nRet + "]";
            sHold = outThrd.strOutput;
            sRet += "\nSuccess";
            }
        catch (IOException e)
            {
            sRet = e.getMessage();
            e.printStackTrace();
            }
        catch (InterruptedException e)
            {
            e.printStackTrace();
            }

        return (sRet);
        }

    private String SendPing(String sIPAddr)
        {
        Process pProc;
        String sRet = "";
        String [] theArgs = new String [4];
        boolean bStillRunning = true;
        int nBytesOut = 0;
        int nBytesErr = 0;
        int nBytesRead = 0;
        byte[] buffer = new byte[1024];

        theArgs[0] = "ping";
        theArgs[1] = "-c";
        theArgs[2] = "3";
        theArgs[3] = sIPAddr;
        Log.i(LOGTAG, "Pinging " + sIPAddr);

        try
            {
            pProc = Runtime.getRuntime().exec(theArgs);
            InputStream sutOut = pProc.getInputStream();
            InputStream sutErr = pProc.getErrorStream();

            while (bStillRunning)
                {
                try
                    {
                    if ((nBytesOut = sutOut.available()) > 0)
                        {
                        if (nBytesOut > buffer.length)
                            {
                            buffer = null;
                            System.gc();
                            buffer = new byte[nBytesOut];
                            }
                        nBytesRead = sutOut.read(buffer, 0, nBytesOut);
                        if (nBytesRead == -1)
                            bStillRunning = false;
                        else
                            {
                            String sRep = new String(buffer,0,nBytesRead).replace("\n", "\r\n");
                            sRet += sRep;
                            sRep = null;
                            }
                        }

                    if ((nBytesErr = sutErr.available()) > 0)
                        {
                        if (nBytesErr > buffer.length)
                            {
                            buffer = null;
                            System.gc();
                            buffer = new byte[nBytesErr];
                            }
                        nBytesRead = sutErr.read(buffer, 0, nBytesErr);
                        if (nBytesRead == -1)
                            bStillRunning = false;
                        else
                            {
                            String sRep = new String(buffer,0,nBytesRead).replace("\n", "\r\n");
                            sRet += sRep;
                            sRep = null;
                            }
                        }

                    bStillRunning = (IsProcRunning(pProc) || (sutOut.available() > 0) || (sutErr.available() > 0));
                    }
                catch (IOException e)
                    {
                    e.printStackTrace();
                    }

                if ((bStillRunning == true) && (nBytesErr == 0) && (nBytesOut == 0))
                    {
                    try {
                        Thread.sleep(2000);
                        }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                        }
                    }
                }

            pProc.destroy();
            pProc = null;
            }
        catch (IOException e)
            {
            sRet = e.getMessage();
            e.printStackTrace();
            }

        Log.i(LOGTAG, String.format("Ping result was: '%s'", sRet.trim()));
        return (sRet);
        }

    private boolean IsProcRunning(Process pProc)
        {
        boolean bRet = false;
        @SuppressWarnings("unused")
        int nExitCode = 0;

        try
            {
            nExitCode = pProc.exitValue();
            }
        catch (IllegalThreadStateException z)
            {
            bRet = true;
            }
        catch (Exception e)
            {
            e.printStackTrace();
            }

        return(bRet);
        }

    private class UpdateApplication implements Runnable {
        Thread    runner;
        String    msPkgName = "";
        String    msPkgFileName = "";
        String    msOutFile = "";
        int       mbReboot = 0;

        public UpdateApplication(String sPkgName, String sPkgFileName, String sOutFile, int bReboot) {
            runner = new Thread(this);
            msPkgName = sPkgName;
            msPkgFileName = sPkgFileName;
            msOutFile = sOutFile;
            mbReboot = bReboot;
            runner.start();
        }

        public void run() {
            bInstalling = true;
            UpdtApp(msPkgName, msPkgFileName, msOutFile, mbReboot);
            bInstalling = false;
        }
    }

    private class MyTime extends TimerTask
        {
        int    nStrikes = 0;
        final int PERIODS_TO_WAIT_FOR_SDCARD = 3;
        int    nPeriodsWaited = 0;

        public MyTime()
            {
            }

        @Override
        public void run()
            {
            if (bInstalling)
                return;

            // See if the network is up, if not reboot after a configurable
            // number of tries
            if (nMaxStrikes > 0)
                {
                    String sRet = SendPing(sPingTarget);
                    if (!sRet.contains("3 received"))
                        {
                            Log.i(LOGTAG, String.format("Failed ping attempt (remaining: %s)!",
                                 nMaxStrikes - nStrikes));
                            if (++nStrikes >= nMaxStrikes)
                                {
                                    Log.e(LOGTAG, String.format("Number of failed ping attempts to %s (%s) exceeded maximum (%s), running reboot!", sPingTarget, nStrikes, nMaxStrikes));
                                    RunReboot(null);
                                }
                        }
                    else
                        {
                            nStrikes = 0;
                        }
                }

            String sProgramName = "com.mozilla.SUTAgentAndroid";

            // Ensure the sdcard is mounted before we even attempt to start the agent
            // We will wait for the sdcard to mount for PERIODS_TO_WAIT_FOR_SDCARD
            // after which time we go ahead and attempt to start the agent.
            if (nPeriodsWaited++ < PERIODS_TO_WAIT_FOR_SDCARD) {
                String state = Environment.getExternalStorageState();
                if (Environment.MEDIA_MOUNTED.compareTo(state) != 0) {
                    Log.i(LOGTAG, "SDcard not mounted, waiting another turn");
                    return;
                } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
                    Log.e(LOGTAG, "SDcard mounted read only not starting agent now, try again in 60s");
                    return;
                }
            }

            boolean isProc = GetProcessInfo(sProgramName);
            if (bStartSUTAgent && !isProc)
                {
                Log.i(LOGTAG, "Starting SUTAgent from watcher code");
                Intent agentIntent = new Intent();
                agentIntent.setPackage(sProgramName);
                agentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                agentIntent.setAction(Intent.ACTION_MAIN);
                try {
                    PackageManager pm = myContext.getPackageManager();
                    PackageInfo pi = pm.getPackageInfo(sProgramName, PackageManager.GET_ACTIVITIES | PackageManager.GET_INTENT_FILTERS);
                    ActivityInfo [] ai = pi.activities;
                    for (int i = 0; i < ai.length; i++)
                        {
                        ActivityInfo a = ai[i];
                        if (a.name.length() > 0)
                            {
                            agentIntent.setClassName(a.packageName, a.name);
                            break;
                            }
                        }
                    }
                catch (NameNotFoundException e)
                    {
                    e.printStackTrace();
                    }
                try
                    {
                    myContext.startActivity(agentIntent);
                    }
                catch(ActivityNotFoundException anf)
                    {
                    anf.printStackTrace();
                    }
                }
            }
        }
}
