com.ecocitizen.service.SensorMapUploaderService.java Source code

Java tutorial

Introduction

Here is the source code for com.ecocitizen.service.SensorMapUploaderService.java

Source

/*
 * Copyright (C) 2010-2012 Eco Mobile Citizen
 *
 * This file is part of EcoCitizen.
 *
 * EcoCitizen is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EcoCitizen 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EcoCitizen.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.ecocitizen.service;

import java.io.InputStream;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

import com.ecocitizen.app.TreeViewActivity;
import com.ecocitizen.common.DeviceManagerServiceCallback;
import com.ecocitizen.common.MessageType;
import com.ecocitizen.common.bundlewrapper.NoteBundleWrapper;
import com.ecocitizen.common.bundlewrapper.SensorDataBundleWrapper;

public class SensorMapUploaderService extends Service {
    // Debugging
    private static final String TAG = "SensorMapUploaderService";
    private static final boolean D = false;

    private boolean shouldStartSession = true;

    private NotificationManager mNotificationManager;

    private static final int ICON_LOGINERROR = android.R.drawable.stat_sys_warning;
    private static final int ICON_STANDBY = android.R.drawable.stat_sys_phone_call_on_hold;
    private static final int ICON_UPLOADING = android.R.drawable.stat_sys_phone_call;
    private static final int ICON_BLOCKED = android.R.drawable.stat_notify_missed_call;

    // TODO switch to these when they are ready
    //   private static final int ICON_STANDBY = R.drawable.smu_standby;
    //   private static final int ICON_UPLOADING = R.drawable.smu_uploading;
    //   private static final int ICON_BLOCKED = R.drawable.smu_blocked;

    private enum Status {
        NONE, STANDBY, UPLOADING, BLOCKED, LOGINERROR
    }

    private Status status = Status.NONE;

    @Override
    public IBinder onBind(Intent intent) {
        if (D)
            Log.d(TAG, "+++ ON BIND +++");
        return mBinder;
    }

    @Override
    public void onRebind(Intent intent) {
        if (D)
            Log.d(TAG, "+++ ON REBIND +++");
        super.onRebind(intent);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        if (D)
            Log.d(TAG, "+++ ON UNBIND +++");
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (D)
            Log.d(TAG, "+++ ON CREATE +++");

        String ns = Context.NOTIFICATION_SERVICE;
        mNotificationManager = (NotificationManager) getSystemService(ns);
        updateStatus(Status.STANDBY);
    }

    @Override
    public void onDestroy() {
        if (D)
            Log.d(TAG, "--- ON DESTROY ---");

        disconnectDeviceManager();

        Toast.makeText(this, "Sensor Map Uploader stopped", Toast.LENGTH_SHORT);

        super.onDestroy();
    }

    private final ISensorMapUploaderService.Stub mBinder = new ISensorMapUploaderService.Stub() {
        public void deactivate() throws RemoteException {
            SensorMapUploaderService.this.deactivate();
        }

        public void activate() throws RemoteException {
            SensorMapUploaderService.this.activate();
        }

        public int getPid() throws RemoteException {
            return Process.myPid();
        }

        public void shutdown() throws RemoteException {
            mNotificationManager.cancelAll();
            stopSelf();
        }
    };

    private void activate() {
        active = true;

        connectDeviceManager();
    }

    private void deactivate() {
        active = false;

        disconnectDeviceManager();

        // TODO cancel pending SENSOR_DATA messages
        // TODO do not cancel other messages, we still need them to track sessions
    }

    private void connectDeviceManager() {
        // Start the service if not already running
        startService(new Intent(IDeviceManagerService.class.getName()));

        // Establish connection with the service.
        bindService(new Intent(IDeviceManagerService.class.getName()), mConnection, Context.BIND_AUTO_CREATE);
    }

    private void disconnectDeviceManager() {
        if (mService != null) {
            try {
                mService.unregisterCallback(mCallback);
            } catch (RemoteException e) {
                // There is nothing special we need to do if the service
                // has crashed.
                Log.e(TAG, "Exception during unregister callback.");
            }

            mService = null;

            // Detach our existing connection.
            unbindService(mConnection);
        }
    }

    private boolean active = false;

    private void receivedSensorDataBundle(Bundle bundle) {
        if (!active)
            return;

        String datarecord = new SensorDataBundleWrapper(bundle).toString();

        uploadDataRecord(datarecord);
    }

    private void receivedNoteBundle(Bundle bundle) {
        if (!active)
            return;
        uploadDataRecord(new NoteBundleWrapper(bundle).toString());
    }

    // The Handler that gets information back from the BluetoothSensorService
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MessageType.SENSOR_DATA:
                if (shouldStartSession) {
                    startSession();
                    shouldStartSession = false;
                }
                receivedSensorDataBundle((Bundle) msg.obj);
                break;
            case MessageType.NOTE:
                if (shouldStartSession) {
                    startSession();
                    shouldStartSession = false;
                }
                receivedNoteBundle((Bundle) msg.obj);
                break;
            case MessageType.SM_ALL_DEVICES_GONE:
                endSession();
                shouldStartSession = true;
                break;
            default:
                // drop all other message types
                break;
            }
        }
    };

    private IDeviceManagerService mService = null;

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IDeviceManagerService.Stub.asInterface(service);

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {
                // In this case the service has crashed before we could even
                // do anything with it; we can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
                Log.e(TAG, "Exception during register callback.");
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
        }
    };

    /**
     * Callback to receive calls from the remote service.
     */
    private IDeviceManagerServiceCallback mCallback = new DeviceManagerServiceCallback(mHandler);

    //////////////////////////////////////////////////////////////////////////
    // web service handling 
    //////////////////////////////////////////////////////////////////////////

    private static final int HTTP_STATUS_OK = 200;

    private static final int WAITFOR_SENSORMAP_MILLIS = 30000;
    private static final int WAITFOR_PREFSCHANGE_MILLIS = 10000;

    private String SENSORMAP_STATUS_URL;
    private String SENSORMAP_STARTSESSION_URL;
    private String SENSORMAP_STORE_URL;
    private String SENSORMAP_ENDSESSION_URL;
    private final String SENSORMAP_API_VERSION = "4";

    // Member variables
    private String username;
    private String api_key;
    private String map_server_url;

    private String mSessionId = null;

    private void reloadConfiguration() {
        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
        username = settings.getString("username", "");
        map_server_url = settings.getString("map_server_url", "");
        api_key = settings.getString("api_key", "");

        SENSORMAP_STATUS_URL = String.format("%sstatus/", map_server_url);
        SENSORMAP_STARTSESSION_URL = String.format("%sstartsession/%s/%s/", map_server_url, username, api_key);
        SENSORMAP_STORE_URL = String.format("%sstore/%s/", map_server_url, username);
        SENSORMAP_ENDSESSION_URL = String.format("%sendsession/%s/", map_server_url, username);
    }

    private void startSession() {
        if (D)
            Log.d(TAG, "STARTSESSION");
        mSessionId = null;
        waitForSensorMapReachable();
        waitForStartSession();
    }

    private void uploadDataRecord(String data) {
        //if (D) Log.d(TAG, "STORE = " + data);
        waitForStore(data);
    }

    private void endSession() {
        if (D)
            Log.d(TAG, "ENDSESSION");
        if (mSessionId != null)
            waitForEndSession();
    }

    /**
     * Returns HTTP response as string, or NULL on network errors
     * and if response status code is not HTTP_STATUS_OK.
     * 
     * @param url
     * @return
     */
    private String getStringResponse(String url) {
        if (D)
            Log.d(TAG, url);
        HttpClient client = new DefaultHttpClient();
        HttpGet request = new HttpGet(url);

        try {
            HttpResponse response = client.execute(request);
            StatusLine status = response.getStatusLine();
            if (status.getStatusCode() != HTTP_STATUS_OK)
                return null;

            HttpEntity entity = response.getEntity();
            InputStream istream = entity.getContent();
            byte[] buf = new byte[100];
            int bytes_read = istream.read(buf);
            if (bytes_read > 0) {
                char[] charbuf = new char[bytes_read];
                for (int i = 0; i < bytes_read; ++i)
                    charbuf[i] = (char) buf[i];
                String str = String.valueOf(charbuf);
                return str;
            } else {
                return "";
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "Exception in getStringResponse");
            return null;
        }
    }

    private void waitForSensorMapReachable() {
        while (active) {
            if (mSessionId == null)
                reloadConfiguration();

            String ret = getStringResponse(SENSORMAP_STATUS_URL);
            if (ret != null) {
                return;
            } else {
                Log.e(TAG, "sensormap UNREACHABLE");
                updateStatus(Status.BLOCKED);
                try {
                    Thread.sleep(WAITFOR_SENSORMAP_MILLIS);
                } catch (InterruptedException e) {
                    // ignore sleep interrupts
                }
            }
        }
    }

    private void waitForAccountInfoChange() {
        updateStatus(Status.LOGINERROR);

        String old_username = username;
        String old_map_server_url = map_server_url;
        String old_api_key = api_key;

        while (active) {
            if (D)
                Log.d(TAG, "Waiting for change in preferences ...");
            reloadConfiguration();

            if (!old_username.equals(username) || !old_map_server_url.equals(map_server_url)
                    || !old_api_key.equals(api_key)) {
                if (D)
                    Log.d(TAG, "OK, change in preferences detected.");
                return;
            }

            try {
                Thread.sleep(WAITFOR_PREFSCHANGE_MILLIS);
            } catch (InterruptedException e) {
                // ignore sleep interrupts
            }
        }
    }

    private void waitForStartSession() {
        while (active) {
            mSessionId = getStringResponse(SENSORMAP_STARTSESSION_URL);
            if (mSessionId == null) {
                // network error
                Log.e(TAG, "network error during /startsession, sensor map UNREACHABLE");
                waitForSensorMapReachable();
            } else if (mSessionId.equals("")) {
                // login error
                Log.e(TAG, "login error during /startsession, fix account info in preferences");
                waitForAccountInfoChange();
            } else {
                // success!
                return;
            }
        }
    }

    private void waitForStore(String data) {
        data = data.replace(" ", "");
        while (active) {
            String ret = getStringResponse(
                    SENSORMAP_STORE_URL + mSessionId + "/" + SENSORMAP_API_VERSION + "/" + data);
            //URLEncoder.encode(data));
            if (ret == null) {
                // network error
                Log.e(TAG, "network error during /store, sensor map UNREACHABLE");
                waitForSensorMapReachable();
            } else {
                // success!
                updateStatus(Status.UPLOADING);
                return;
            }
        }
    }

    private void waitForEndSession() {
        while (active) {
            String ret = getStringResponse(SENSORMAP_ENDSESSION_URL + mSessionId + "/");
            if (ret == null) {
                // network error
                Log.e(TAG, "network error during /endsession, sensor map UNREACHABLE");
                waitForSensorMapReachable();
            } else {
                // success!
                mSessionId = null;
                updateStatus(Status.STANDBY);
                return;
            }
        }
    }

    private void updateStatus(Status status) {
        if (this.status == status)
            return;
        this.status = status;

        Context context = getApplicationContext();
        CharSequence contentTitle = context.getString(com.ecocitizen.app.R.string.notification_smu_title);
        CharSequence tickerText = context.getString(com.ecocitizen.app.R.string.notification_smu_ticker);

        Notification notification;
        long when = System.currentTimeMillis();
        int icon = 0;
        CharSequence contentText = null;

        switch (status) {
        case LOGINERROR:
            icon = ICON_LOGINERROR;
            contentText = context.getString(com.ecocitizen.app.R.string.notification_smu_loginerror);
            break;
        case STANDBY:
            icon = ICON_STANDBY;
            contentText = context.getString(com.ecocitizen.app.R.string.notification_smu_standby);
            break;
        case UPLOADING:
            icon = ICON_UPLOADING;
            contentText = context.getString(com.ecocitizen.app.R.string.notification_smu_uploading);
            break;
        case BLOCKED:
        default:
            icon = ICON_BLOCKED;
            contentText = context.getString(com.ecocitizen.app.R.string.notification_smu_blocked);
            break;
        }
        notification = new Notification(icon, tickerText, when);
        Intent notificationIntent = new Intent(this, TreeViewActivity.class);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
        mNotificationManager.notify(1, notification);
    }
}