org.computeforcancer.android.client.Monitor.java Source code

Java tutorial

Introduction

Here is the source code for org.computeforcancer.android.client.Monitor.java

Source

/*******************************************************************************
 * This file is part of BOINC.
 * http://boinc.berkeley.edu
 * Copyright (C) 2012 University of California
 * 
 * BOINC is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 * 
 * BOINC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package org.computeforcancer.android.client;

import org.computeforcancer.android.Message;
import org.computeforcancer.android.utils.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

import android.app.NotificationManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Build;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.RemoteException;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.TextView;

import org.computeforcancer.android.R;
import org.computeforcancer.android.mutex.BoincMutex;
import org.computeforcancer.android.AccountIn;
import org.computeforcancer.android.AccountOut;
import org.computeforcancer.android.rpc.AcctMgrRPCReply;
import org.computeforcancer.android.rpc.CcState;
import org.computeforcancer.android.rpc.CcStatus;
import org.computeforcancer.android.GlobalPreferences;
import org.computeforcancer.android.HostInfo;
import org.computeforcancer.android.ImageWrapper;
import org.computeforcancer.android.Notice;
import org.computeforcancer.android.Project;
import org.computeforcancer.android.ProjectConfig;
import org.computeforcancer.android.ProjectInfo;
import org.computeforcancer.android.Result;
import org.computeforcancer.android.Transfer;
import org.computeforcancer.android.AcctMgrInfo;

/**
 * Main Service of BOINC on Android
 * - manages life-cycle of the BOINC Client.
 * - frequently polls the latest status of the client (e.g. running tasks, attached projects etc)
 * - reports device status (e.g. battery level, connected to charger etc) to the client
 * - holds singleton of client status data model and applications persistent preferences
 */
public class Monitor extends Service {

    public static String DONATION_TIME_MESSAGE = "org.computeforcancer.android.donation.message";
    public static String DONATION_LAST_SESSION = "org.computeforcancer.android.donation.last";
    public static String DONATION_OVERALL = "org.computeforcancer.android.donation.overall";
    public static String DONATION_EMAIL = "org.computeforcancer.android.donation.email";

    private static BoincMutex mutex = new BoincMutex(); // holds the BOINC mutex, only compute if acquired
    private static ClientStatus clientStatus; //holds the status of the client as determined by the Monitor
    private static AppPreferences appPrefs; //hold the status of the app, controlled by AppPreferences
    private static DeviceStatus deviceStatus; // holds the status of the device, i.e. status information that can only be obtained trough Java APIs

    public ClientInterfaceImplementation clientInterface = new ClientInterfaceImplementation(); //provides functions for interaction with client via rpc

    // XML defined variables, populated in onCreate
    private String fileNameClient;
    private String fileNameCLI;
    private String fileNameCABundle;
    private String fileNameClientConfig;
    private String fileNameGuiAuthentication;
    private String fileNameAllProjectsList;
    private String boincWorkingDir;
    private Integer clientStatusInterval;
    private Integer deviceStatusIntervalScreenOff;
    private String clientSocketAddress;

    private Timer updateTimer = new Timer(true); // schedules frequent client status update
    private TimerTask statusUpdateTask = new StatusUpdateTimerTask();
    private boolean updateBroadcastEnabled = false;
    private Integer screenOffStatusOmitCounter = 0;

    // screen on/off updated by screenOnOffBroadcastReceiver
    private boolean screenOn = false;

    private boolean forceReinstall = false; // for debugging purposes //TODO

    @Override
    public IBinder onBind(Intent intent) {
        if (Logging.DEBUG)
            Log.d(Logging.TAG, "Monitor onBind");
        return mBinder;
    }

    @Override
    public void onCreate() {

        Log.d(Logging.TAG, "Monitor onCreate()");

        // populate attributes with XML resource values
        boincWorkingDir = getString(R.string.client_path);
        fileNameClient = getString(R.string.client_name);
        fileNameCLI = getString(R.string.client_cli);
        fileNameCABundle = getString(R.string.client_cabundle);
        fileNameClientConfig = getString(R.string.client_config);
        fileNameGuiAuthentication = getString(R.string.auth_file_name);
        fileNameAllProjectsList = getString(R.string.all_projects_list);
        clientStatusInterval = getResources().getInteger(R.integer.status_update_interval_ms);
        deviceStatusIntervalScreenOff = getResources()
                .getInteger(R.integer.device_status_update_screen_off_every_X_loop);
        clientSocketAddress = getString(R.string.client_socket_address);

        // initialize singleton helper classes and provide application context
        clientStatus = new ClientStatus(this);
        getAppPrefs().readPrefs(this);
        deviceStatus = new DeviceStatus(this, getAppPrefs());
        if (Logging.ERROR)
            Log.d(Logging.TAG, "Monitor onCreate(): singletons initialized");

        // set current screen on/off status
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        screenOn = pm.isScreenOn();

        // initialize DeviceStatus wrapper
        deviceStatus = new DeviceStatus(getApplicationContext(), getAppPrefs());

        // register screen on/off receiver
        IntentFilter onFilter = new IntentFilter(Intent.ACTION_SCREEN_ON);
        IntentFilter offFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
        registerReceiver(screenOnOffReceiver, onFilter);
        registerReceiver(screenOnOffReceiver, offFilter);
    }

    @Override
    public void onDestroy() {
        if (Logging.ERROR)
            Log.d(Logging.TAG, "Monitor onDestroy()");

        updateBroadcastEnabled = false; // prevent broadcast from currently running update task
        updateTimer.cancel(); // cancel task

        // there might be still other AsyncTasks executing RPCs
        // close sockets in a synchronized way
        clientInterface.close();

        try {
            // remove screen on/off receiver
            unregisterReceiver(screenOnOffReceiver);
        } catch (Exception ex) {
        }

        updateBroadcastEnabled = false; // prevent broadcast from currently running update task
        updateTimer.cancel(); // cancel task

        mutex.release(); // release BOINC mutex

        // release locks, if held.
        try {
            clientStatus.setWakeLock(false);
            clientStatus.setWifiLock(false);
        } catch (Exception ex) {
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //this gets called after startService(intent) (either by BootReceiver or SplashActivity, depending on the user's autostart configuration)
        if (Logging.ERROR)
            Log.d(Logging.TAG, "Monitor onStartCommand()");

        // try to acquire BOINC mutex
        // run here in order to recover, if mutex holding app gets closed.
        if (!updateBroadcastEnabled && mutex.acquire()) {
            updateBroadcastEnabled = true;
            // register and start update task
            // using .scheduleAtFixedRate() can cause a series of bunched-up runs
            // when previous executions are delayed (e.g. during clientSetup() )
            updateTimer.schedule(statusUpdateTask, 0, clientStatusInterval);
        }
        if (!mutex.acquired)
            if (Logging.ERROR)
                Log.e(Logging.TAG, "Monitor.onStartCommand: mutex acquisition failed, do not start BOINC.");

        // execute action if one is explicitly requested (e.g. from notification)
        if (intent != null) {
            int actionCode = intent.getIntExtra("action", -1);
            if (Logging.DEBUG)
                Log.d(Logging.TAG, "Monitor.onStartCommand() with action code: " + actionCode);
            switch (actionCode) {
            case 1: // suspend
                new SetClientRunModeAsync().execute(BOINCDefs.RUN_MODE_NEVER);
                break;
            case 2: // resume
                new SetClientRunModeAsync().execute(BOINCDefs.RUN_MODE_AUTO);
                break;
            }
        }

        /*
         * START_STICKY causes service to stay in memory until stopSelf() is called, even if all
         * Activities get destroyed by the system. Important for GUI keep-alive
         * For detailed service documentation see
         * http://android-developers.blogspot.com.au/2010/02/service-api-changes-starting-with.html
         */
        return START_STICKY;
    }
    // --end-- attributes and methods related to Android Service life-cycle

    // singleton getter
    /**
     * Retrieve singleton of ClientStatus.
     * @return ClientStatus, represents the data model of the BOINC client's status
     * @throws Exception if client status has not been initialized
     */
    public static ClientStatus getClientStatus() throws Exception { //singleton pattern
        if (clientStatus == null) {
            // client status needs application context, but context might not be available
            // in static code. functions have to deal with Exception!
            if (Logging.WARNING)
                Log.w(Logging.TAG, "getClientStatus: clientStatus not yet initialized");
            throw new Exception("clientStatus not initialized");
        }
        return clientStatus;
    }

    /**
     * Retrieve singleton of AppPreferences.
     * @return AppPreferences, interface to Android applications persistent key-value store
     */
    public static AppPreferences getAppPrefs() { //singleton pattern
        if (appPrefs == null) {
            appPrefs = new AppPreferences();
        }
        return appPrefs;
    }

    /**
     * Retrieve singleton of DeviceStatus.
     * @return DeviceStatus, represents data model of device information reported to the client
     * @throws Exception if deviceStatus hast not been initialized
     */
    public static DeviceStatus getDeviceStatus() throws Exception {//singleton pattern
        if (deviceStatus == null) {
            // device status needs application context, but context might not be available
            // in static code. functions have to deal with Exception!
            if (Logging.WARNING)
                Log.w(Logging.TAG, "getDeviceStatus: deviceStatus not yet initialized");
            throw new Exception("deviceStatus not initialized");
        }
        return deviceStatus;
    }
    // --end-- singleton getter

    // public methods for Activities
    /**
     * Indicates whether service was able to obtain BOINC mutex.
     * If not, BOINC has not started and all other calls will fail.
     * @return BOINC mutex acquisition successful
     */
    public boolean boincMutexAcquired() {
        return mutex.acquired;
    }

    /**
     * Force refresh of client status data model, will fire Broadcast upon success.
     */
    public void forceRefresh() {
        if (!mutex.acquired)
            return; // do not try to update if client is not running
        if (Logging.DEBUG)
            Log.d(Logging.TAG, "forceRefresh()");
        try {
            updateTimer.schedule(new StatusUpdateTimerTask(), 0);
        } catch (Exception e) {
        } // throws IllegalStateException if called after timer got cancelled, i.e. after manual shutdown
    }

    /**
     * Determines BOINC platform name corresponding to device's cpu architecture (ARM, x86 or MIPS).
     * Defaults to ARM
     * @return ID of BOINC platform name string in resources
     */
    public int getBoincPlatform() {
        int platformId = 0;
        String arch = System.getProperty("os.arch");
        String normalizedArch = arch.toUpperCase(Locale.US);
        if (normalizedArch.contains("AARCH64"))
            platformId = R.string.boinc_platform_name_arm64;
        else if (normalizedArch.contains("ARM64"))
            platformId = R.string.boinc_platform_name_arm64;
        else if (normalizedArch.contains("MIPS64"))
            platformId = R.string.boinc_platform_name_mips64;
        else if (normalizedArch.contains("X86_64"))
            platformId = R.string.boinc_platform_name_x86_64;
        else if (normalizedArch.contains("ARM"))
            platformId = R.string.boinc_platform_name_arm;
        else if (normalizedArch.contains("MIPS"))
            platformId = R.string.boinc_platform_name_mips;
        else if (normalizedArch.contains("86"))
            platformId = R.string.boinc_platform_name_x86;
        else {
            if (Logging.ERROR)
                Log.w(Logging.TAG, "could not map os.arch (" + arch + ") to platform, default to arm.");
            platformId = R.string.boinc_platform_name_arm;
        }

        if (Logging.ERROR)
            Log.d(Logging.TAG, "BOINC platform: " + getString(platformId) + " for os.arch: " + arch);
        return platformId;
    }

    /**
     * Determines BOINC alt platform name corresponding to device's cpu architecture (ARM, x86 or MIPS).
     * @return  BOINC platform name string in resources
     */
    public String getBoincAltPlatform() {
        String platformName = "";
        String arch = System.getProperty("os.arch");
        String normalizedArch = arch.toUpperCase(Locale.US);
        if (normalizedArch.contains("AARCH64"))
            platformName = getString(R.string.boinc_platform_name_arm);
        else if (normalizedArch.contains("ARM64"))
            platformName = getString(R.string.boinc_platform_name_arm);
        else if (normalizedArch.contains("MIPS64"))
            platformName = getString(R.string.boinc_platform_name_mips);
        else if (normalizedArch.contains("X86_64"))
            platformName = getString(R.string.boinc_platform_name_x86);

        if (Logging.ERROR)
            Log.d(Logging.TAG, "BOINC Alt platform: " + platformName + " for os.arch: " + arch);
        return platformName;
    }

    /**
     * Returns path to file in BOINC's working directory that contains GUI authentication key
     * @return absolute path to file holding GUI authentication key
     */
    public String getAuthFilePath() {
        return boincWorkingDir + fileNameGuiAuthentication;
    }
    // --end-- public methods for Activities

    // multi-threaded frequent information polling
    /**
     * Task to frequently and asynchronously poll the client's status. Executed in different thread.
     */
    private final class StatusUpdateTimerTask extends TimerTask {
        @Override
        public void run() {
            updateStatus();
        }
    }

    /**
     * Reports current device status to client and reads current client status.
     * Updates ClientStatus and fires Broadcast.
     * Called frequently to poll current status.
     */
    private void updateStatus() {
        // check whether RPC client connection is alive
        if (!clientInterface.connectionAlive()) {
            if (clientSetup()) { // start setup routine
                // interact with client only if connection established successfully
                reportDeviceStatus();
                readClientStatus(true); // read initial data
            }
        }

        if (!screenOn && screenOffStatusOmitCounter < deviceStatusIntervalScreenOff)
            screenOffStatusOmitCounter++; // omit status reporting according to configuration
        else {
            // screen is on, or omit counter reached limit
            if (clientInterface.connectionAlive()) {
                reportDeviceStatus();
                readClientStatus(false); // readClientStatus is also required when screen is off, otherwise no wakeLock acquisition.
            }
        }
    }

    private long prevTime, currTime, temp;
    private volatile long startSessionTime;

    /**
     * Reads client status via RPCs
     * Optimized to retrieve only subset of information (required to determine wakelock state) if screen is turned off
     * @param forceCompleteUpdate forces update of entire status information, regardless of screen status
     */
    private synchronized void readClientStatus(Boolean forceCompleteUpdate) {
        try {
            CcStatus status; // read independently of screen status

            // complete status read, depending on screen status
            // screen off: only read computing status to adjust wakelock, do not send broadcast
            // screen on: read complete status, set ClientStatus, send broadcast
            // forceCompleteUpdate: read complete status, independently of screen setting
            if (screenOn || forceCompleteUpdate) {
                // complete status read, with broadcast
                if (Logging.VERBOSE)
                    Log.d(Logging.TAG, "readClientStatus(): screen on, get complete status");
                status = clientInterface.getCcStatus();
                CcState state = clientInterface.getState();
                ArrayList<Transfer> transfers = clientInterface.getFileTransfers();
                AcctMgrInfo acctMgrInfo = clientInterface.getAcctMgrInfo();
                ArrayList<Notice> newNotices = clientInterface
                        .getNotices(Monitor.getClientStatus().getMostRecentNoticeSeqNo());

                if ((status != null) && (state != null) && (state.results != null) && (state.projects != null)
                        && (transfers != null) && (state.host_info != null) && (acctMgrInfo != null)) {
                    Monitor.getClientStatus().setClientStatus(status, state.results, state.projects, transfers,
                            state.host_info, acctMgrInfo, newNotices);
                } else {
                    String nullValues = "";
                    try {
                        if (state == null)
                            nullValues += "state,";
                        if (state.results == null)
                            nullValues += "state.results,";
                        if (state.projects == null)
                            nullValues += "state.projects,";
                        if (transfers == null)
                            nullValues += "transfers,";
                        if (state.host_info == null)
                            nullValues += "state.host_info,";
                        if (acctMgrInfo == null)
                            nullValues += "acctMgrInfo,";
                    } catch (NullPointerException e) {
                    }
                    ;
                    if (Logging.ERROR)
                        Log.e(Logging.TAG, "readClientStatus(): connection problem, null: " + nullValues);
                }

                // update notices notification
                //NoticeNotification.getInstance(getApplicationContext()).update(Monitor.getClientStatus().getRssNotices(), Monitor.getAppPrefs().getShowNotificationForNotices());

                // check whether monitor is still intended to update, if not, skip broadcast and exit...
                if (updateBroadcastEnabled) {
                    Intent clientStatus = new Intent();
                    clientStatus.setAction("org.computeforcancer.android.clientstatus");
                    getApplicationContext().sendBroadcast(clientStatus);
                }
            } else {
                // read only ccStatus to adjust wakelocks and service state independently of screen status
                status = clientInterface.getCcStatus();
            }

            // independent of screen on off:
            // wake locks and foreground enabled when Client is not suspended, therefore also during
            // idle.
            // treat cpu throttling as if it was computing.
            Boolean computing = (status.task_suspend_reason == BOINCDefs.SUSPEND_NOT_SUSPENDED)
                    || (status.task_suspend_reason == BOINCDefs.SUSPEND_REASON_CPU_THROTTLE);
            if (Logging.VERBOSE)
                Log.d(Logging.TAG, "readClientStatus(): computation enabled: " + computing);
            Monitor.getClientStatus().setWifiLock(computing);
            Monitor.getClientStatus().setWakeLock(computing);
            SharedPreferences mSharedPreferences = getApplicationContext().getSharedPreferences(
                    "org.computeforcancer.android", Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
            //Log.d("TEST", "deviceStatus.getStatus().user_active " + deviceStatus.getStatus().user_active);
            if (computing && !deviceStatus.getStatus().user_active) {
                //Log.d("TEST", "computing");
                currTime = System.currentTimeMillis();
                if (currTime - prevTime < 14000) {
                    temp = mSharedPreferences.getLong(
                            SharedPrefs.DONATION_TIME + mSharedPreferences.getString(SharedPrefs.CURRENT_EMAIL, ""),
                            0);
                    ;
                    //Log.d("TEST", "1get DONATION_TIME " + temp);
                    //Log.d("TEST", "1put DONATION_TIME " + (temp +
                    //      currTime - prevTime));
                    mSharedPreferences.edit()
                            .putLong(
                                    SharedPrefs.DONATION_TIME
                                            + mSharedPreferences.getString(SharedPrefs.CURRENT_EMAIL, ""),
                                    temp + currTime - prevTime)
                            .commit();
                }
                prevTime = currTime;
                //Log.d("TEST", "1get START_SESSION_TIME " + startSessionTime);
                if (startSessionTime == 0) {
                    //Log.d("TEST", "1put START_SESSION_TIME " + currTime);
                    startSessionTime = currTime;
                }
            } else {
                //Log.d("TEST", "2get START_SESSION_TIME " + startSessionTime);
                if (startSessionTime != 0) {
                    currTime = System.currentTimeMillis();
                    mSharedPreferences.edit()
                            .putLong(
                                    SharedPrefs.LAST_SESSION_TIME
                                            + mSharedPreferences.getString(SharedPrefs.CURRENT_EMAIL, ""),
                                    currTime - startSessionTime)
                            .commit();

                    //Log.d("TEST", "2get DONATION_TIME " + mSharedPreferences.getLong(SharedPrefs.DONATION_TIME + mSharedPreferences.getString(SharedPrefs.CURRENT_EMAIL, ""), 0));

                    temp = mSharedPreferences.getLong(
                            SharedPrefs.DONATION_TIME + mSharedPreferences.getString(SharedPrefs.CURRENT_EMAIL, ""),
                            0);
                    ;
                    mSharedPreferences.edit()
                            .putLong(
                                    SharedPrefs.DONATION_TIME
                                            + mSharedPreferences.getString(SharedPrefs.CURRENT_EMAIL, ""),
                                    temp + currTime - prevTime)
                            .commit();
                    prevTime = currTime;

                    sendDonationTimeMessage(
                            mSharedPreferences.getLong(SharedPrefs.LAST_SESSION_TIME
                                    + mSharedPreferences.getString(SharedPrefs.CURRENT_EMAIL, ""), 0),
                            mSharedPreferences.getLong(SharedPrefs.DONATION_TIME
                                    + mSharedPreferences.getString(SharedPrefs.CURRENT_EMAIL, ""), 0),
                            mSharedPreferences.getString(SharedPrefs.CURRENT_EMAIL, ""));
                    //Log.d("TEST", "2put START_SESSION_TIME " + 0);
                    startSessionTime = 0;
                    //Log.d("TEST", "System.currentTimeMillis() " + System.currentTimeMillis());
                    //Log.d("TEST", "System.getLastNotification() " + mSharedPreferences.getLong(SharedPrefs.LAST_NOTIFICATION, 0));
                    //Log.d("TEST", "System.getNotificationDelay() " + mSharedPreferences.getLong(SharedPrefs.NOTIFICATION_DELAY, Long.MAX_VALUE));
                    if (currTime - mSharedPreferences.getLong(SharedPrefs.LAST_NOTIFICATION, 0) > mSharedPreferences
                            .getLong(SharedPrefs.NOTIFICATION_DELAY, Long.MAX_VALUE)) {
                        mSharedPreferences.edit().putLong(SharedPrefs.LAST_NOTIFICATION, System.currentTimeMillis())
                                .commit();
                        //Log.d("TEST", "get DONATION_TIME " + mSharedPreferences.getLong(SharedPrefs.DONATION_TIME + mSharedPreferences.getString(SharedPrefs.CURRENT_EMAIL, ""), 0));
                        sendNotification(mSharedPreferences.getLong(SharedPrefs.DONATION_TIME
                                + mSharedPreferences.getString(SharedPrefs.CURRENT_EMAIL, ""), 0));
                    }
                }
            }
            //ClientNotification.getInstance(getApplicationContext()).update(Monitor.getClientStatus(), this, computing);

        } catch (Exception e) {
            if (Logging.ERROR)
                Log.e(Logging.TAG, "Monitor.readClientStatus excpetion: " + e.getMessage(), e);
        }
    }

    private void sendNotification(long overall) {
        //Log.d("TEST", "sendNotification overall " + overall);
        String hours = String.format("%02d", TimeUnit.MILLISECONDS.toHours(overall));
        String minutes = String.format("%02d", TimeUnit.MILLISECONDS.toMinutes(overall)
                - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(overall)));
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.notify_icon)
                .setContentTitle(getApplicationContext().getString(R.string.app_name))
                .setContentText(String.format(getString(R.string.notification), hours, minutes))
                .setStyle(new NotificationCompat.BigTextStyle()
                        .bigText(String.format(getString(R.string.notification), hours, minutes)));

        NotificationManager mNotificationManager = (NotificationManager) getSystemService(
                Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(1, mBuilder.build());
    }

    public void sendDonationTimeMessage(long lastSession, long overall, String email) {
        //Log.d("TEST", "sendDonationTimeMessage overall " + overall + "; lastSession" + lastSession);
        Intent intent = new Intent(DONATION_TIME_MESSAGE);
        intent.putExtra(DONATION_LAST_SESSION, lastSession);
        intent.putExtra(DONATION_EMAIL, email);
        intent.putExtra(DONATION_OVERALL, overall);
        getApplicationContext().sendBroadcast(intent);
    }

    // reports current device status to the client via rpc
    // client uses data to enforce preferences, e.g. suspend on battery
    /**
     * Reports current device status to the client via RPC
     * BOINC client uses this data to enforce preferences, e.g. suspend battery but requires information only/best available through Java API calls.
     */
    private void reportDeviceStatus() {
        if (Logging.VERBOSE)
            Log.d(Logging.TAG, "reportDeviceStatus()");
        try {
            // set devices status
            if (deviceStatus != null) { // make sure deviceStatus is initialized
                Boolean reportStatusSuccess = clientInterface.reportDeviceStatus(deviceStatus.update(screenOn)); // transmit device status via rpc
                if (reportStatusSuccess)
                    screenOffStatusOmitCounter = 0;
                else if (Logging.DEBUG)
                    Log.d(Logging.TAG, "reporting device status returned false.");
            } else if (Logging.WARNING)
                Log.w(Logging.TAG, "reporting device status failed, wrapper not initialized.");
        } catch (Exception e) {
            if (Logging.ERROR)
                Log.e(Logging.TAG, "Monitor.reportDeviceStatus excpetion: " + e.getMessage());
        }
    }
    // --end-- multi-threaded frequent information polling

    // BOINC client installation and run-time management
    /**
     * installs client binaries(if changed) and other required files
     * executes client process
     * triggers initial reads (e.g. preferences, project list etc)
     * @return Boolean whether connection established successfully
     */
    private Boolean clientSetup() {
        if (Logging.ERROR)
            Log.d(Logging.TAG, "Monitor.clientSetup()");

        // try to get current client status from monitor
        ClientStatus status;
        try {
            status = Monitor.getClientStatus();
        } catch (Exception e) {
            if (Logging.WARNING)
                Log.w(Logging.TAG, "Monitor.clientSetup: Could not load data, clientStatus not initialized.");
            return false;
        }

        status.setSetupStatus(ClientStatus.SETUP_STATUS_LAUNCHING, true);
        String clientProcessName = boincWorkingDir + fileNameClient;

        String md5AssetClient = computeMd5(fileNameClient, true);
        //if(Logging.DEBUG) Log.d(Logging.TAG, "Hash of client (Asset): '" + md5AssetClient + "'");

        String md5InstalledClient = computeMd5(clientProcessName, false);
        //if(Logging.DEBUG) Log.d(Logging.TAG, "Hash of client (File): '" + md5InstalledClient + "'");

        // If client hashes do not match, we need to install the one that is a part
        // of the package. Shutdown the currently running client if needed.
        //
        if (forceReinstall || !md5InstalledClient.equals(md5AssetClient)) {
            if (Logging.DEBUG)
                Log.d(Logging.TAG, "Hashes of installed client does not match binary in assets - re-install.");

            // try graceful shutdown using RPC (faster)
            if (getPidForProcessName(clientProcessName) != null) {
                if (connectClient()) {
                    clientInterface.quit();
                    Integer attempts = getApplicationContext().getResources()
                            .getInteger(R.integer.shutdown_graceful_rpc_check_attempts);
                    Integer sleepPeriod = getApplicationContext().getResources()
                            .getInteger(R.integer.shutdown_graceful_rpc_check_rate_ms);
                    for (int x = 0; x < attempts; x++) {
                        try {
                            Thread.sleep(sleepPeriod);
                        } catch (Exception e) {
                        }
                        if (getPidForProcessName(clientProcessName) == null) { //client is now closed
                            if (Logging.DEBUG)
                                Log.d(Logging.TAG,
                                        "quitClient: gracefull RPC shutdown successful after " + x + " seconds");
                            x = attempts;
                        }
                    }
                }
            }

            // quit with OS signals
            if (getPidForProcessName(clientProcessName) != null) {
                quitProcessOsLevel(clientProcessName);
            }

            // at this point client is definitely not running. install new binary...
            if (!installClient()) {
                if (Logging.ERROR)
                    Log.w(Logging.TAG, "BOINC client installation failed!");
                return false;
            }
        }

        // Start the BOINC client if we need to.
        //
        Integer clientPid = getPidForProcessName(clientProcessName);
        if (clientPid == null) {
            if (Logging.ERROR)
                Log.d(Logging.TAG, "Starting the BOINC client");
            if (!runClient()) {
                if (Logging.ERROR)
                    Log.d(Logging.TAG, "BOINC client failed to start");
                return false;
            }
        }

        // Try to connect to executed Client in loop
        //
        Integer retryRate = getResources().getInteger(R.integer.monitor_setup_connection_retry_rate_ms);
        Integer retryAttempts = getResources().getInteger(R.integer.monitor_setup_connection_retry_attempts);
        Boolean connected = false;
        Integer counter = 0;
        while (!connected && (counter < retryAttempts)) {
            if (Logging.DEBUG)
                Log.d(Logging.TAG, "Attempting BOINC client connection...");
            connected = connectClient();
            counter++;

            try {
                Thread.sleep(retryRate);
            } catch (Exception e) {
            }
        }

        Boolean init = false;
        if (connected) { // connection established
            try {
                // read preferences for GUI to be able to display data
                GlobalPreferences clientPrefs = clientInterface.getGlobalPrefsWorkingStruct();
                if (clientPrefs == null)
                    throw new Exception("client prefs null");
                status.setPrefs(clientPrefs);

                // set Android model as hostinfo
                // should output something like "Samsung Galaxy SII - SDK:15 ABI:armeabi-v7a"
                String model = Build.MANUFACTURER + " " + Build.MODEL + " - SDK:" + Build.VERSION.SDK_INT + " ABI: "
                        + Build.CPU_ABI;
                String version = Build.VERSION.RELEASE;
                if (Logging.ERROR)
                    Log.d(Logging.TAG, "reporting hostinfo model name: " + model);
                if (Logging.ERROR)
                    Log.d(Logging.TAG, "reporting hostinfo os name: Android");
                if (Logging.ERROR)
                    Log.d(Logging.TAG, "reporting hostinfo os version: " + version);
                clientInterface.setHostInfo(model, version);

                init = true;
            } catch (Exception e) {
                if (Logging.ERROR)
                    Log.e(Logging.TAG, "Monitor.clientSetup() init failed: " + e.getMessage());
            }
        }

        if (init) {
            if (Logging.ERROR)
                Log.d(Logging.TAG, "Monitor.clientSetup() - setup completed successfully");
            status.setSetupStatus(ClientStatus.SETUP_STATUS_AVAILABLE, false);
        } else {
            if (Logging.ERROR)
                Log.e(Logging.TAG, "Monitor.clientSetup() - setup experienced an error");
            status.setSetupStatus(ClientStatus.SETUP_STATUS_ERROR, true);
        }

        return connected;
    }

    /**
     * Executes BOINC client.
     * Using Java Runtime exec method
     * @return Boolean success
     */
    private Boolean runClient() {
        Boolean success = false;
        try {
            String[] cmd = new String[3];

            cmd[0] = boincWorkingDir + fileNameClient;
            cmd[1] = "--daemon";
            cmd[2] = "--gui_rpc_unix_domain";

            if (Logging.ERROR)
                Log.w(Logging.TAG, "Launching '" + cmd[0] + "' from '" + boincWorkingDir + "'");
            Runtime.getRuntime().exec(cmd, null, new File(boincWorkingDir));
            success = true;
        } catch (IOException e) {
            if (Logging.ERROR)
                Log.d(Logging.TAG, "Starting BOINC client failed with exception: " + e.getMessage());
            if (Logging.ERROR)
                Log.e(Logging.TAG, "IOException", e);
        }
        return success;
    }

    /**
     * Establishes connection to client and handles initial authentication
     * @return Boolean success
     */
    private Boolean connectClient() {
        Boolean success = false;

        success = clientInterface.open(clientSocketAddress);
        if (!success) {
            if (Logging.ERROR)
                Log.e(Logging.TAG, "connection failed!");
            return success;
        }

        //authorize
        success = clientInterface.authorizeGuiFromFile(boincWorkingDir + fileNameGuiAuthentication);
        if (!success) {
            if (Logging.ERROR)
                Log.e(Logging.TAG, "authorization failed!");
        }
        return success;
    }

    /**
     * Installs required files from APK's asset directory to the applications' internal storage.
     * File attributes override and executable are defined here
     * @return Boolean success
     */
    private Boolean installClient() {

        if (!installFile(fileNameClient, true, true)) {
            if (Logging.ERROR)
                Log.d(Logging.TAG, "Failed to install: " + fileNameClient);
            return false;
        }
        if (!installFile(fileNameCLI, true, true)) {
            if (Logging.ERROR)
                Log.d(Logging.TAG, "Failed to install: " + fileNameCLI);
            return false;
        }
        if (!installFile(fileNameCABundle, true, false)) {
            if (Logging.ERROR)
                Log.d(Logging.TAG, "Failed to install: " + fileNameCABundle);
            return false;
        }
        if (!installFile(fileNameClientConfig, true, false)) {
            if (Logging.ERROR)
                Log.d(Logging.TAG, "Failed to install: " + fileNameClientConfig);
            return false;
        }
        if (!installFile(fileNameAllProjectsList, true, false)) {
            if (Logging.ERROR)
                Log.d(Logging.TAG, "Failed to install: " + fileNameAllProjectsList);
            return false;
        }

        return true;
    }

    /**
     * Copies given file from APK assets to internal storage.
     * @param file name of file as it appears in assets directory
     * @param override define override, if already present in internal storage
     * @param executable set executable flag of file in internal storage
     * @return Boolean success
     */
    private Boolean installFile(String file, Boolean override, Boolean executable) {
        Boolean success = false;
        byte[] b = new byte[1024];
        int count;

        // If file is executable, cpu architecture has to be evaluated
        // and assets directory select accordingly
        String source = "";
        if (executable)
            source = getAssestsDirForCpuArchitecture() + file;
        else
            source = file;

        try {
            if (Logging.ERROR)
                Log.d(Logging.TAG, "installing: " + source);

            File target = new File(boincWorkingDir + file);

            // Check path and create it
            File installDir = new File(boincWorkingDir);
            if (!installDir.exists()) {
                installDir.mkdir();
                installDir.setWritable(true);
            }

            if (target.exists()) {
                if (override)
                    target.delete();
                else {
                    if (Logging.DEBUG)
                        Log.d(Logging.TAG, "skipped file, exists and ovverride is false");
                    return true;
                }
            }

            // Copy file from the asset manager to clientPath
            InputStream asset = getApplicationContext().getAssets().open(source);
            OutputStream targetData = new FileOutputStream(target);
            while ((count = asset.read(b)) != -1) {
                targetData.write(b, 0, count);
            }
            asset.close();
            targetData.flush();
            targetData.close();

            success = true; //copy succeeded without exception

            // Set executable, if requested
            Boolean isExecutable = false;
            if (executable) {
                target.setExecutable(executable);
                isExecutable = target.canExecute();
                success = isExecutable; // return false, if not executable
            }

            if (Logging.ERROR)
                Log.d(Logging.TAG,
                        "install of " + source + " successfull. executable: " + executable + "/" + isExecutable);

        } catch (IOException e) {
            if (Logging.ERROR)
                Log.e(Logging.TAG, "IOException: " + e.getMessage());
            if (Logging.ERROR)
                Log.d(Logging.TAG, "install of " + source + " failed.");
        }

        return success;
    }

    /**
     * Determines assets directory (contains BOINC client binaries) corresponding to device's cpu architecture (ARM, x86 or MIPS)
     * @return name of assets directory for given platform, not an absolute path.
     */
    private String getAssestsDirForCpuArchitecture() {
        String archAssetsDirectory = "";
        switch (getBoincPlatform()) {
        case R.string.boinc_platform_name_arm:
            archAssetsDirectory = getString(R.string.assets_dir_arm);
            break;
        case R.string.boinc_platform_name_arm64:
            archAssetsDirectory = getString(R.string.assets_dir_arm64);
            break;
        case R.string.boinc_platform_name_x86:
            archAssetsDirectory = getString(R.string.assets_dir_x86);
            break;
        case R.string.boinc_platform_name_x86_64:
            archAssetsDirectory = getString(R.string.assets_dir_x86_64);
            break;
        case R.string.boinc_platform_name_mips:
            archAssetsDirectory = getString(R.string.assets_dir_mips);
            break;
        case R.string.boinc_platform_name_mips64:
            archAssetsDirectory = getString(R.string.assets_dir_mips64);
            break;
        }
        return archAssetsDirectory;
    }

    /**
     * Computes MD5 hash of requested file
     * @param fileName absolute path or name of file in assets directory, see inAssets parameter
     * @param inAssets if true, fileName is file name in assets directory, if not, absolute path
     * @return md5 hash of file
     */
    private String computeMd5(String fileName, Boolean inAssets) {
        byte[] b = new byte[1024];
        int count;

        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");

            InputStream fs = null;
            if (inAssets)
                fs = getApplicationContext().getAssets().open(getAssestsDirForCpuArchitecture() + fileName);
            else
                fs = new FileInputStream(new File(fileName));

            while ((count = fs.read(b)) != -1) {
                md5.update(b, 0, count);
            }
            fs.close();

            byte[] md5hash = md5.digest();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < md5hash.length; ++i) {
                sb.append(String.format("%02x", md5hash[i]));
            }

            return sb.toString();
        } catch (IOException e) {
            if (Logging.ERROR)
                Log.e(Logging.TAG, "IOException: " + e.getMessage());
        } catch (NoSuchAlgorithmException e) {
            if (Logging.ERROR)
                Log.e(Logging.TAG, "NoSuchAlgorithmException: " + e.getMessage());
        }

        return "";
    }

    /**
     * Determines ProcessID corresponding to given process name
     * @param processName name of process, according to output of "ps"
     * @return process id, according to output of "ps"
     */
    private Integer getPidForProcessName(String processName) {
        int count;
        char[] buf = new char[1024];
        StringBuffer sb = new StringBuffer();

        //run ps and read output
        try {
            Process p = Runtime.getRuntime().exec("ps");
            p.waitFor();
            InputStreamReader isr = new InputStreamReader(p.getInputStream());
            while ((count = isr.read(buf)) != -1) {
                sb.append(buf, 0, count);
            }
        } catch (Exception e) {
            if (Logging.ERROR)
                Log.e(Logging.TAG, "Exception: " + e.getMessage());
            return null;
        }

        String[] processLinesAr = sb.toString().split("\n");
        if (processLinesAr.length < 2) {
            if (Logging.ERROR)
                Log.e(Logging.TAG, "getPidForProcessName(): ps output has less than 2 lines, failure!");
            return null;
        }

        // figure out what index PID has
        String[] headers = processLinesAr[0].split("[\\s]+");
        Integer PidIndex = 1;
        for (int x = 0; x < headers.length; x++) {
            if (headers[x].equals("PID")) {
                PidIndex = x;
                continue;
            }
        }
        if (Logging.DEBUG)
            Log.d(Logging.TAG,
                    "getPidForProcessName(): PID at index: " + PidIndex + " for output: " + processLinesAr[0]);

        Integer pid = null;
        for (int y = 1; y < processLinesAr.length; y++) {
            Boolean found = false;
            String[] comps = processLinesAr[y].split("[\\s]+");
            for (String arg : comps) {
                if (arg.equals(processName)) {
                    if (Logging.DEBUG)
                        Log.d(Logging.TAG, "getPidForProcessName(): " + processName + " found in line: " + y);
                    found = true;
                }
            }
            if (found) {
                try {
                    pid = Integer.parseInt(comps[PidIndex]);
                    if (Logging.ERROR)
                        Log.d(Logging.TAG, "getPidForProcessName(): pid: " + pid);
                } catch (NumberFormatException e) {
                    if (Logging.ERROR)
                        Log.e(Logging.TAG, "getPidForProcessName(): NumberFormatException for " + comps[PidIndex]
                                + " at index: " + PidIndex);
                }
                continue;
            }
        }
        // if not happen in ps output, not running?!
        if (pid == null)
            if (Logging.ERROR)
                Log.d(Logging.TAG, "getPidForProcessName(): " + processName + " not found in ps output!");

        // Find required pid
        return pid;
    }

    /**
     * Exits a process by sending it Linux SIGQUIT and SIGKILL signals
     * @param processName name of process to be killed, according to output of "ps"
     */
    private void quitProcessOsLevel(String processName) {
        Integer clientPid = getPidForProcessName(processName);

        // client PID could not be read, client already ended / not yet started?
        if (clientPid == null) {
            if (Logging.ERROR)
                Log.d(Logging.TAG, "quitProcessOsLevel could not find PID, already ended or not yet started?");
            return;
        }

        if (Logging.DEBUG)
            Log.d(Logging.TAG, "quitProcessOsLevel for " + processName + ", pid: " + clientPid);

        // Do not just kill the client on the first attempt.  That leaves dangling 
        // science applications running which causes repeated spawning of applications.
        // Neither the UI or client are happy and each are trying to recover from the
        // situation.  Instead send SIGQUIT and give the client time to clean up.
        //
        android.os.Process.sendSignal(clientPid, android.os.Process.SIGNAL_QUIT);

        // Wait for the client to shutdown gracefully
        Integer attempts = getApplicationContext().getResources()
                .getInteger(R.integer.shutdown_graceful_os_check_attempts);
        Integer sleepPeriod = getApplicationContext().getResources()
                .getInteger(R.integer.shutdown_graceful_os_check_rate_ms);
        for (int x = 0; x < attempts; x++) {
            try {
                Thread.sleep(sleepPeriod);
            } catch (Exception e) {
            }
            if (getPidForProcessName(processName) == null) { //client is now closed
                if (Logging.DEBUG)
                    Log.d(Logging.TAG, "quitClient: gracefull SIGQUIT shutdown successful after " + x + " seconds");
                x = attempts;
            }
        }

        clientPid = getPidForProcessName(processName);
        if (clientPid != null) {
            // Process is still alive, send SIGKILL
            if (Logging.ERROR)
                Log.w(Logging.TAG, "SIGQUIT failed. SIGKILL pid: " + clientPid);
            android.os.Process.killProcess(clientPid);
        }

        clientPid = getPidForProcessName(processName);
        if (clientPid != null) {
            if (Logging.ERROR)
                Log.w(Logging.TAG, "SIGKILL failed. still living pid: " + clientPid);
        }
    }
    // --end-- BOINC client installation and run-time management

    // broadcast receiver
    /**
     * broadcast receiver to detect changes to screen on or off, used to adapt scheduling of StatusUpdateTimerTask
     * e.g. avoid polling GUI status RPCs while screen is off in order to save battery
     */
    BroadcastReceiver screenOnOffReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                screenOn = false;
                // forces report of device status at next scheduled update
                // allows timely reaction to screen off for resume of computation
                screenOffStatusOmitCounter = deviceStatusIntervalScreenOff;
                if (Logging.DEBUG)
                    Log.d(Logging.TAG, "screenOnOffReceiver: screen turned off");
            }
            if (action.equals(Intent.ACTION_SCREEN_ON)) {
                screenOn = true;
                if (Logging.DEBUG)
                    Log.d(Logging.TAG, "screenOnOffReceiver: screen turned on, force data refresh...");
                forceRefresh();
            }
        }
    };
    // --end-- broadcast receiver

    // async tasks
    private final class SetClientRunModeAsync extends AsyncTask<Integer, Void, Void> {
        @Override
        protected Void doInBackground(Integer... params) {
            try {
                mBinder.setRunMode(params[0]);
            } catch (RemoteException e) {
            }
            return null;
        }
    }
    // --end -- async tasks

    // remote service
    private final IMonitor.Stub mBinder = new IMonitor.Stub() {

        @Override
        public boolean transferOperation(List<Transfer> list, int op) throws RemoteException {
            return clientInterface.transferOperation((ArrayList<Transfer>) list, op);
        }

        @Override
        public boolean synchronizeAcctMgr(String url) throws RemoteException {
            return clientInterface.synchronizeAcctMgr(url);
        }

        @Override
        public boolean setRunMode(int mode) throws RemoteException {
            return clientInterface.setRunMode(mode);
        }

        @Override
        public boolean setNetworkMode(int mode) throws RemoteException {
            return clientInterface.setNetworkMode(mode);
        }

        @Override
        public boolean setGlobalPreferences(GlobalPreferences pref) throws RemoteException {
            return clientInterface.setGlobalPreferences(pref);
        }

        @Override
        public boolean setCcConfig(String config) throws RemoteException {
            return clientInterface.setCcConfig(config);
        }

        @Override
        public boolean resultOp(int op, String url, String name) throws RemoteException {
            return clientInterface.resultOp(op, url, name);
        }

        @Override
        public String readAuthToken(String path) throws RemoteException {
            return clientInterface.readAuthToken(path);
        }

        @Override
        public boolean projectOp(int status, String url) throws RemoteException {
            return clientInterface.projectOp(status, url);
        }

        @Override
        public int getBoincPlatform() throws RemoteException {
            return Monitor.this.getBoincPlatform();
        }

        @Override
        public AccountOut lookupCredentials(AccountIn credentials) throws RemoteException {
            return clientInterface.lookupCredentials(credentials);
        }

        @Override
        public boolean isStationaryDeviceSuspected() throws RemoteException {
            try {
                return Monitor.getDeviceStatus().isStationaryDeviceSuspected();
            } catch (Exception e) {
            }
            return false;
        }

        @Override
        public List<Notice> getServerNotices() throws RemoteException {
            return clientStatus.getServerNotices();
        }

        @Override
        public ProjectConfig getProjectConfigPolling(String url) throws RemoteException {
            return clientInterface.getProjectConfigPolling(url);
        }

        @Override
        public List<Notice> getNotices(int seq) throws RemoteException {
            return clientInterface.getNotices(seq);
        }

        @Override
        public List<Message> getMessages(int seq) throws RemoteException {
            return clientInterface.getMessages(seq);
        }

        @Override
        public List<Message> getEventLogMessages(int seq, int num) throws RemoteException {
            return clientInterface.getEventLogMessages(seq, num);
        }

        @Override
        public int getBatteryChargeStatus() throws RemoteException {
            try {
                return getDeviceStatus().getStatus().battery_charge_pct;
            } catch (Exception e) {
            }
            return 0;
        }

        @Override
        public AcctMgrInfo getAcctMgrInfo() throws RemoteException {
            return clientInterface.getAcctMgrInfo();
        }

        @Override
        public void forceRefresh() throws RemoteException {
            Monitor.this.forceRefresh();
        }

        @Override
        public AccountOut createAccountPolling(AccountIn information) throws RemoteException {
            return clientInterface.createAccountPolling(information);
        }

        @Override
        public boolean checkProjectAttached(String url) throws RemoteException {
            return clientInterface.checkProjectAttached(url);
        }

        @Override
        public boolean attachProject(String url, String projectName, String authenticator) throws RemoteException {
            return clientInterface.attachProject(url, projectName, authenticator);
        }

        @Override
        public int addAcctMgrErrorNum(String url, String userName, String pwd) throws RemoteException {
            AcctMgrRPCReply acctMgr = clientInterface.addAcctMgr(url, userName, pwd);
            if (acctMgr != null) {
                return acctMgr.error_num;
            }
            return -1;
        }

        @Override
        public String getAuthFilePath() throws RemoteException {
            return Monitor.this.getAuthFilePath();
        }

        @Override
        public List<ProjectInfo> getAttachableProjects() throws RemoteException {
            return clientInterface.getAttachableProjects(getString(getBoincPlatform()), getBoincAltPlatform());
        }

        @Override
        public boolean getAcctMgrInfoPresent() throws RemoteException {
            return clientStatus.getAcctMgrInfo().present;
        }

        @Override
        public int getSetupStatus() throws RemoteException {
            return clientStatus.setupStatus;
        }

        @Override
        public int getComputingStatus() throws RemoteException {
            return clientStatus.computingStatus;
        }

        @Override
        public int getComputingSuspendReason() throws RemoteException {
            return clientStatus.computingSuspendReason;
        }

        @Override
        public int getNetworkSuspendReason() throws RemoteException {
            return clientStatus.networkSuspendReason;
        }

        @Override
        public HostInfo getHostInfo() throws RemoteException {
            return clientStatus.getHostInfo();
        }

        @Override
        public GlobalPreferences getPrefs() throws RemoteException {
            return clientStatus.getPrefs();
        }

        @Override
        public List<Project> getProjects() throws RemoteException {
            return clientStatus.getProjects();
        }

        @Override
        public AcctMgrInfo getClientAcctMgrInfo() throws RemoteException {
            return clientStatus.getAcctMgrInfo();
        }

        @Override
        public List<Transfer> getTransfers() throws RemoteException {
            return clientStatus.getTransfers();
        }

        @Override
        public void setAutostart(boolean isAutoStart) throws RemoteException {
            Monitor.getAppPrefs().setAutostart(isAutoStart);
        }

        @Override
        public void setShowNotificationForNotices(boolean isShow) throws RemoteException {
            Monitor.getAppPrefs().setShowNotificationForNotices(isShow);
        }

        @Override
        public boolean getShowAdvanced() throws RemoteException {
            return Monitor.getAppPrefs().getShowAdvanced();
        }

        @Override
        public boolean getAutostart() throws RemoteException {
            return Monitor.getAppPrefs().getAutostart();
        }

        @Override
        public boolean getShowNotificationForNotices() throws RemoteException {
            return Monitor.getAppPrefs().getShowNotificationForNotices();
        }

        @Override
        public int getLogLevel() throws RemoteException {
            return Monitor.getAppPrefs().getLogLevel();
        }

        @Override
        public void setLogLevel(int level) throws RemoteException {
            Monitor.getAppPrefs().setLogLevel(level);
        }

        @Override
        public void setPowerSourceAc(boolean src) throws RemoteException {
            Monitor.getAppPrefs().setPowerSourceAc(src);
        }

        @Override
        public void setPowerSourceUsb(boolean src) throws RemoteException {
            Monitor.getAppPrefs().setPowerSourceUsb(src);
        }

        @Override
        public void setPowerSourceWireless(boolean src) throws RemoteException {
            Monitor.getAppPrefs().setPowerSourceWireless(src);
        }

        @Override
        public List<Result> getTasks() throws RemoteException {
            return clientStatus.getTasks();
        }

        @Override
        public String getProjectStatus(String url) throws RemoteException {
            return clientStatus.getProjectStatus(url);
        }

        @Override
        public List<Notice> getRssNotices() throws RemoteException {
            return clientStatus.getRssNotices();
        }

        @Override
        public List<ImageWrapper> getSlideshowForProject(String url) throws RemoteException {
            return clientStatus.getSlideshowForProject(url);
        }

        @Override
        public boolean getStationaryDeviceMode() throws RemoteException {
            return Monitor.getAppPrefs().getStationaryDeviceMode();
        }

        @Override
        public boolean getPowerSourceAc() throws RemoteException {
            return Monitor.getAppPrefs().getPowerSourceAc();
        }

        @Override
        public boolean getPowerSourceUsb() throws RemoteException {
            return Monitor.getAppPrefs().getPowerSourceUsb();
        }

        @Override
        public boolean getPowerSourceWireless() throws RemoteException {
            return Monitor.getAppPrefs().getPowerSourceWireless();
        }

        @Override
        public void setShowAdvanced(boolean isShow) throws RemoteException {
            Monitor.getAppPrefs().setShowAdvanced(isShow);
        }

        @Override
        public void setStationaryDeviceMode(boolean mode) throws RemoteException {
            Monitor.getAppPrefs().setStationaryDeviceMode(mode);

        }

        @Override
        public Bitmap getProjectIconByName(String name) throws RemoteException {
            return clientStatus.getProjectIconByName(name);
        }

        @Override
        public Bitmap getProjectIcon(String id) throws RemoteException {
            return clientStatus.getProjectIcon(id);
        }

        @Override
        public boolean getSuspendWhenScreenOn() throws RemoteException {
            return Monitor.getAppPrefs().getSuspendWhenScreenOn();
        }

        @Override
        public void setSuspendWhenScreenOn(boolean swso) throws RemoteException {
            Monitor.getAppPrefs().setSuspendWhenScreenOn(swso);
        }

        @Override
        public String getCurrentStatusTitle() throws RemoteException {
            return clientStatus.getCurrentStatusTitle();
        }

        @Override
        public String getCurrentStatusDescription() throws RemoteException {
            return clientStatus.getCurrentStatusDescription();
        }

        @Override
        public void cancelNoticeNotification() throws RemoteException {
            NoticeNotification.getInstance(getApplicationContext()).cancelNotification();
        }

        @Override
        public void setShowNotificationDuringSuspend(boolean isShow) throws RemoteException {
            Monitor.getAppPrefs().setShowNotificationDuringSuspend(isShow);

        }

        @Override
        public boolean getShowNotificationDuringSuspend() throws RemoteException {
            return Monitor.getAppPrefs().getShowNotificationDuringSuspend();
        }

        @Override
        public boolean runBenchmarks() throws RemoteException {
            return clientInterface.runBenchmarks();
        }

        @Override
        public ProjectInfo getProjectInfo(String url) throws RemoteException {
            return clientInterface.getProjectInfo(url);
        }

        @Override
        public boolean boincMutexAcquired() throws RemoteException {
            return mutex.acquired;
        }
    };
    // --end-- remote service   
}