com.secupwn.aimsicd.utils.RequestTask.java Source code

Java tutorial

Introduction

Here is the source code for com.secupwn.aimsicd.utils.RequestTask.java

Source

/* Android IMSI-Catcher Detector | (c) AIMSICD Privacy Project
 * -----------------------------------------------------------
 * LICENSE:  http://git.io/vki47 | TERMS:  http://git.io/vki4o
 * -----------------------------------------------------------
 */
package com.secupwn.aimsicd.utils;

import android.content.Context;
import android.content.Intent;
import android.support.v4.app.Fragment;
import android.support.v4.content.LocalBroadcastManager;

import com.secupwn.aimsicd.BuildConfig;
import com.secupwn.aimsicd.R;
import com.secupwn.aimsicd.constants.DrawerMenu;
import com.secupwn.aimsicd.constants.TinyDbKeys;
import com.secupwn.aimsicd.service.CellTracker;
import com.secupwn.aimsicd.ui.fragments.MapFragment;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;

import io.freefair.android.injection.annotation.Inject;
import io.freefair.android.injection.app.InjectionAppCompatActivity;
import io.freefair.android.util.logging.Logger;
import io.realm.Realm;
import lombok.Cleanup;

/**
 *
 * Description:
 *
 *      This class is the request handler for Downloading and Uploading of BTS data and
 *      the backing up of the database. The download function currently requests a CVS file
 *      from OCID though an API query. The parsing of this CVS file is done in the
 *      "AIMSICDDbAdapter.java" adapter, which put the downloaded data into the
 *      {@link com.secupwn.aimsicd.data.model.Import Import} realm. This should be a read-only table, in the sense that no new
 *      BTS or info should be added there. The indexing there can be very tricky when
 *      later displayed in "DbViewerFragment.java", as they are different.
 *
 * Criticism:
 *
 *      From the original look of it. It seem to first upload newly found BTSs to OCID,
 *      then it immediately attempts to download a new OCID data set, probably expecting
 *      to see the new BTS in that data. (If this description is correct?)
 *
 *      This is NOT correct behaviour as:
 *
 *   1) OCID data takes at least a few minutes before updating their DBs with the
 *      newly uploaded CSV data file.
 *   2) It doesn't make sense to re-download data that is already populated in the
 *      DBi_bts and and DBi_measure tables.
 *   3) This is very bad because if there are fake BTS found by AIMSICD, then we're
 *      uploading them and thus making users of AIMSICD believe these are good cells.
 *      Basically we'd be corrupting the OCID data.
 *
 *
 * Issues:
 *          [ ] There is no onPreExecute here...perhaps that's why the progress bar is not shown?
 *              see:  http://developer.android.com/reference/android/os/AsyncTask.html
 *  To Fix:
 *
 *      [ ] Explain why BACKUP/RESTORE_DATABASE is in here?
 *      [ ] Think about what "lookup cell info" (CELL_LOOKUP) should do
 *      [ ] App is blocked while downloading.
 *
 */
public class RequestTask extends BaseAsyncTask<String, Integer, String> {

    //Calling from the menu more extensive(more difficult for sever),
    // we have to give more time for the server response
    public static final int REQUEST_TIMEOUT_MAPS = 80000; // [ms] 80 s Calling from map
    public static final int REQUEST_TIMEOUT_MENU = 80000; // [ms] 80 s Calling from menu

    public static final char DBE_DOWNLOAD_REQUEST = 1; // OCID download request from "APPLICATION" drawer title
    public static final char DBE_DOWNLOAD_REQUEST_FROM_MAP = 2; // OCID download request from "Antenna Map Viewer"
    public static final char DBE_UPLOAD_REQUEST = 6; // OCID upload request from "APPLICATION" drawer title
    public static final char CELL_LOOKUP = 5; // TODO: "All Current Cell Details (ALL_CURRENT_CELL_DETAILS)"

    @Inject
    private Logger log;

    private RealmHelper mDbAdapter;
    private Context mAppContext;
    private char mType;
    private int mTimeOut;

    private AsyncTaskCompleteListener mListener;

    @Inject
    private OkHttpClient okHttpClient;

    /**
     *
     * @param context App context
     * @param type What type of request to be performed (download OCID, upload OCID, etc.)
     * @param listener Allows the caller of RequestTask to implement success/fail callbacks
     */
    public RequestTask(InjectionAppCompatActivity context, char type, AsyncTaskCompleteListener listener) {
        super(context);
        this.mType = type;
        this.mAppContext = context.getApplicationContext();
        this.mDbAdapter = new RealmHelper(mAppContext);
        this.mTimeOut = REQUEST_TIMEOUT_MAPS;
        this.mListener = listener;
    }

    /**
     * @deprecated Use {@link #RequestTask(InjectionAppCompatActivity, char, AsyncTaskCompleteListener)}
     *             instead because of the listener callback interface.
     * @param context
     * @param type
     */
    @Deprecated
    public RequestTask(InjectionAppCompatActivity context, char type) {
        this(context, type, null);
        log.warn(
                "RequestTask(InjectionAppCompatActivity, char) is deprecated in favour of using listener callbacks");
    }

    @Override
    protected String doInBackground(String... commandString) {

        // We need to create a separate case for UPLOADING to DBe (OCID, MLS etc)
        switch (mType) {
        // OCID upload request from "APPLICATION" drawer title
        case DBE_UPLOAD_REQUEST:
            try {
                @Cleanup
                Realm realm = Realm.getDefaultInstance();
                boolean prepared = mDbAdapter.prepareOpenCellUploadData(realm);

                log.info("OCID upload data prepared - " + String.valueOf(prepared));
                if (prepared) {
                    File file = new File((mAppContext.getExternalFilesDir(null) + File.separator)
                            + "OpenCellID/aimsicd-ocid-data.csv");
                    publishProgress(25, 100);

                    RequestBody requestBody = new MultipartBuilder().type(MultipartBuilder.FORM)
                            .addFormDataPart("key", CellTracker.OCID_API_KEY).addFormDataPart("datafile",
                                    "aimsicd-ocid-data.csv", RequestBody.create(MediaType.parse("text/csv"), file))
                            .build();

                    Request request = new Request.Builder().url("http://www.opencellid.org/measure/uploadCsv")
                            .post(requestBody).build();

                    publishProgress(60, 100);

                    Response response = okHttpClient.newCall(request).execute();

                    publishProgress(80, 100);
                    if (response != null) {
                        log.info("OCID Upload Response: " + response.code() + " - " + response.message());
                        if (response.code() == 200) {
                            Realm.Transaction transaction = mDbAdapter.ocidProcessed();
                            realm.executeTransaction(transaction);
                        }
                        publishProgress(95, 100);
                    }
                    return "Successful";
                } else {
                    Helpers.msgLong(mAppContext, mAppContext.getString(R.string.no_data_for_publishing));
                    return null;
                }

                // all caused by httpclient.execute(httppost);
            } catch (UnsupportedEncodingException e) {
                log.error("Upload OpenCellID data Exception", e);
            } catch (FileNotFoundException e) {
                log.error("Upload OpenCellID data Exception", e);
            } catch (IOException e) {
                log.error("Upload OpenCellID data Exception", e);
            } catch (Exception e) {
                log.error("Upload OpenCellID data Exception", e);
            }

            // DOWNLOADING...
        case DBE_DOWNLOAD_REQUEST: // OCID download request from "APPLICATION" drawer title
            mTimeOut = REQUEST_TIMEOUT_MENU;
        case DBE_DOWNLOAD_REQUEST_FROM_MAP: // OCID download request from "Antenna Map Viewer"
            int count;
            try {
                long total;
                int progress = 0;
                String dirName = getOCDBDownloadDirectoryPath(mAppContext);
                File dir = new File(dirName);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                File file = new File(dir, OCDB_File_Name);
                log.info("DBE_DOWNLOAD_REQUEST write to: " + dirName + OCDB_File_Name);

                Request request = new Request.Builder().url(commandString[0]).get().build();

                Response response;
                try {
                    // OCID's API can be slow. Give it up to a minute to do its job. Since this
                    // is a backgrounded task, it's ok to wait for a while.
                    okHttpClient.setReadTimeout(60, TimeUnit.SECONDS);
                    response = okHttpClient.newCall(request).execute();
                    okHttpClient.setReadTimeout(10, TimeUnit.SECONDS); // Restore back to default
                } catch (SocketTimeoutException e) {
                    log.warn("Trying to talk to OCID timed out after 60 seconds. API is slammed? Throttled?");
                    return "Timeout";
                }

                if (response.code() != 200) {
                    try {
                        String error = response.body().string();
                        Helpers.msgLong(mAppContext, mAppContext.getString(R.string.download_error) + " " + error);
                        log.error("Download OCID data error: " + error);
                    } catch (Exception e) {
                        Helpers.msgLong(mAppContext, mAppContext.getString(R.string.download_error) + " "
                                + e.getClass().getName() + " - " + e.getMessage());
                        log.error("Download OCID exception: ", e);
                    }
                    return "Error";
                } else {
                    // This returns "-1" for streamed response (Chunked Transfer Encoding)
                    total = response.body().contentLength();
                    if (total == -1) {
                        log.debug("doInBackground DBE_DOWNLOAD_REQUEST total not returned!");
                        total = 1024; // Let's set it arbitrarily to something other than "-1"
                    } else {
                        log.debug("doInBackground DBE_DOWNLOAD_REQUEST total: " + total);
                        publishProgress((int) (0.25 * total), (int) total); // Let's show something!
                    }

                    FileOutputStream output = new FileOutputStream(file, false);
                    InputStream input = new BufferedInputStream(response.body().byteStream());

                    byte[] data = new byte[1024];
                    while ((count = input.read(data)) > 0) {
                        // writing data to file
                        output.write(data, 0, count);
                        progress += count;
                        publishProgress(progress, (int) total);
                    }
                    input.close();
                    // flushing output
                    output.flush();
                    output.close();
                }
                return "Successful";

            } catch (IOException e) {
                log.warn("Problem reading data from steam", e);
                return null;
            }
        }

        return null;
    }

    /**
     * This is where we:
     * <ol>
     * <li>Check the success for OCID data download</li>
     * <li>call the updateOpenCellID() to populate the {@link com.secupwn.aimsicd.data.model.Import Import} realm</li>
     * <li>call the {@link RealmHelper#checkDBe()} to cleanup bad cells from imported data</li>
     * <li>present a failure/success toast message</li>
     * <li>set a shared preference to indicate that data has been downloaded:
     * {@code ocid_downloaded true}</li>
     * </ol>
     */
    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);
        TinyDB tinydb = TinyDB.getInstance();

        @Cleanup
        Realm realm = Realm.getDefaultInstance();

        switch (mType) {
        case DBE_DOWNLOAD_REQUEST:
            // if `result` is null, it will evaluate to false, no need to check for null
            if ("Successful".equals(result)) {

                if (mDbAdapter.populateDBeImport(realm)) {
                    Helpers.msgShort(mAppContext,
                            mAppContext.getString(R.string.opencellid_data_successfully_received));
                }

                realm.executeTransaction(mDbAdapter.checkDBe());
                tinydb.putBoolean("ocid_downloaded", true);
            } else if ("Timeout".equals(result)) {
                Helpers.msgLong(mAppContext, mAppContext.getString(R.string.download_timed_out));
            } else {
                Helpers.msgLong(mAppContext, mAppContext.getString(R.string.error_retrieving_opencellid_data));
            }
            break;

        case DBE_DOWNLOAD_REQUEST_FROM_MAP:
            if ("Successful".equals(result)) {
                if (mDbAdapter.populateDBeImport(realm)) {
                    Intent intent = new Intent(MapFragment.updateOpenCellIDMarkers);
                    LocalBroadcastManager.getInstance(mAppContext).sendBroadcast(intent);
                    Helpers.msgShort(mAppContext,
                            mAppContext.getString(R.string.opencellid_data_successfully_received_markers_updated));

                    realm.executeTransaction(mDbAdapter.checkDBe());
                    tinydb.putBoolean("ocid_downloaded", true);
                }
            } else if ("Timeout".equals(result)) {
                Helpers.msgLong(mAppContext, mAppContext.getString(R.string.download_timed_out));
            } else {
                Helpers.msgLong(mAppContext, mAppContext.getString(R.string.error_retrieving_opencellid_data));
            }
            showHideMapProgressBar(false);
            TinyDB.getInstance().putBoolean(TinyDbKeys.FINISHED_LOAD_IN_MAP, true);
            break;

        case DBE_UPLOAD_REQUEST:
            if ("Successful".equals(result)) {
                Helpers.msgShort(mAppContext, mAppContext.getString(R.string.uploaded_bts_data_successfully));
            } else {
                Helpers.msgLong(mAppContext, mAppContext.getString(R.string.error_uploading_bts_data));
            }
            break;
        }
        if (mListener != null) {
            if ("Successful".equals(result)) {
                mListener.onAsyncTaskSucceeded();
            } else {
                mListener.onAsyncTaskFailed(result);
            }
        }
    }

    @Override
    protected void onActivityDetached() {
        if (mType == DBE_DOWNLOAD_REQUEST_FROM_MAP) {
            showHideMapProgressBar(false);
        }
    }

    @Override
    protected void onActivityAttached() {
        if (mType == DBE_DOWNLOAD_REQUEST_FROM_MAP) {
            showHideMapProgressBar(true);
        }
    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
        if (mType == DBE_DOWNLOAD_REQUEST_FROM_MAP) {
            showHideMapProgressBar(false);
        }
    }

    private void showHideMapProgressBar(boolean pFlag) {
        InjectionAppCompatActivity lActivity = getActivity();
        if (BuildConfig.DEBUG && lActivity == null) {
            log.verbose("BaseTask showHideMapProgressBar() activity is null");
        }

        if (lActivity != null) {
            Fragment myFragment = lActivity.getSupportFragmentManager()
                    .findFragmentByTag(String.valueOf(DrawerMenu.ID.MAIN.ALL_CURRENT_CELL_DETAILS));
            if (myFragment instanceof MapFragment) {
                ((MapFragment) myFragment).setRefreshActionButtonState(pFlag);
            }
        }
    }

    public static final String OCDB_File_Name = "opencellid.csv";

    /**
     * The folder path to OCDB download.
     * @param context
     * @return
     */
    public static String getOCDBDownloadDirectoryPath(Context context) {
        return (context.getExternalFilesDir(null) + File.separator) + "OpenCellID/";
    }

    /**
     * The interface to be implemented by the caller of RequestTask so it can perform contextual
     * actions once the async task is completed.
     *
     * E.g. rechecking current cell in the newly updated database after OCID download.
     */
    public interface AsyncTaskCompleteListener {
        void onAsyncTaskSucceeded();

        void onAsyncTaskFailed(String result);
    }
}