com.ginstr.android.service.opencellid.upload.data.MeasurementsUploaderService.java Source code

Java tutorial

Introduction

Here is the source code for com.ginstr.android.service.opencellid.upload.data.MeasurementsUploaderService.java

Source

/**
 * Copyright 2014 ginstr GmbH
 * 
 * This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License.
 * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ 
 *  
 *  Unless required by applicable law or agreed to in writing, software 
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 *  See the License for the specific language governing permissions and 
 *  limitations under the License. 
 */
package com.ginstr.android.service.opencellid.upload.data;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.Locale;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.util.EntityUtils;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

import com.ginstr.android.service.opencellid.library.data.ApiKeyHandler;
import com.ginstr.android.service.opencellid.library.data.Measurement;
import com.ginstr.android.service.opencellid.library.data.Network;
import com.ginstr.android.service.opencellid.library.db.MeasurementsDatabase;
import com.ginstr.android.service.opencellid.library.db.MeasurementsTable.MeasurementsDBIterator;
import com.ginstr.android.service.opencellid.library.db.NetworksTable.NetworkDBIterator;
import com.ginstr.android.service.opencellid.library.db.OpenCellIdLibContext;
import com.ginstr.android.service.opencellid.upload.OpenCellIDUploadService;
import com.ginstr.android.service.opencellid.upload.utils.NetUtils;

/**
 * This service will upload data to OpenCellID servers.
 * Use {@link OpenCellIDUploadService} to control it.
 * 
 * @author fox
 * @author Roberto Gonzalez
 * @author Danijel
 * @author Dinko Ivkovic
 */
public class MeasurementsUploaderService extends Service {
    private OpenCellIdLibContext mLibContext;
    private MeasurementsDatabase mDatabase;

    /**
     * MeasurementsUploaderService parameters
     */
    private String appId;
    private String apiKey = "";
    private String openCellUrl;
    private String networksUrl;
    private long newDataCheckInterval = UploadConstants.NEW_DATA_CHECK_INTERVAL_LONG_DEFAULT;
    private boolean wifiOnly = UploadConstants.PREF_ONLY_WIFI_UPLOAD_DEFAULT;
    private boolean testEnvironment = UploadConstants.PREF_TEST_ENVIRONMENT;
    private int maxLogFileSize = UploadConstants.MAX_LOG_SIZE_DEFAULT;
    private boolean logToFileEnabled = UploadConstants.LOG_TO_FILE_DEFAULT;

    private Thread uploadThread;
    private static volatile boolean uploadThreadRunning;

    private HttpClient httpclient;
    private HttpPost httppost;

    private static final String MULTIPART_FILENAME = "upload.csv";
    private static final int MEASUREMENTS_BATCH_SIZE = 1024;

    /**
     * Configuration change receiver. Anyone can send broadcasts to this service
     * with changed parameters.
     */
    private BroadcastReceiver configurationReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {

            writeToLog("MeasurementsUploaderService configurationReceiver.onReceive()");

            if (intent != null) {
                Bundle extras = intent.getExtras();
                getExtraParameters(extras);
            }
        }
    };

    private void writeToLog(String message) {
        mLibContext.getLogService().writeLog(Log.INFO, OpenCellIdLibContext.LOG_FILENAME_PREFIX, message);
    }

    private void writeExceptionToLog(Exception ex) {
        mLibContext.getLogService().writeErrorLog(OpenCellIdLibContext.LOG_FILENAME_PREFIX, "", ex);
    }

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

        mLibContext = OpenCellIdLibContext.getInstance() == null ? new OpenCellIdLibContext(getApplicationContext())
                : OpenCellIdLibContext.getInstance();

        writeToLog("MeasurementsUploaderService onCreate()");

        // android.os.Debug.waitForDebugger();

        registerReceiver(configurationReceiver, new IntentFilter(UploadConstants.CONFIGURATION_ACTION));
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        writeToLog("MeasurementsUploaderService onStartCommand()");

        mLibContext = OpenCellIdLibContext.getInstance() == null ? new OpenCellIdLibContext(getApplicationContext())
                : OpenCellIdLibContext.getInstance();

        mDatabase = mLibContext.getMeasurementsDatabase();

        // check the received intent for configuration parameters
        configurationReceiver.onReceive(this, intent);

        //start upload process
        startUploading();

        return START_REDELIVER_INTENT;
    }

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

        writeToLog("MeasurementsUploaderService onDestroy()");

        // stop upload process
        uploadThreadRunning = false;

        if (uploadThread != null) {
            try {
                uploadThread.interrupt();
            } catch (Exception e) {
            }
            uploadThread = null;
        }

        unregisterReceiver(configurationReceiver);
    }

    /**
     * reads API key
     */
    private void retrieveApiKey() {
        if (apiKey == null || apiKey.length() == 0) {
            String temp = ApiKeyHandler.getApiKey();
            if (temp == null)
                writeToLog("Cannot get API key!");
            else
                apiKey = temp;
        }
    }

    /**
     * Starts uploading data.
     */
    private void startUploading() {
        if (uploadThread != null) {
            writeToLog("Upload thread active");
            return;
        }

        uploadThread = new Thread(new Runnable() {
            @Override
            public void run() {
                uploadThreadRunning = true;

                while (uploadThreadRunning) {
                    //get API key
                    retrieveApiKey();

                    //get a list of non uploaded measurements
                    MeasurementsDBIterator dbIterator = mDatabase.getNonUploadedMeasurements();
                    boolean success = true;
                    String errorMsg = "";
                    int max = 0;

                    int count = 0;

                    // stop uploading if the upload is working only when the wifi is active 
                    if (wifiOnly && !NetUtils.isWifiConnected(getApplicationContext())) {
                        writeToLog("WiFi only and WiFi is not connected.");
                        try {
                            Thread.sleep(newDataCheckInterval);
                        } catch (Exception e) {
                        }

                        continue;
                    }

                    try {
                        writeToLog("Checking if there are records for upload...");
                        if (dbIterator.getCount() > 0) {

                            httpclient = new DefaultHttpClient();
                            httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
                                    HttpVersion.HTTP_1_0);

                            writeToLog("MeasurementsUploaderService startUploading() - openCellUrl=" + openCellUrl);

                            httppost = new HttpPost(openCellUrl);

                            max = dbIterator.getCount();
                            count = 0;

                            NumberFormat latLonFormat = NumberFormat.getNumberInstance(Locale.US);
                            latLonFormat.setMaximumFractionDigits(10);

                            while (dbIterator.hasNext() && uploadThreadRunning) {
                                //upload measurements as a batch file
                                count = uploadMeasurementsBatch(dbIterator, latLonFormat, count, max);
                                Intent progress = new Intent(OpenCellIDUploadService.BROADCAST_PROGRESS_ACTION);
                                progress.putExtra(OpenCellIDUploadService.XTRA_MAXPROGRESS, max);
                                progress.putExtra(OpenCellIDUploadService.XTRA_PROGRESS, count);
                                sendBroadcast(progress);
                            }

                            // set all measurements as uploaded
                            if (uploadThreadRunning) {
                                mDatabase.setAllMeasurementsUploaded();
                            }

                            // start uploading networks data
                            if (uploadThreadRunning) {
                                uploadNetworks();
                            }

                            writeToLog("Uploaded " + count + " of " + max + " records.");
                            httpclient.getConnectionManager().shutdown();
                            success = true;
                        } else {
                            writeToLog("No records for upload right now.");
                        }

                    } catch (Exception e) {
                        writeExceptionToLog(e);
                        success = false;
                        errorMsg = e.getMessage();
                    } finally {
                        dbIterator.close();
                    }

                    Intent progress = new Intent(OpenCellIDUploadService.BROADCAST_PROGRESS_ACTION);
                    progress.putExtra(OpenCellIDUploadService.XTRA_MAXPROGRESS, max);
                    progress.putExtra(OpenCellIDUploadService.XTRA_PROGRESS, count);
                    progress.putExtra(OpenCellIDUploadService.XTRA_DONE, true);
                    progress.putExtra(OpenCellIDUploadService.XTRA_SUCCESS, success);

                    if (!success) {
                        progress.putExtra(OpenCellIDUploadService.XTRA_FAILURE_MSG, errorMsg);
                    }
                    sendBroadcast(progress);

                    try {
                        Thread.sleep(newDataCheckInterval);
                    } catch (Exception e) {
                    }
                }
            }
        });
        uploadThread.start();
    }

    /**
     * uploads measurements data as a batch file
     * @param dbIterator
     * @param latLonFormat
     * @param count
     * @param max
     * @return number of uploaded measurements
     */
    private int uploadMeasurementsBatch(MeasurementsDBIterator dbIterator, NumberFormat latLonFormat, int count,
            int max) {
        writeToLog("uploadMeasurementsBatch(" + count + ", " + max + ")");

        try {
            StringBuilder sb = new StringBuilder(
                    "lat,lon,mcc,mnc,lac,cellid,signal,measured_at,rating,speed,direction,act\n");

            int thisBatchSize = 0;
            while (thisBatchSize < MEASUREMENTS_BATCH_SIZE && dbIterator.hasNext() && uploadThreadRunning) {
                Measurement meassurement = dbIterator.next();

                sb.append(latLonFormat.format(meassurement.getLat())).append(",");
                sb.append(latLonFormat.format(meassurement.getLon())).append(",");
                sb.append(meassurement.getMcc()).append(",");
                sb.append(meassurement.getMnc()).append(",");
                sb.append(meassurement.getLac()).append(",");
                sb.append(meassurement.getCellid()).append(",");
                sb.append(meassurement.getGsmSignalStrength()).append(",");
                sb.append(meassurement.getTimestamp()).append(",");
                sb.append((meassurement.getAccuracy() != null) ? meassurement.getAccuracy() : "").append(",");
                sb.append((int) meassurement.getSpeed()).append(",");
                sb.append((int) meassurement.getBearing()).append(",");
                sb.append((meassurement.getNetworkType() != null) ? meassurement.getNetworkType() : "");
                sb.append("\n");

                thisBatchSize++;
            }

            HttpResponse response = null;

            writeToLog("Upload request URL: " + httppost.getURI());

            if (uploadThreadRunning) {
                String csv = sb.toString();

                writeToLog("Upload data: " + csv);

                MultipartEntity mpEntity = new MultipartEntity();
                mpEntity.addPart("key", new StringBody(apiKey));
                mpEntity.addPart("appId", new StringBody(appId));
                mpEntity.addPart("datafile", new InputStreamBody(new ByteArrayInputStream(csv.getBytes()),
                        "text/csv", MULTIPART_FILENAME));

                ByteArrayOutputStream bArrOS = new ByteArrayOutputStream();
                // reqEntity is the MultipartEntity instance
                mpEntity.writeTo(bArrOS);
                bArrOS.flush();
                ByteArrayEntity bArrEntity = new ByteArrayEntity(bArrOS.toByteArray());
                bArrOS.close();

                bArrEntity.setChunked(false);
                bArrEntity.setContentEncoding(mpEntity.getContentEncoding());
                bArrEntity.setContentType(mpEntity.getContentType());

                httppost.setEntity(bArrEntity);

                response = httpclient.execute(httppost);
                if (response == null) {
                    writeToLog("Upload: null HTTP-response");
                    throw new IllegalStateException("no HTTP-response from server");
                }

                HttpEntity resEntity = response.getEntity();

                writeToLog(
                        "Upload: " + response.getStatusLine().getStatusCode() + " - " + response.getStatusLine());

                if (resEntity != null) {
                    resEntity.consumeContent();
                }

                if (response.getStatusLine() == null) {
                    writeToLog(": " + "null HTTP-status-line");
                    throw new IllegalStateException("no HTTP-status returned");
                }

                if (response.getStatusLine().getStatusCode() != 200) {
                    throw new IllegalStateException(
                            "HTTP-status code returned : " + response.getStatusLine().getStatusCode());
                }
            }

            return count + thisBatchSize;

        } catch (IOException e) {
            throw new IllegalStateException("IO-Error: " + e.getMessage());
        }
    }

    /**
     * Used to upload entries from networks table to OCID servers.
     */
    private void uploadNetworks() {
        writeToLog("uploadNetworks()");

        String existingFileName = "uploadNetworks.csv";
        String data = null;

        NetworkDBIterator dbIterator = mDatabase.getNonUploadedNetworks();
        try {
            if (dbIterator.getCount() > 0) {
                //timestamp, mcc, mnc, net (network type), nen (network name)
                StringBuilder sb = new StringBuilder("timestamp,mcc,mnc,net,nen" + ((char) 0xA));

                while (dbIterator.hasNext() && uploadThreadRunning) {
                    Network network = dbIterator.next();
                    sb.append(network.getTimestamp()).append(",");
                    sb.append(network.getMcc()).append(",");
                    sb.append(network.getMnc()).append(",");
                    sb.append(network.getType()).append(",");
                    sb.append(network.getName());
                    sb.append(((char) 0xA));
                }

                data = sb.toString();

            } else {
                writeToLog("No networks for upload.");
                return;
            }
        } finally {
            dbIterator.close();
        }

        writeToLog("uploadNetworks(): " + data);

        if (uploadThreadRunning) {

            try {
                httppost = new HttpPost(networksUrl);

                HttpResponse response = null;

                writeToLog("Upload request URL: " + httppost.getURI());

                if (uploadThreadRunning) {
                    MultipartEntity mpEntity = new MultipartEntity();
                    mpEntity.addPart("apikey", new StringBody(apiKey));
                    mpEntity.addPart("datafile", new InputStreamBody(new ByteArrayInputStream(data.getBytes()),
                            "text/csv", existingFileName));

                    ByteArrayOutputStream bArrOS = new ByteArrayOutputStream();
                    // reqEntity is the MultipartEntity instance
                    mpEntity.writeTo(bArrOS);
                    bArrOS.flush();
                    ByteArrayEntity bArrEntity = new ByteArrayEntity(bArrOS.toByteArray());
                    bArrOS.close();

                    bArrEntity.setChunked(false);
                    bArrEntity.setContentEncoding(mpEntity.getContentEncoding());
                    bArrEntity.setContentType(mpEntity.getContentType());

                    httppost.setEntity(bArrEntity);

                    response = httpclient.execute(httppost);
                    if (response == null) {
                        writeToLog("Upload: null HTTP-response");
                        throw new IllegalStateException("no HTTP-response from server");
                    }

                    HttpEntity resEntity = response.getEntity();

                    writeToLog("Response: " + response.getStatusLine().getStatusCode() + " - "
                            + response.getStatusLine());

                    if (resEntity != null) {
                        writeToLog("Response content: " + EntityUtils.toString(resEntity));
                        resEntity.consumeContent();
                    }
                }

                if (uploadThreadRunning) {
                    if (response == null) {
                        writeToLog(": " + "null response");

                        throw new IllegalStateException("no response");
                    }
                    if (response.getStatusLine() == null) {
                        writeToLog(": " + "null HTTP-status-line");

                        throw new IllegalStateException("no HTTP-status returned");
                    }
                    if (response.getStatusLine().getStatusCode() == 200) {
                        mDatabase.setAllNetworksUploaded();
                    } else if (response.getStatusLine().getStatusCode() != 200) {
                        throw new IllegalStateException(
                                response.getStatusLine().getStatusCode() + " HTTP-status returned");
                    }
                }

            } catch (Exception e) {
                // httppost cancellation throws exceptions
                if (uploadThreadRunning) {
                    writeExceptionToLog(e);
                }
            }
        }
    }

    /**
     * No binder is needed.
     */
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * checks intent configuration parameters
     * @param extras
     */
    private void getExtraParameters(Bundle extras) {
        if (extras != null) {
            writeToLog("configurationReceiver.onReceive() extras : " + extras.keySet());

            if (extras != null) {
                appId = extras.getString(UploadConstants.PREF_APPID_KEY);

                if (extras.containsKey(UploadConstants.PREF_API_KEY))
                    apiKey = extras.getString(UploadConstants.PREF_API_KEY);

                if (extras.containsKey(UploadConstants.PREF_OPENCELL_UPLOAD_URL_KEY))
                    openCellUrl = extras.getString(UploadConstants.PREF_OPENCELL_UPLOAD_URL_KEY);

                if (extras.containsKey(UploadConstants.PREF_OPENCELL_NETWORK_UPLOAD_URL_KEY))
                    networksUrl = extras.getString(UploadConstants.PREF_OPENCELL_NETWORK_UPLOAD_URL_KEY);

                testEnvironment = extras.getBoolean(UploadConstants.PREF_TEST_ENVIRONMENT_KEY,
                        UploadConstants.PREF_TEST_ENVIRONMENT);

                if (openCellUrl == null) {
                    if (testEnvironment) {
                        openCellUrl = UploadConstants.OPEN_CELL_TEST_UPLOAD_URL;
                    } else {
                        openCellUrl = UploadConstants.OPEN_CELL_DEFAULT_UPLOAD_URL;
                    }
                }

                if (networksUrl == null) {
                    if (testEnvironment) {
                        networksUrl = UploadConstants.OPENCELL_NETWORKS_TEST_UPLOAD_URL;
                    } else {
                        networksUrl = UploadConstants.OPENCELL_NETWORKS_UPLOAD_URL;
                    }
                }

                if (extras.containsKey(UploadConstants.PREF_NEW_DATA_CHECK_INTERVAL_KEY))
                    newDataCheckInterval = extras.getLong(UploadConstants.PREF_NEW_DATA_CHECK_INTERVAL_KEY);

                if (newDataCheckInterval < UploadConstants.NEW_DATA_CHECK_INTERVAL_LONG_DEFAULT)
                    newDataCheckInterval = UploadConstants.NEW_DATA_CHECK_INTERVAL_LONG_DEFAULT;

                if (extras.containsKey(UploadConstants.PREF_ONLY_WIFI_UPLOAD_KEY))
                    wifiOnly = extras.getBoolean(UploadConstants.PREF_ONLY_WIFI_UPLOAD_KEY);

                // check if the maxLogFileSize parameter is provided through intent         
                if (extras.containsKey(UploadConstants.PREFKEY_MAX_LOG_SIZE_INT)) {
                    maxLogFileSize = extras.getInt(UploadConstants.PREFKEY_MAX_LOG_SIZE_INT);

                    mLibContext.getLogService().setMaxLogFileSize(maxLogFileSize);
                }

                // check if the logToFileEnabled parameter is provided through intent         
                if (extras.containsKey(UploadConstants.PREFKEY_LOG_TO_FILE)) {
                    logToFileEnabled = extras.getBoolean(UploadConstants.PREFKEY_LOG_TO_FILE);

                    mLibContext.getLogService().setFileLoggingEnabled(logToFileEnabled);
                }
            }

            writeToLog("onConfigurationReceiver ()");
            writeToLog("apiKey = " + apiKey);
            writeToLog("openCellUrl = " + openCellUrl);
            writeToLog("networks url = " + networksUrl);
            writeToLog("newDataCheckInterval = " + newDataCheckInterval);
            writeToLog("wifiOnly = " + wifiOnly);
            writeToLog("testEnvironment = " + testEnvironment);
        }
    }
}