info.zamojski.soft.towercollector.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for info.zamojski.soft.towercollector.MainActivity.java

Source

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package info.zamojski.soft.towercollector;

import info.zamojski.soft.towercollector.analytics.IntentSource;
import info.zamojski.soft.towercollector.enums.FileType;
import info.zamojski.soft.towercollector.enums.MeansOfTransport;
import info.zamojski.soft.towercollector.enums.Validity;
import info.zamojski.soft.towercollector.events.CollectorStartedEvent;
import info.zamojski.soft.towercollector.events.PrintMainWindowEvent;
import info.zamojski.soft.towercollector.events.SystemTimeChangedEvent;
import info.zamojski.soft.towercollector.controls.DialogManager;
import info.zamojski.soft.towercollector.dao.MeasurementsDatabase;
import info.zamojski.soft.towercollector.model.ChangelogInfo;
import info.zamojski.soft.towercollector.model.UpdateInfo;
import info.zamojski.soft.towercollector.model.UpdateInfo.DownloadLink;
import info.zamojski.soft.towercollector.providers.ChangelogProvider;
import info.zamojski.soft.towercollector.providers.HtmlChangelogFormatter;
import info.zamojski.soft.towercollector.tasks.ExportFileAsyncTask;
import info.zamojski.soft.towercollector.tasks.UpdateCheckAsyncTask;
import info.zamojski.soft.towercollector.utils.ApkUtils;
import info.zamojski.soft.towercollector.utils.BackgroundTaskHelper;
import info.zamojski.soft.towercollector.utils.DateUtils;
import info.zamojski.soft.towercollector.utils.FileUtils;
import info.zamojski.soft.towercollector.utils.GpsUtils;
import info.zamojski.soft.towercollector.utils.NetworkUtils;
import info.zamojski.soft.towercollector.utils.PermissionUtils;
import info.zamojski.soft.towercollector.utils.StorageUtils;
import info.zamojski.soft.towercollector.utils.UpdateDialogArrayAdapter;
import info.zamojski.soft.towercollector.utils.StringUtils;
import info.zamojski.soft.towercollector.utils.Validator;
import info.zamojski.soft.towercollector.views.MainActivityPagerAdapter;

import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import org.acra.ACRA;

import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources.NotFoundException;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.provider.Settings;
import android.support.annotation.StringRes;
import android.support.design.widget.TabLayout;
import android.support.design.widget.TabLayout.Tab;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.telephony.TelephonyManager;

import permissions.dispatcher.NeedsPermission;
import permissions.dispatcher.OnNeverAskAgain;
import permissions.dispatcher.OnPermissionDenied;
import permissions.dispatcher.OnShowRationale;
import permissions.dispatcher.PermissionRequest;
import permissions.dispatcher.RuntimePermissions;
import trikita.log.Log;

import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

@RuntimePermissions
public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    private AtomicBoolean isCollectorServiceRunning = new AtomicBoolean(false);
    private boolean isGpsEnabled = false;
    private boolean showAskForLocationSettingsDialog = false;
    private boolean showNotCompatibleDialog = true;

    private String exportedFileAbsolutePath;
    private boolean showExportFinishedDialog = false;

    private Boolean canStartNetworkTypeSystemActivityResult = null;

    private Menu mainMenu;
    private MenuItem startMenu;
    private MenuItem stopMenu;
    private MenuItem networkTypeMenu;

    private boolean isMinimized = false;

    public ICollectorService collectorServiceBinder;

    private BackgroundTaskHelper backgroundTaskHelper;

    private MainActivityPagerAdapter pageAdapter;
    private ViewPager viewPager;

    // ========== ACTIVITY ========== //

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setTheme(MyApplication.getCurrentAppTheme());
        super.onCreate(savedInstanceState);
        Log.d("onCreate(): Creating activity");
        // set fixed screen orientation
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        setContentView(R.layout.main);
        //setup toolbar
        Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
        setSupportActionBar(toolbar);
        // setup tabbed layout
        pageAdapter = new MainActivityPagerAdapter(getSupportFragmentManager(), getApplication());
        viewPager = (ViewPager) findViewById(R.id.main_pager);
        viewPager.setAdapter(pageAdapter);
        TabLayout tabLayout = (TabLayout) findViewById(R.id.main_tab_layout);
        tabLayout.setupWithViewPager(viewPager);

        tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(Tab tab) {
                Log.d("onTabSelected() Switching to tab %s", tab.getPosition());
                // switch to page when tab is selected
                viewPager.setCurrentItem(tab.getPosition());
            }

            @Override
            public void onTabUnselected(Tab tab) {
                // nothing
            }

            @Override
            public void onTabReselected(Tab tab) {
                // nothing
            }
        });

        backgroundTaskHelper = new BackgroundTaskHelper(this);

        displayNotCompatibleDialog();

        // show latest developer's messages
        displayDevelopersMessages();

        // show introduction
        displayIntroduction();

        processOnStartIntent(getIntent());

        // check for availability of new version
        checkForNewVersionAvailability();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d("onDestroy(): Unbinding from service");
        if (isCollectorServiceRunning.get())
            unbindService(collectorServiceConnection);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d("onStart(): Binding to service");
        isCollectorServiceRunning.set(isServiceRunning(CollectorService.SERVICE_FULL_NAME));
        if (isCollectorServiceRunning.get()) {
            bindService(new Intent(this, CollectorService.class), collectorServiceConnection, 0);
        }
        if (isMinimized && showExportFinishedDialog) {
            displayExportFinishedDialog();
        }
        isMinimized = false;
        EventBus.getDefault().register(this);
        MyApplication.getAnalytics().sendMainActivityStarted();
    }

    @Override
    protected void onStop() {
        super.onStop();
        isMinimized = true;
        EventBus.getDefault().unregister(this);
        MyApplication.getAnalytics().sendMainActivityStopped();
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d("onResume(): Resuming");
        // print on UI
        EventBus.getDefault().post(new PrintMainWindowEvent());
        // restore recent tab
        int recentTabIndex = MyApplication.getPreferencesProvider().getMainWindowRecentTab();
        viewPager.setCurrentItem(recentTabIndex);
        // if coming back from Android settings rerun the action
        if (showAskForLocationSettingsDialog) {
            startCollectorServiceWithCheck();
        }
        // if keep on is checked
        if (MyApplication.getPreferencesProvider().getMainKeepScreenOnMode()) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        } else {
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d("onPause(): Pausing");
        // remember current tab
        int currentTabIndex = viewPager.getCurrentItem();
        MyApplication.getPreferencesProvider().setMainWindowRecentTab(currentTabIndex);
    }

    // ========== MENU ========== //

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        Log.d("onCreateOptionsMenu(): Loading action bar");
        getMenuInflater().inflate(R.menu.main, menu);
        // save references
        startMenu = menu.findItem(R.id.main_menu_start);
        stopMenu = menu.findItem(R.id.main_menu_stop);
        networkTypeMenu = menu.findItem(R.id.main_menu_network_type);
        mainMenu = menu;// store the menu in an local variable for hardware key
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        boolean isRunning = isCollectorServiceRunning.get();
        Log.d("onPrepareOptionsMenu(): Preparing action bar menu for running = %s", isRunning);
        // toggle visibility
        startMenu.setVisible(!isRunning);
        stopMenu.setVisible(isRunning);
        boolean networkTypeAvailable = canStartNetworkTypeSystemActivity();
        networkTypeMenu.setVisible(networkTypeAvailable);
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // start action
        switch (item.getItemId()) {
        case R.id.main_menu_start:
            startCollectorServiceWithCheck();
            return true;
        case R.id.main_menu_stop:
            stopCollectorService();
            return true;
        case R.id.main_menu_upload:
            startUploaderServiceWithCheck();
            return true;
        case R.id.main_menu_export:
            // NOTE: delegate the permission handling to generated method
            MainActivityPermissionsDispatcher.startExportAsyncTaskWithCheck(this);
            return true;
        case R.id.main_menu_preferences:
            startPreferencesActivity();
            return true;
        case R.id.main_menu_network_type:
            startNetworkTypeSystemActivity();
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onNewIntent(Intent intent) {
        Log.d("onNewIntent(): New intent received: %s", intent);
        processOnStartIntent(intent);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_MENU) {
            if (event.getAction() == KeyEvent.ACTION_UP && mainMenu != null) {
                Log.i("onKeyUp(): Hardware menu key pressed");
                mainMenu.performIdentifierAction(R.id.main_menu_more, 0);
                return true;
            }
        }
        return super.onKeyUp(keyCode, event);
    }

    // ========== UI ========== //

    private void printInvalidSystemTime(ICollectorService collectorServiceBinder) {
        Validity valid = collectorServiceBinder.isSystemTimeValid();
        EventBus.getDefault().postSticky(new SystemTimeChangedEvent(valid));
    }

    private void hideInvalidSystemTime() {
        EventBus.getDefault().postSticky(new SystemTimeChangedEvent(Validity.Valid));
    }

    // have to be public to prevent Force Close
    public void displayHelpOnClick(View view) {
        int titleId = View.NO_ID;
        int messageId = View.NO_ID;
        switch (view.getId()) {
        case R.id.main_gps_status_tablerow:
            titleId = R.string.main_help_gps_status_title;
            messageId = R.string.main_help_gps_status_description;
            break;
        case R.id.main_invalid_system_time_tablerow:
            titleId = R.string.main_help_invalid_system_time_title;
            messageId = R.string.main_help_invalid_system_time_description;
            break;
        case R.id.main_stats_today_locations_tablerow:
            titleId = R.string.main_help_today_locations_title;
            messageId = R.string.main_help_today_locations_description;
            break;
        case R.id.main_stats_today_cells_tablerow:
            titleId = R.string.main_help_today_cells_title;
            messageId = R.string.main_help_today_cells_description;
            break;
        case R.id.main_stats_local_locations_tablerow:
            titleId = R.string.main_help_local_locations_title;
            messageId = R.string.main_help_local_locations_description;
            break;
        case R.id.main_stats_local_cells_tablerow:
            titleId = R.string.main_help_local_cells_title;
            messageId = R.string.main_help_local_cells_description;
            break;
        case R.id.main_stats_global_locations_tablerow:
            titleId = R.string.main_help_global_locations_title;
            messageId = R.string.main_help_global_locations_description;
            break;
        case R.id.main_stats_global_cells_tablerow:
            titleId = R.string.main_help_global_cells_title;
            messageId = R.string.main_help_global_cells_description;
            break;
        case R.id.main_last_network_type_tablerow:
            titleId = R.string.main_help_last_network_type_title;
            messageId = R.string.main_help_last_network_type_description;
            break;
        case R.id.main_last_cell_id_tablerow:
            titleId = R.string.main_help_last_cell_id_title;
            messageId = R.string.main_help_last_cell_id_description;
            break;
        case R.id.main_last_lac_tablerow:
            titleId = R.string.main_help_last_lac_title;
            messageId = R.string.main_help_last_lac_description;
            break;
        case R.id.main_last_mcc_tablerow:
            titleId = R.string.main_help_last_mcc_title;
            messageId = R.string.main_help_last_mcc_description;
            break;
        case R.id.main_last_mnc_tablerow:
            titleId = R.string.main_help_last_mnc_title;
            messageId = R.string.main_help_last_mnc_description;
            break;
        case R.id.main_last_signal_strength_tablerow:
            titleId = R.string.main_help_last_signal_strength_title;
            messageId = R.string.main_help_last_signal_strength_description;
            break;
        case R.id.main_last_latitude_tablerow:
            titleId = R.string.main_help_last_latitude_title;
            messageId = R.string.main_help_last_latitude_description;
            break;
        case R.id.main_last_longitude_tablerow:
            titleId = R.string.main_help_last_longitude_title;
            messageId = R.string.main_help_last_longitude_description;
            break;
        case R.id.main_last_gps_accuracy_tablerow:
            titleId = R.string.main_help_last_gps_accuracy_title;
            messageId = R.string.main_help_last_gps_accuracy_description;
            break;
        case R.id.main_last_date_time_tablerow:
            titleId = R.string.main_help_last_date_time_title;
            messageId = R.string.main_help_last_date_time_description;
            break;
        }
        Log.d("displayHelpOnClick(): Displaying help for title: %s", titleId);
        if (titleId != View.NO_ID && messageId != View.NO_ID) {
            AlertDialog dialog = new AlertDialog.Builder(this).setTitle(titleId).setMessage(messageId)
                    .setPositiveButton(R.string.dialog_ok, null).create();
            dialog.setCanceledOnTouchOutside(true);
            dialog.setCancelable(true);
            dialog.show();
        }
    }

    private void displayUploadResultDialog(Bundle extras) {
        int descriptionId = extras.getInt(UploaderService.INTENT_KEY_RESULT_DESCRIPTION);
        try {
            String descriptionContent = getString(descriptionId);
            Log.d("displayUploadResultDialog(): Received extras: %s", descriptionId);
            // display dialog
            AlertDialog alertDialog = new AlertDialog.Builder(this).create();
            alertDialog.setCanceledOnTouchOutside(true);
            alertDialog.setCancelable(true);
            alertDialog.setTitle(R.string.uploader_result_dialog_title);
            alertDialog.setMessage(descriptionContent);
            alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.dialog_ok),
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                        }
                    });
            alertDialog.show();
        } catch (NotFoundException ex) {
            Log.w("displayUploadResultDialog(): Invalid string id received with intent extras: %s", descriptionId);
            MyApplication.getAnalytics().sendException(ex, Boolean.FALSE);
            ACRA.getErrorReporter().handleSilentException(ex);
        }
    }

    private void displayNewVersionDownloadOptions(Bundle extras) {
        UpdateInfo updateInfo = (UpdateInfo) extras.getSerializable(UpdateCheckAsyncTask.INTENT_KEY_UPDATE_INFO);
        // display dialog
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
        LayoutInflater inflater = LayoutInflater.from(this);
        View dialogLayout = inflater.inflate(R.layout.new_version, null);
        dialogBuilder.setView(dialogLayout);
        final AlertDialog alertDialog = dialogBuilder.create();
        alertDialog.setCanceledOnTouchOutside(true);
        alertDialog.setCancelable(true);
        alertDialog.setTitle(R.string.updater_dialog_new_version_available);
        // load data
        ArrayAdapter<UpdateInfo.DownloadLink> adapter = new UpdateDialogArrayAdapter(alertDialog.getContext(),
                inflater, updateInfo.getDownloadLinks());
        ListView listView = (ListView) dialogLayout.findViewById(R.id.download_options_list);
        listView.setAdapter(adapter);
        // bind events
        final CheckBox disableAutoUpdateCheckCheckbox = (CheckBox) dialogLayout
                .findViewById(R.id.download_options_disable_auto_update_check_checkbox);
        listView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                DownloadLink downloadLink = (DownloadLink) parent.getItemAtPosition(position);
                Log.d("displayNewVersionDownloadOptions(): Selected position: %s", downloadLink.getLabel());
                boolean disableAutoUpdateCheckCheckboxChecked = disableAutoUpdateCheckCheckbox.isChecked();
                Log.d("displayNewVersionDownloadOptions(): Disable update check checkbox checked = %s",
                        disableAutoUpdateCheckCheckboxChecked);
                if (disableAutoUpdateCheckCheckboxChecked) {
                    MyApplication.getPreferencesProvider().setUpdateCheckEnabled(false);
                }
                MyApplication.getAnalytics().sendUpdateAction(downloadLink.getLabel());
                String link = downloadLink.getLink();
                try {
                    startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(link)));
                } catch (ActivityNotFoundException ex) {
                    Toast.makeText(getApplication(), R.string.web_browser_missing, Toast.LENGTH_LONG).show();
                }
                alertDialog.dismiss();
            }
        });
        alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.dialog_cancel),
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        boolean disableAutoUpdateCheckCheckboxChecked = disableAutoUpdateCheckCheckbox.isChecked();
                        Log.d("displayNewVersionDownloadOptions(): Disable update check checkbox checked = %s",
                                disableAutoUpdateCheckCheckboxChecked);
                        if (disableAutoUpdateCheckCheckboxChecked) {
                            MyApplication.getPreferencesProvider().setUpdateCheckEnabled(false);
                        }
                    }
                });
        alertDialog.show();
    }

    private void displayNotCompatibleDialog() {
        // check if displayed in this app run
        if (showNotCompatibleDialog) {
            // check if not disabled in preferences
            boolean showCompatibilityWarningEnabled = MyApplication.getPreferencesProvider()
                    .getShowCompatibilityWarning();
            if (showCompatibilityWarningEnabled) {
                TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
                // check if device contains telephony hardware (some tablets doesn't report even if have)
                // NOTE: in the future this may need to be expanded when new specific features appear
                PackageManager packageManager = getPackageManager();
                boolean noRadioDetected = !(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
                        && (packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_GSM)
                                || packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA)));
                // show dialog if something is not supported
                if (noRadioDetected) {
                    Log.d("displayNotCompatibleDialog(): Not compatible because of radio: %s, phone type: %s",
                            noRadioDetected, telephonyManager.getPhoneType());
                    //use custom layout to show "don't show this again" checkbox
                    AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
                    LayoutInflater inflater = LayoutInflater.from(this);
                    View dialogLayout = inflater.inflate(R.layout.dont_show_again_dialog, null);
                    final CheckBox dontShowAgainCheckbox = (CheckBox) dialogLayout
                            .findViewById(R.id.dont_show_again_dialog_checkbox);
                    dialogBuilder.setView(dialogLayout);
                    AlertDialog alertDialog = dialogBuilder.create();
                    alertDialog.setCanceledOnTouchOutside(true);
                    alertDialog.setCancelable(true);
                    alertDialog.setTitle(R.string.main_dialog_not_compatible_title);
                    StringBuilder stringBuilder = new StringBuilder(
                            getString(R.string.main_dialog_not_compatible_begin));
                    if (noRadioDetected) {
                        stringBuilder.append(getString(R.string.main_dialog_no_compatible_mobile_radio_message));
                    }
                    // text set this way to prevent checkbox from disappearing when text is too long
                    TextView messageTextView = (TextView) dialogLayout
                            .findViewById(R.id.dont_show_again_dialog_textview);
                    messageTextView.setText(stringBuilder.toString());
                    alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.dialog_ok),
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int which) {
                                    boolean dontShowAgainCheckboxChecked = dontShowAgainCheckbox.isChecked();
                                    Log.d("displayNotCompatibleDialog(): Don't show again checkbox checked = %s",
                                            dontShowAgainCheckboxChecked);
                                    if (dontShowAgainCheckboxChecked) {
                                        MyApplication.getPreferencesProvider().setShowCompatibilityWarning(false);
                                    }
                                }
                            });
                    alertDialog.show();
                }
            }
            showNotCompatibleDialog = false;
        }
    }

    private boolean displayInAirplaneModeDialog() {
        // check if in airplane mode
        boolean inAirplaneMode = NetworkUtils.isInAirplaneMode(getApplication());
        if (inAirplaneMode) {
            Log.d("displayInAirplaneModeDialog(): Device is in airplane mode");
            AlertDialog alertDialog = new AlertDialog.Builder(this).create();
            alertDialog.setCanceledOnTouchOutside(true);
            alertDialog.setCancelable(true);
            alertDialog.setTitle(R.string.main_dialog_in_airplane_mode_title);
            alertDialog.setMessage(getString(R.string.main_dialog_in_airplane_mode_message));
            alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.dialog_ok),
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                        }
                    });
            alertDialog.show();
        }
        return inAirplaneMode;
    }

    private void displayIntroduction() {
        if (MyApplication.getPreferencesProvider().getShowIntroduction()) {
            Log.d("displayIntroduction(): Showing introduction");
            DialogManager.createHtmlInfoDialog(this, R.string.info_introduction_title,
                    R.raw.info_introduction_content, false, false).show();
            MyApplication.getPreferencesProvider().setShowIntroduction(false);
        }
    }

    private void displayDevelopersMessages() {
        int previousVersionCode = MyApplication.getPreferencesProvider().getPreviousDeveloperMessagesVersion();
        int currentVersionCode = ApkUtils.getApkVersionCode(getApplication());
        if (previousVersionCode != currentVersionCode) {
            MyApplication.getPreferencesProvider().setRecentDeveloperMessagesVersion(currentVersionCode);
            // skip recent changes for first startup
            if (MyApplication.getPreferencesProvider().getShowIntroduction()) {
                return;
            }
            Log.d("displayDevelopersMessages(): Showing changelog between %s and %s", previousVersionCode,
                    currentVersionCode);
            ChangelogProvider provider = new ChangelogProvider(getApplication(), R.raw.changelog);
            ChangelogInfo changelog = provider.getChangelog(previousVersionCode);
            HtmlChangelogFormatter formatter = new HtmlChangelogFormatter();
            String message = formatter.formatChangelog(changelog);
            DialogManager.createHtmlInfoDialog(this, R.string.dialog_what_is_new, message, false, false).show();
        }
    }

    public void displayExportFinishedDialog() {
        showExportFinishedDialog = false;
        // show dialog
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setTitle(R.string.export_dialog_finished_title);
        builder.setMessage(getString(R.string.export_dialog_finished_message, exportedFileAbsolutePath));
        builder.setPositiveButton(R.string.dialog_keep, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // cancel
                MyApplication.getAnalytics().sendExportKeepAction();
            }
        });
        builder.setNeutralButton(R.string.dialog_upload, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                MyApplication.getAnalytics().sendExportUploadAction();
                startUploaderServiceWithCheck();
            }
        });
        builder.setNegativeButton(R.string.dialog_delete, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                MeasurementsDatabase.getInstance(MainActivity.this).deleteAllMeasurements();
                EventBus.getDefault().post(new PrintMainWindowEvent());
                MyApplication.getAnalytics().sendExportDeleteAction();
            }
        });
        AlertDialog dialog = builder.create();
        dialog.setCanceledOnTouchOutside(false);
        dialog.setCancelable(false);
        dialog.show();
        exportedFileAbsolutePath = null;
    }

    // ========== MENU START/STOP METHODS ========== //

    private void startCollectorServiceWithCheck() {
        String runningTaskClassName = MyApplication.getBackgroundTaskName();
        if (runningTaskClassName != null) {
            Log.d("startCollectorServiceWithCheck(): Another task is running in background: %s",
                    runningTaskClassName);
            backgroundTaskHelper.showTaskRunningMessage(runningTaskClassName);
            return;
        }
        askAndSetGpsEnabled();
        if (isGpsEnabled) {
            // checking for airplane mode
            boolean inAirplaneMode = displayInAirplaneModeDialog();
            if (!inAirplaneMode) {
                MainActivityPermissionsDispatcher.startCollectorServiceWithCheck(MainActivity.this);
            }
        }
    }

    @NeedsPermission({ Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.READ_PHONE_STATE })
    void startCollectorService() {
        Log.d("startCollectorService(): Air plane mode off, starting service");
        // create intent
        final Intent intent = new Intent(this, CollectorService.class);
        // pass means of transport inside intent
        boolean gpsOptimizationsEnabled = MyApplication.getPreferencesProvider().getGpsOptimizationsEnabled();
        MeansOfTransport selectedType = (gpsOptimizationsEnabled ? MeansOfTransport.Universal
                : MeansOfTransport.Fixed);
        intent.putExtra(CollectorService.INTENT_KEY_TRANSPORT_MODE, selectedType);
        // pass screen on mode
        final String keepScreenOnMode = MyApplication.getPreferencesProvider().getCollectorKeepScreenOnMode();
        intent.putExtra(CollectorService.INTENT_KEY_KEEP_SCREEN_ON_MODE, keepScreenOnMode);
        // start service
        startService(intent);
        EventBus.getDefault().post(new CollectorStartedEvent(intent));
        MyApplication.getAnalytics().sendCollectorStarted(IntentSource.User);
    }

    @OnShowRationale({ Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.READ_PHONE_STATE })
    void onStartCollectorShowRationale(PermissionRequest request) {
        onShowRationale(request, R.string.permission_collector_rationale_message);
    }

    @OnPermissionDenied({ Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.READ_PHONE_STATE })
    void onStartCollectorPermissionDenied() {
        onPermissionDenied(R.string.permission_collector_denied_message);
    }

    @OnNeverAskAgain({ Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.READ_PHONE_STATE })
    void onStartCollectorNeverAskAgain() {
        onNeverAskAgain(R.string.permission_collector_never_ask_again_message);
    }

    private void stopCollectorService() {
        stopService(new Intent(this, CollectorService.class));
    }

    private void startUploaderServiceWithCheck() {
        String runningTaskClassName = MyApplication.getBackgroundTaskName();
        if (runningTaskClassName != null) {
            Log.d("startUploaderService(): Another task is running in background: %s", runningTaskClassName);
            backgroundTaskHelper.showTaskRunningMessage(runningTaskClassName);
            return;
        }
        // check API key
        String apiKey = MyApplication.getPreferencesProvider().getApiKey();
        if (!Validator.isOpenCellIdApiKeyValid(apiKey)) {
            final String apiKeyLocal = apiKey;
            AlertDialog alertDialog = new AlertDialog.Builder(this).create();
            alertDialog.setCanceledOnTouchOutside(true);
            alertDialog.setCancelable(true);
            if (StringUtils.isNullEmptyOrWhitespace(apiKey)) {
                alertDialog.setTitle(R.string.main_dialog_api_key_empty_title);
                alertDialog.setMessage(getString(R.string.main_dialog_api_key_empty_message));
                alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.dialog_register),
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                try {
                                    Intent browserIntent = new Intent(Intent.ACTION_VIEW,
                                            Uri.parse(getString(R.string.preferences_opencellid_org_sign_up_link)));
                                    startActivity(browserIntent);
                                } catch (ActivityNotFoundException ex) {
                                    Toast.makeText(getApplication(), R.string.web_browser_missing,
                                            Toast.LENGTH_LONG).show();
                                }
                            }
                        });
            } else {
                alertDialog.setTitle(R.string.main_dialog_api_key_invalid_title);
                alertDialog.setMessage(getString(R.string.main_dialog_api_key_invalid_message));
                alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.dialog_upload),
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                startUploaderService(apiKeyLocal);
                            }
                        });
            }
            alertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, getString(R.string.dialog_enter_api_key),
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            startPreferencesActivity();
                        }
                    });
            alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.dialog_cancel),
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                        }
                    });
            alertDialog.show();
            return;
        } else {
            startUploaderService(apiKey);
        }
    }

    private void startUploaderService(String apiKey) {
        // start task
        if (!isServiceRunning(UploaderService.SERVICE_FULL_NAME)) {
            Intent intent = new Intent(MainActivity.this, UploaderService.class);
            intent.putExtra(UploaderService.INTENT_KEY_APIKEY, apiKey);
            startService(intent);
            MyApplication.getAnalytics().sendUploadStarted(IntentSource.User);
        } else
            Toast.makeText(getApplication(), R.string.uploader_already_running, Toast.LENGTH_LONG).show();
    }

    @NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    void startExportAsyncTask() {
        String runningTaskClassName = MyApplication.getBackgroundTaskName();
        if (runningTaskClassName != null) {
            Log.d("startExportAsyncTask(): Another task is running in background: %s", runningTaskClassName);
            backgroundTaskHelper.showTaskRunningMessage(runningTaskClassName);
            return;
        }
        if (StorageUtils.isExternalMemoryWritable()) {
            final Map<String, FileType> fileTypes = getFileTypes();
            final String[] fileTypeNames = fileTypes.keySet().toArray(new String[fileTypes.size()]);
            // show dialog that runs async task
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle(R.string.export_dialog_format_selection_title);
            builder.setItems(fileTypeNames, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int itemIndex) {
                    String selectedItem = fileTypeNames[itemIndex];
                    Log.d("onCreateDialog(): User selected position: %s", selectedItem);
                    // parse response
                    FileType selectedType = FileType.Unknown;
                    // pass selected means of transport
                    if (fileTypes.containsKey(selectedItem)) {
                        selectedType = fileTypes.get(selectedItem);
                    }
                    String extension;
                    switch (selectedType) {
                    case Csv:
                        extension = "csv";
                        break;
                    case Gpx:
                        extension = "gpx";
                        break;
                    default:
                        // cancel
                        extension = null;
                        break;
                    }
                    if (selectedType != FileType.Unknown) {
                        String path = FileUtils.combinePath(FileUtils.getExternalStorageAppDir(),
                                FileUtils.getCurrentDateFilename(extension));
                        ExportFileAsyncTask task = new ExportFileAsyncTask(MainActivity.this,
                                new InternalMessageHandler(MainActivity.this), path, selectedType);
                        task.execute(new Void[0]);
                        MyApplication.getAnalytics().sendExportStarted();
                    }
                }
            }).setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int id) {
                    // cancel
                }
            });
            AlertDialog dialog = builder.create();
            dialog.setCanceledOnTouchOutside(true);
            dialog.setCancelable(true);
            dialog.show();
            //ExportFormatSelectionDialogFragment dialog = new ExportFormatSelectionDialogFragment();
            //dialog.setContext(this);
            //dialog.setHandler(new InternalMessageHandler(this));
            //dialog.show(getSupportFragmentManager(), dialog.getClass().getSimpleName());
        } else if (StorageUtils.isExternalMemoryPresent()) {
            Toast.makeText(getApplication(), R.string.export_toast_storage_read_only, Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(getApplication(), R.string.export_toast_no_storage, Toast.LENGTH_LONG).show();
        }
    }

    @OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    void onStartExportShowRationale(PermissionRequest request) {
        onShowRationale(request, R.string.permission_export_rationale_message);
    }

    @OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    void onStartExportPermissionDenied() {
        onPermissionDenied(R.string.permission_export_denied_message);
    }

    @OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    void onStartExportNeverAskAgain() {
        onNeverAskAgain(R.string.permission_export_never_ask_again_message);
    }

    private void startPreferencesActivity() {
        startActivity(new Intent(this, PreferencesActivity.class));
    }

    private boolean canStartNetworkTypeSystemActivity() {
        if (canStartNetworkTypeSystemActivityResult == null) {
            canStartNetworkTypeSystemActivityResult = (createDataRoamingSettingsIntent()
                    .resolveActivity(getPackageManager()) != null);
        }
        return canStartNetworkTypeSystemActivityResult;
    }

    private void startNetworkTypeSystemActivity() {
        try {
            startActivity(createDataRoamingSettingsIntent());
        } catch (ActivityNotFoundException ex) {
            Log.w("askAndSetGpsEnabled(): Could not open Settings to change network type", ex);
            MyApplication.getAnalytics().sendException(ex, Boolean.FALSE);
            ACRA.getErrorReporter().handleSilentException(ex);
            AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this)
                    .setMessage(R.string.dialog_could_not_open_network_type_settings)
                    .setPositiveButton(R.string.dialog_ok, null).create();
            alertDialog.setCanceledOnTouchOutside(true);
            alertDialog.setCancelable(true);
            alertDialog.show();
        }
    }

    private Intent createDataRoamingSettingsIntent() {
        Intent intent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS);
        // Theoretically this is not needed starting from 4.0.1 but it sometimes fail
        // bug https://code.google.com/p/android/issues/detail?id=13368
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
            ComponentName componentName = new ComponentName("com.android.phone", "com.android.phone.Settings");
            intent.setComponent(componentName);
        }
        return intent;
    }

    // ========== SERVICE CONNECTIONS ========== //

    private ServiceConnection collectorServiceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            Log.d("onServiceConnected(): Service connection created for %s", name);
            isCollectorServiceRunning.set(true);
            // refresh menu status
            invalidateOptionsMenu();
            if (binder instanceof ICollectorService) {
                collectorServiceBinder = (ICollectorService) binder;
                // display invalid system time if necessary
                printInvalidSystemTime(collectorServiceBinder);
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("onServiceDisconnected(): Service connection destroyed of %s", name);
            unbindService(collectorServiceConnection);
            isCollectorServiceRunning.set(false);
            // refresh menu status
            invalidateOptionsMenu();
            // hide invalid system time message
            hideInvalidSystemTime();
            // release reference to service
            collectorServiceBinder = null;
        }
    };

    // ========== PERMISSION REQUEST HANDLING ========== //
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        // NOTE: delegate the permission handling to generated method
        MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
    }

    private void onShowRationale(final PermissionRequest request, @StringRes int messageResId) {
        new AlertDialog.Builder(this).setTitle(R.string.permission_required).setMessage(messageResId)
                .setCancelable(true)
                .setPositiveButton(R.string.dialog_proceed, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.proceed();
                    }
                }).setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.cancel();
                    }
                }).show();
    }

    private void onPermissionDenied(@StringRes int messageResId) {
        Toast.makeText(this, messageResId, Toast.LENGTH_LONG).show();
    }

    private void onNeverAskAgain(@StringRes int messageResId) {
        new AlertDialog.Builder(this).setTitle(R.string.permission_denied).setMessage(messageResId)
                .setCancelable(true)
                .setPositiveButton(R.string.dialog_permission_settings, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        PermissionUtils.openAppSettings(MainActivity.this);
                    }
                }).setNegativeButton(R.string.dialog_cancel, null).show();
    }

    // ========== MISCELLANEOUS ========== //

    private boolean isServiceRunning(String serviceClassName) {
        ActivityManager activityManager = (ActivityManager) getApplication()
                .getSystemService(Context.ACTIVITY_SERVICE);
        List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
        for (RunningServiceInfo runningServiceInfo : services) {
            if (runningServiceInfo.service.getClassName().equals(serviceClassName)) {
                return true;
            }
        }
        return false;
    }

    private void askAndSetGpsEnabled() {
        if (GpsUtils.isGpsEnabled(getApplication())) {
            Log.d("askAndSetGpsEnabled(): GPS enabled");
            isGpsEnabled = true;
            showAskForLocationSettingsDialog = false;
        } else {
            Log.d("askAndSetGpsEnabled(): GPS disabled, asking user");
            isGpsEnabled = false;
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage(R.string.dialog_want_enable_gps)
                    .setPositiveButton(R.string.dialog_yes, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            Log.d("askAndSetGpsEnabled(): display settings");
                            Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                            try {
                                startActivity(intent);
                                showAskForLocationSettingsDialog = true;
                            } catch (ActivityNotFoundException ex1) {
                                intent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
                                try {
                                    startActivity(intent);
                                    showAskForLocationSettingsDialog = true;
                                } catch (ActivityNotFoundException ex2) {
                                    Log.w("askAndSetGpsEnabled(): Could not open Settings to enable GPS");
                                    MyApplication.getAnalytics().sendException(ex2, Boolean.FALSE);
                                    ACRA.getErrorReporter().handleSilentException(ex2);
                                    AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this)
                                            .setMessage(R.string.dialog_could_not_open_gps_settings)
                                            .setPositiveButton(R.string.dialog_ok, null).create();
                                    alertDialog.setCanceledOnTouchOutside(true);
                                    alertDialog.setCancelable(true);
                                    alertDialog.show();
                                }
                            } finally {
                                dialog.dismiss();
                            }
                        }
                    }).setNegativeButton(R.string.dialog_no, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            Log.d("askAndSetGpsEnabled(): cancel");
                            dialog.cancel();
                            if (GpsUtils.isGpsEnabled(getApplication())) {
                                Log.d("askAndSetGpsEnabled(): provider enabled in the meantime");
                                startCollectorServiceWithCheck();
                            } else {
                                isGpsEnabled = false;
                                showAskForLocationSettingsDialog = false;
                            }
                        }
                    });
            AlertDialog dialog = builder.create();
            dialog.setCanceledOnTouchOutside(true);
            dialog.setCancelable(true);
            dialog.show();
        }
    }

    private void checkForNewVersionAvailability() {
        boolean isAutoUpdateEnabled = MyApplication.getPreferencesProvider().getUpdateCheckEnabled();
        if (isAutoUpdateEnabled) {
            long currentDate = DateUtils.getCurrentDateWithoutTime();
            long lastUpdateCheckDate = MyApplication.getPreferencesProvider().getLastUpdateCheckDate();
            long diffInDays = DateUtils.getTimeDiff(currentDate, lastUpdateCheckDate);
            Log.d("checkForNewVersionAvailability(): Last update check performed on: %s, diff to current in days: %s",
                    new Date(lastUpdateCheckDate), diffInDays);
            // if currently is at least one day after last check (day, not 24 hrs)
            if (diffInDays >= 1) {
                // check if network available
                if (NetworkUtils.isNetworkAvailable(getApplication())) {
                    int currentVersion = ApkUtils.getApkVersionCode(getApplication());
                    String updateFeedUrl = String.format(BuildConfig.UPDATE_CHECK_FEED_URI, currentVersion);
                    if (!StringUtils.isNullEmptyOrWhitespace(updateFeedUrl)) {
                        UpdateCheckAsyncTask updateCheckTask = new UpdateCheckAsyncTask(getApplication(),
                                currentVersion);
                        updateCheckTask.execute(updateFeedUrl);
                    }
                    MyApplication.getPreferencesProvider().setLastUpdateCheckDate(currentDate);
                } else {
                    Log.d("checkForNewVersionAvailability(): No active network connection");
                }
            }
        }
    }

    private void processOnStartIntent(Intent intent) {
        Bundle extras = intent.getExtras();
        if (extras != null) {
            if (extras.containsKey(UploaderService.INTENT_KEY_RESULT_DESCRIPTION)) {
                // display upload result
                displayUploadResultDialog(extras);
            } else if (extras.containsKey(UpdateCheckAsyncTask.INTENT_KEY_UPDATE_INFO)) {
                // display dialog with download options
                displayNewVersionDownloadOptions(extras);
            }
        }
    }

    private Map<String, FileType> getFileTypes() {
        Log.d("getFileTypes(): Loading file types");
        final String[] resLabels = getResources().getStringArray(R.array.export_formats_labels);
        final String[] resValues = getResources().getStringArray(R.array.export_formats_values);
        Map<String, FileType> fileTypes = new LinkedHashMap<String, FileType>();
        for (int i = 0; i < resLabels.length; i++) {
            String label = resLabels[i];
            String value = resValues[i];
            fileTypes.put(label, FileType.valueOf(value));
        }
        return fileTypes;
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEvent(CollectorStartedEvent event) {
        bindService(event.getIntent(), collectorServiceConnection, 0);
    }

    // ========== INNER OBJECTS ========== //

    public static class InternalMessageHandler extends Handler {
        public static final int EXPORT_FINISHED_UI_REFRESH = 0;

        private MainActivity mainActivity;

        public InternalMessageHandler(MainActivity mainActivity) {
            this.mainActivity = mainActivity;
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case EXPORT_FINISHED_UI_REFRESH:
                mainActivity.exportedFileAbsolutePath = msg.getData().getString(ExportFileAsyncTask.ABSOLUTE_PATH);
                if (!mainActivity.isMinimized)
                    mainActivity.displayExportFinishedDialog();
                else
                    mainActivity.showExportFinishedDialog = true;
                break;
            }
        }
    }
}