com.github.jthuraisamy.yellowusage.ui.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.github.jthuraisamy.yellowusage.ui.MainActivity.java

Source

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

package com.github.jthuraisamy.yellowusage.ui;

import android.annotation.SuppressLint;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.PorterDuff;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.CardView;
import android.support.v7.widget.Toolbar;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;

import com.github.jthuraisamy.yellowusage.Constants;
import com.github.jthuraisamy.yellowusage.R;
import com.github.jthuraisamy.yellowusage.models.Balance;
import com.github.jthuraisamy.yellowusage.models.usage.data.DataUsage;
import com.github.jthuraisamy.yellowusage.models.usage.messaging.TextMessageUsage;
import com.github.jthuraisamy.yellowusage.models.usage.voice.PeakTimeVoiceUsage;
import com.github.jthuraisamy.yellowusage.parsers.BalanceParser;
import com.github.jthuraisamy.yellowusage.parsers.DataParser;
import com.github.jthuraisamy.yellowusage.parsers.MessageParser;
import com.github.jthuraisamy.yellowusage.parsers.VoiceParser;
import com.github.jthuraisamy.yellowusage.utils.OverageFeesHelper;
import com.github.jthuraisamy.yellowusage.utils.PreferenceHelper;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import com.sothree.slidinguppanel.SlidingUpPanelLayout.PanelSlideListener;
import com.sothree.slidinguppanel.SlidingUpPanelLayout.PanelState;

import java.io.IOException;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.util.LinkedList;
import java.util.Queue;

import io.realm.Realm;
import io.realm.RealmChangeListener;

import static com.github.jthuraisamy.yellowusage.AccountInfoService.startActionRefreshUI;
import static com.github.jthuraisamy.yellowusage.parsers.DataParser.humanReadableByteCount;
import static com.github.jthuraisamy.yellowusage.utils.NetworkHelper.isAirplaneModeOn;
import static com.github.jthuraisamy.yellowusage.utils.NetworkHelper.isConnectedNetworkOperatorValid;
import static com.github.jthuraisamy.yellowusage.utils.NetworkHelper.isMobileDataConnected;
import static com.github.jthuraisamy.yellowusage.utils.NetworkHelper.isMobileDataEnabled;
import static com.github.jthuraisamy.yellowusage.utils.NetworkHelper.isWifiConnected;
import static com.github.jthuraisamy.yellowusage.utils.NetworkHelper.setMobileDataEnabled;
import static com.github.jthuraisamy.yellowusage.utils.UIHelper.refreshUiSection;
import static java.text.DateFormat.getDateInstance;

public class MainActivity extends ActionBarActivity {

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

    private Realm realm;
    private String networkBrand;
    private int ongoingTasks;
    private Queue<AsyncTask<Void, Void, Void>> taskQueue = new LinkedList<>();

    // APIs for managing mobile connectivity.
    private ConnectivityManager connectivityManager;
    private WifiManager wifiManager;
    private TelephonyManager telephonyManager;
    private boolean wifiWasEnabled;

    // Menu elements.
    private View refreshItemActionView;

    // Data usage card UI elements.
    private CardView dataUsageCard;
    private TextView dataUsagePrimarySubheader;
    private TextView dataUsagePrimaryValue;
    private TextView dataUsageSecondaryValue;
    private ProgressBar dataUsageProgress;
    private Switch dataProtectionSwitch;

    // Voice usage card UI elements.
    private CardView voiceUsageCard;
    private TextView voiceUsagePrimarySubheader;
    private TextView voiceUsagePrimaryValue;
    private TextView voiceUsageSecondaryValue;
    private ProgressBar voiceUsageProgress;
    private Switch voiceProtectionSwitch;

    // Messaging usage card UI elements.
    private CardView messagingUsageCard;
    private TextView messagingUsagePrimarySubheader;
    private TextView messagingUsagePrimaryValue;
    private TextView messagingUsageSecondaryValue;
    private ProgressBar messagingUsageProgress;
    private Switch messagingProtectionSwitch;

    // Sliding panel UI elements.
    private SlidingUpPanelLayout feesPanel;
    private Toolbar bottomBar;
    private ImageView panelStateIcon;
    private TextView overageStatus;
    private TextView planNameHeader;
    private TextView totalAmountDueValue;
    private TextView paymentDueDateValue;
    private TextView nextBillDateValue;
    private TextView dataOverageFeesValue;
    private TextView weekdayMinutesOverageFeesValue;

    /**
     * Listener for phone data state. Run any and all queued tasks once data is connected.
     */
    private PhoneStateListener phoneStateListener = new PhoneStateListener() {
        @Override
        public void onDataConnectionStateChanged(int state) {
            super.onDataConnectionStateChanged(state);

            Log.i(TAG, "phoneStateListener: state = " + String.valueOf(state) + ".");

            if (state == TelephonyManager.DATA_CONNECTED) {
                Log.i(TAG, "phoneStateListener: Data is connected.");

                if (!taskQueue.isEmpty()) {
                    // Stop prepare refresh animation in the app bar via onPrepareOptionsMenu().
                    //                    supportInvalidateOptionsMenu();

                    runTasks();
                }
            }
        }
    };

    /**
     * Listener for AsyncTask requests.
     */
    private final BroadcastReceiver asyncTaskReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // Don't add the task if mobile data is not enabled.
            if (!isMobileDataEnabled(connectivityManager))
                return;

            String section = intent.getStringExtra(Constants.EXTRA_SECTION);

            switch (section) {
            case Constants.BALANCE:
                taskQueue.add(new BalanceTask());
                break;
            case Constants.DATA:
                taskQueue.add(new DataUsageTask());
                break;
            case Constants.VOICE_PEAKTIME:
                taskQueue.add(new VoiceUsageTask());
                break;
            case Constants.MESSAGING_TEXT:
                taskQueue.add(new MessagingUsageTask());
                break;
            default:
                break;
            }

            Log.d(TAG, "runTasks: asyncTaskReceiver - " + section);
            if (!taskQueue.isEmpty())
                runTasks();
        }
    };

    /**
     * Listener for UI refreshes. Refresh UI after retrieving data for applicable models.
     */
    private final BroadcastReceiver uiRefreshReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String used, included, secondaryValue;
            boolean isRemaining, isUnlimited, isOverage;

            String section = intent.getStringExtra(Constants.EXTRA_SECTION);
            Log.i(TAG, "Refreshing section: " + section + ".");

            switch (section) {
            case Constants.BALANCE:
                // Get Realm model.
                Balance balance = realm.where(Balance.class).findFirst();

                // Populate values in the fees panel.
                totalAmountDueValue.setText(balance.getTotalAmountDue());
                paymentDueDateValue.setText(getDateInstance(DateFormat.FULL).format(balance.getPaymentDueDate()));
                nextBillDateValue.setText(getDateInstance(DateFormat.FULL).format(balance.getNextBillDate()));
                break;

            case Constants.DATA:
                // Get Realm models.
                balance = realm.where(Balance.class).findFirst();
                DataUsage dataUsage = realm.where(DataUsage.class).findFirst();

                // Format and populate secondary values.
                used = humanReadableByteCount(dataUsage.getUsed());
                included = (dataUsage.getIncluded() < 0) ? getString(R.string.unlimited)
                        : humanReadableByteCount(dataUsage.getIncluded());
                secondaryValue = String.format(getString(R.string.subheader_secondary_value), used, included);
                dataUsageSecondaryValue.setText(secondaryValue);

                // Set plan name.
                planNameHeader.setText(balance.getPlanName());

                // Set boolean values for usage states.
                isRemaining = dataUsage.getRemaining() > 0;
                isUnlimited = dataUsage.getRemaining() < 0;
                isOverage = dataUsage.getOverage() > 0;

                // If there is usage remaining:
                if (isRemaining) {
                    // Format and set primary values.
                    dataUsagePrimarySubheader.setText(R.string.subheader_remaining);
                    dataUsagePrimaryValue.setText(humanReadableByteCount(dataUsage.getRemaining()));

                    // Set progress bar to proportional values.
                    int progress = (int) (dataUsage.getUsed() * 100.0 / dataUsage.getIncluded() + 0.5);
                    dataUsageProgress.setMax(100);
                    dataUsageProgress.setProgress(progress);

                    // Color primary value to be proportionate to amount of used data.
                    if (progress >= 90) {
                        dataUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_orange_900));
                    } else if (progress >= 75) {
                        dataUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_amber_800));
                    } else {
                        dataUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_green_500));
                    }

                    // Make usage protection switch visible.
                    dataProtectionSwitch.setVisibility(View.VISIBLE);

                    // If usage is unlimited:
                } else if (isUnlimited) {
                    // Format and set primary values.
                    dataUsagePrimarySubheader.setText(R.string.subheader_remaining);
                    dataUsagePrimaryValue.setText(R.string.unlimited);
                    dataUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_green_500));

                    // Set progress bar to green.
                    dataUsageProgress.setMax(100);
                    dataUsageProgress.setProgress(100);
                    dataUsageProgress.getProgressDrawable()
                            .setColorFilter(getResources().getColor(R.color.md_green_500), PorterDuff.Mode.SRC_IN);

                    // If there is an overage:
                } else if (isOverage) {
                    // Format and set primary values.
                    dataUsagePrimarySubheader.setText(R.string.subheader_overage);
                    dataUsagePrimaryValue.setText(humanReadableByteCount(dataUsage.getOverage()));
                    dataUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_red_700));

                    // Set progress bar to red.
                    dataUsageProgress.setMax(100);
                    dataUsageProgress.setProgress(100);
                    dataUsageProgress.getProgressDrawable()
                            .setColorFilter(getResources().getColor(R.color.md_red_700), PorterDuff.Mode.SRC_IN);

                    // Make usage protection switch visible.
                    dataProtectionSwitch.setVisibility(View.VISIBLE);
                }
                break;

            case Constants.VOICE_PEAKTIME:
                // Get Realm model.
                PeakTimeVoiceUsage peakTimeVoiceUsage = realm.where(PeakTimeVoiceUsage.class).findFirst();

                // Format and set secondary values.
                used = String.format("%d min", peakTimeVoiceUsage.getUsed());
                included = (peakTimeVoiceUsage.getIncluded() < 0) ? getString(R.string.unlimited)
                        : String.format("%d min", peakTimeVoiceUsage.getIncluded());
                secondaryValue = String.format(getString(R.string.subheader_secondary_value), used, included);
                voiceUsageSecondaryValue.setText(secondaryValue);

                // Set boolean values for usage states.
                isRemaining = peakTimeVoiceUsage.getRemaining() > 0;
                isUnlimited = peakTimeVoiceUsage.getRemaining() < 0;
                isOverage = peakTimeVoiceUsage.getOverage() > 0;

                // If there is usage remaining:
                if (isRemaining) {
                    // Format and set primary values.
                    voiceUsagePrimarySubheader.setText(R.string.subheader_remaining);
                    voiceUsagePrimaryValue.setText(String.format("%d min", peakTimeVoiceUsage.getRemaining()));
                    voiceUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_green_500));

                    // Set progress bar to proportional values.
                    int progress = (int) (peakTimeVoiceUsage.getUsed() * 100.0 / peakTimeVoiceUsage.getIncluded()
                            + 0.5);
                    voiceUsageProgress.setMax(100);
                    voiceUsageProgress.setProgress(progress);

                    // Color primary value to be proportionate to amount of used data.
                    if (progress >= 90) {
                        voiceUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_orange_900));
                    } else if (progress >= 75) {
                        voiceUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_amber_800));
                    } else {
                        voiceUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_green_500));
                    }

                    // Make usage protection switch visible.
                    voiceProtectionSwitch.setVisibility(View.VISIBLE);

                    // If usage is unlimited:
                } else if (isUnlimited) {
                    // Format and set primary values.
                    voiceUsagePrimarySubheader.setText(R.string.subheader_remaining);
                    voiceUsagePrimaryValue.setText(R.string.unlimited);
                    voiceUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_green_500));

                    // Set progress bar to green.
                    voiceUsageProgress.setMax(100);
                    voiceUsageProgress.setProgress(100);
                    voiceUsageProgress.getProgressDrawable()
                            .setColorFilter(getResources().getColor(R.color.md_green_500), PorterDuff.Mode.SRC_IN);

                    // If there is an overage:
                } else if (isOverage) {
                    // Format and set primary values.
                    voiceUsagePrimarySubheader.setText(R.string.subheader_overage);
                    voiceUsagePrimaryValue.setText(String.format("%d min", peakTimeVoiceUsage.getOverage()));
                    voiceUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_red_700));

                    // Set progress bar to red.
                    voiceUsageProgress.setMax(100);
                    voiceUsageProgress.setProgress(100);
                    voiceUsageProgress.getProgressDrawable()
                            .setColorFilter(getResources().getColor(R.color.md_red_700), PorterDuff.Mode.SRC_IN);

                    // Make usage protection switch visible.
                    voiceProtectionSwitch.setVisibility(View.VISIBLE);
                }
                break;

            case Constants.MESSAGING_TEXT:
                // Get Realm model.
                TextMessageUsage textMessageUsage = realm.where(TextMessageUsage.class).findFirst();

                // Format and set secondary values.
                used = String.valueOf(textMessageUsage.getUsed());
                included = (textMessageUsage.getIncluded() < 0) ? getString(R.string.unlimited)
                        : String.valueOf(textMessageUsage.getIncluded());
                secondaryValue = String.format(getString(R.string.subheader_secondary_value), used, included);
                messagingUsageSecondaryValue.setText(secondaryValue);

                // Set boolean values for usage states.
                isRemaining = textMessageUsage.getRemaining() > 0;
                isUnlimited = textMessageUsage.getRemaining() < 0;
                isOverage = textMessageUsage.getOverage() > 0;

                // If there is usage remaining:
                if (isRemaining) {
                    // Format and set primary values.
                    messagingUsagePrimarySubheader.setText(R.string.subheader_remaining);
                    messagingUsagePrimaryValue.setText(String.format("%d min", textMessageUsage.getRemaining()));
                    messagingUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_green_500));

                    // Set progress bar to proportional values.
                    int progress = (int) (textMessageUsage.getUsed() * 100.0 / textMessageUsage.getIncluded()
                            + 0.5);
                    messagingUsageProgress.setMax(100);
                    messagingUsageProgress.setProgress(progress);

                    // Color primary value to be proportionate to amount of used data.
                    if (progress >= 90) {
                        messagingUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_orange_900));
                    } else if (progress >= 75) {
                        messagingUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_amber_800));
                    } else {
                        messagingUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_green_500));
                    }

                    // Make usage protection switch visible.
                    messagingProtectionSwitch.setVisibility(View.VISIBLE);

                    // If usage is unlimited:
                } else if (isUnlimited) {
                    // Format and set primary values.
                    messagingUsagePrimarySubheader.setText(R.string.subheader_remaining);
                    messagingUsagePrimaryValue.setText(R.string.unlimited);
                    messagingUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_green_500));

                    // Set progress bar to green.
                    messagingUsageProgress.setMax(100);
                    messagingUsageProgress.setProgress(100);
                    messagingUsageProgress.getProgressDrawable()
                            .setColorFilter(getResources().getColor(R.color.md_green_500), PorterDuff.Mode.SRC_IN);

                    // If there is an overage:
                } else if (isOverage) {
                    // Format and set primary values.
                    messagingUsagePrimarySubheader.setText(R.string.subheader_overage);
                    messagingUsagePrimaryValue.setText(String.format("%d min", textMessageUsage.getOverage()));
                    messagingUsagePrimaryValue.setTextColor(getResources().getColor(R.color.md_red_700));

                    // Set progress bar to red.
                    messagingUsageProgress.setMax(100);
                    messagingUsageProgress.setProgress(100);
                    messagingUsageProgress.getProgressDrawable()
                            .setColorFilter(getResources().getColor(R.color.md_red_700), PorterDuff.Mode.SRC_IN);

                    // Make usage protection switch visible.
                    messagingProtectionSwitch.setVisibility(View.VISIBLE);
                }
                break;

            case Constants.OVERAGE_FEES:
                final float dataOverageFee = OverageFeesHelper.calculateData(getApplicationContext());
                final float peakTimeVoiceOverageFee = OverageFeesHelper
                        .calculatePeakTimeVoice(getApplicationContext());

                // Sum all overage fees into a total amount.
                final float totalOverageFee = dataOverageFee + peakTimeVoiceOverageFee;

                // Update bottom toolbar with the overage status.
                if (totalOverageFee > 0) {
                    overageStatus
                            .setText(String.format(getString(R.string.overage_fees_estimate), totalOverageFee));
                    overageStatus.setTextColor(getResources().getColor(R.color.md_red_700));
                } else {
                    overageStatus.setText(getString(R.string.overage_fees_none));
                    overageStatus.setTextColor(getResources().getColor(R.color.md_green_700));
                }

                // Update the data overage fee.
                if (dataOverageFee >= 0) {
                    dataOverageFeesValue.setText(String.format("$%1$.2f", dataOverageFee));
                } else {
                    dataOverageFeesValue.setText(getString(R.string.fees_panel_default_value));
                }

                // Update the peak time voice overage fee.
                if (peakTimeVoiceOverageFee >= 0) {
                    weekdayMinutesOverageFeesValue.setText(String.format("$%1$.2f", peakTimeVoiceOverageFee));
                } else {
                    weekdayMinutesOverageFeesValue.setText(getString(R.string.fees_panel_default_value));
                }

                break;

            default:
                break;
            }
        }
    };

    /**
     * Set panel icon to match whether the panel has expanded or collapsed.
     */
    private PanelSlideListener panelSlideListener = new SlidingUpPanelLayout.PanelSlideListener() {
        @Override
        public void onPanelExpanded(View panel) {
            // Set panel icon state to expand more.
            panelStateIcon = (ImageView) findViewById(R.id.imageView_panelStateIcon);
            panelStateIcon.setImageResource(R.drawable.ic_expand_more_grey600_48dp);
        }

        @Override
        public void onPanelCollapsed(View panel) {
            // Set panel icon state to expand less.
            panelStateIcon = (ImageView) findViewById(R.id.imageView_panelStateIcon);
            panelStateIcon.setImageResource(R.drawable.ic_expand_less_grey600_48dp);
        }

        @Override
        public void onPanelAnchored(View view) {
        }

        @Override
        public void onPanelHidden(View view) {
        }

        @Override
        public void onPanelSlide(View view, float v) {
        }
    };

    /**
     * Launch mobile data settings page when the disable mobile data switch has changed.
     */
    private CompoundButton.OnCheckedChangeListener dataProtectionSwitchListener = new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (isChecked)
                setMobileDataEnabled(getApplicationContext(), connectivityManager, false);
            else
                setMobileDataEnabled(getApplicationContext(), connectivityManager, true);
        }
    };

    private CompoundButton.OnCheckedChangeListener voiceProtectionSwitchListener = new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (isChecked)
                PreferenceHelper.setVoiceOverageProtectionEnabled(getApplicationContext(), true);
            else
                PreferenceHelper.setVoiceOverageProtectionEnabled(getApplicationContext(), false);
        }
    };

    private CompoundButton.OnCheckedChangeListener messagingProtectionSwitchListener = new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (isChecked)
                PreferenceHelper.setMessagingOverageProtectionEnabled(getApplicationContext(), true);
            else
                PreferenceHelper.setMessagingOverageProtectionEnabled(getApplicationContext(), false);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Set app bar.
        Toolbar appBar = (Toolbar) findViewById(R.id.app_bar);
        setSupportActionBar(appBar);

        // Instantiate managers.
        connectivityManager = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
        wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
        telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        networkBrand = telephonyManager.getNetworkOperatorName();

        // Get objects in the data usage card.
        dataUsageCard = (CardView) findViewById(R.id.cardView_dataUsage);
        dataUsagePrimarySubheader = (TextView) findViewById(R.id.textView_dataUsagePrimarySubheader);
        dataUsagePrimaryValue = (TextView) findViewById(R.id.textView_dataUsagePrimaryValue);
        dataUsageSecondaryValue = (TextView) findViewById(R.id.textView_dataUsageSecondaryValue);
        dataUsageProgress = (ProgressBar) findViewById(R.id.progressBar_dataUsage);
        dataProtectionSwitch = (Switch) findViewById(R.id.switch_dataProtection);

        // Get objects in the voice usage card.
        voiceUsageCard = (CardView) findViewById(R.id.cardView_voiceUsage);
        voiceUsagePrimarySubheader = (TextView) findViewById(R.id.textView_voiceUsagePrimarySubheader);
        voiceUsagePrimaryValue = (TextView) findViewById(R.id.textView_voiceUsagePrimaryValue);
        voiceUsageSecondaryValue = (TextView) findViewById(R.id.textView_voiceUsageSecondaryValue);
        voiceUsageProgress = (ProgressBar) findViewById(R.id.progressBar_voiceUsage);
        voiceProtectionSwitch = (Switch) findViewById(R.id.switch_voiceProtection);

        // Get objects in the messaging usage card.
        messagingUsageCard = (CardView) findViewById(R.id.cardView_messagingUsage);
        messagingUsagePrimarySubheader = (TextView) findViewById(R.id.textView_messagingUsagePrimarySubheader);
        messagingUsagePrimaryValue = (TextView) findViewById(R.id.textView_messagingUsagePrimaryValue);
        messagingUsageSecondaryValue = (TextView) findViewById(R.id.textView_messagingUsageSecondaryValue);
        messagingUsageProgress = (ProgressBar) findViewById(R.id.progressBar_messagingUsage);
        messagingProtectionSwitch = (Switch) findViewById(R.id.switch_messagingProtection);

        // Get objects in the sliding panel layout.
        feesPanel = (SlidingUpPanelLayout) findViewById(R.id.sliding_layout);
        bottomBar = (Toolbar) findViewById(R.id.bottom_bar);
        overageStatus = (TextView) findViewById(R.id.textView_overageStatus);
        planNameHeader = (TextView) findViewById(R.id.textView_planNameHeader);
        totalAmountDueValue = (TextView) findViewById(R.id.textView_totalAmountDueValue);
        paymentDueDateValue = (TextView) findViewById(R.id.textView_paymentDueDateValue);
        nextBillDateValue = (TextView) findViewById(R.id.textView_nextBillDateValue);
        dataOverageFeesValue = (TextView) findViewById(R.id.textView_dataOverageFeesValue);
        weekdayMinutesOverageFeesValue = (TextView) findViewById(R.id.textView_weekdayMinutesOverageFeesValue);

        // Instantiate Realm.
        realm = Realm.getInstance(this);

        // Register broadcast receiver for UI refreshes.
        LocalBroadcastManager.getInstance(this).registerReceiver(uiRefreshReceiver,
                new IntentFilter(Constants.ACTION_REFRESH_UI_SECTION));
        LocalBroadcastManager.getInstance(this).registerReceiver(asyncTaskReceiver,
                new IntentFilter(Constants.ACTION_ASYNC_TASK));

        // Check if the user is connected to the right network.
        if (!isConnectedNetworkOperatorValid(telephonyManager) && !isAirplaneModeOn(this)) {
            InvalidNetworkDialog invalidNetworkDialog = InvalidNetworkDialog
                    .create(telephonyManager.getNetworkOperatorName());
            invalidNetworkDialog.show(getSupportFragmentManager(), InvalidNetworkDialog.TAG);
        } else {
            // Refresh UI with the most recent cached information.
            startActionRefreshUI(this);
        }
    }

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

        // Set Realm change listener.
        realm.addChangeListener(new RealmChangeListener() {
            @Override
            public void onChange() {
                // TODO: Something useful should happen here.
                Log.i(TAG, "RealmChangeListener: Changed.");
            }
        });

        // Set TelephonyManager listener.
        telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);

        // Set mobile data protection switch and listener.
        if (isMobileDataEnabled(connectivityManager))
            dataProtectionSwitch.setChecked(false);
        else
            dataProtectionSwitch.setChecked(true);
        dataProtectionSwitch.setOnCheckedChangeListener(dataProtectionSwitchListener);

        // Set voice protection switch and listener.
        if (PreferenceHelper.isVoiceOverageProtectionEnabled(this))
            voiceProtectionSwitch.setChecked(true);
        else
            voiceProtectionSwitch.setChecked(false);
        voiceProtectionSwitch.setOnCheckedChangeListener(voiceProtectionSwitchListener);

        // Set messaging protection switch and listener.
        if (PreferenceHelper.isMessagingOverageProtectionEnabled(this))
            messagingProtectionSwitch.setChecked(true);
        else
            messagingProtectionSwitch.setChecked(false);
        messagingProtectionSwitch.setOnCheckedChangeListener(messagingProtectionSwitchListener);

        // Set PanelSlideListener to change panel state icon based on expanded/collapsed state.
        feesPanel.setPanelSlideListener(panelSlideListener);

        // Set OnClickListener on bottomBar to expand/collapse the sliding panel.
        bottomBar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (feesPanel.getPanelState() == PanelState.COLLAPSED) {
                    feesPanel.expandPanel();
                } else if (feesPanel.getPanelState() == PanelState.EXPANDED) {
                    feesPanel.collapsePanel();
                }
            }
        });
    }

    @Override
    public void onResume() {
        super.onResume();
    }

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

        // Remove LocalBroadcastManager listeners.
        LocalBroadcastManager.getInstance(this).unregisterReceiver(uiRefreshReceiver);
        LocalBroadcastManager.getInstance(this).unregisterReceiver(asyncTaskReceiver);

        // Remove TelephonyManager listener.
        telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);

        // Remove OnCheckedChangeListener on dataProtectionSwitch.
        dataProtectionSwitch.setOnCheckedChangeListener(null);

        // Remove PanelSlideListener on feesPanel.
        feesPanel.setPanelSlideListener(null);

        // Remove OnClickListener on bottomBar.
        bottomBar.setOnClickListener(null);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        MenuItem refreshItem = menu.findItem(R.id.action_refresh);

        Log.d(TAG, "isMobileDataEnabled   = " + isMobileDataEnabled(connectivityManager));
        Log.d(TAG, "isMobileDataConnected = " + isMobileDataConnected(telephonyManager));
        Log.d(TAG, "isWifiConnected       = " + isWifiConnected(wifiManager));

        // Stop refresh icon animation if it's already ongoing.
        if (refreshItemActionView != null)
            refreshItemActionView.clearAnimation();
        refreshItem.setActionView(null);

        boolean waitingForData = (!taskQueue.isEmpty()) && (!isMobileDataConnected(telephonyManager));

        // (Re-)Start preparing refresh icon animation while waiting for mobile data connection.
        if (waitingForData) {
            LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            @SuppressLint("InflateParams")
            ImageView animatingRefreshIcon = (ImageView) layoutInflater.inflate(R.layout.icon_refresh, null);
            Animation clockwiseRotation = AnimationUtils.loadAnimation(this, R.anim.counterclockwise_refresh);
            animatingRefreshIcon.startAnimation(clockwiseRotation);
            refreshItem.setActionView(animatingRefreshIcon);

            // getActionView() always returns null in the else block, so it's assigned to a member variable.
            refreshItemActionView = refreshItem.getActionView();
        }

        // (Re-)Start refresh icon animation only if there are ongoing tasks.
        if (ongoingTasks > 0) {
            LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            @SuppressLint("InflateParams")
            ImageView animatingRefreshIcon = (ImageView) layoutInflater.inflate(R.layout.icon_refresh, null);
            Animation clockwiseRotation = AnimationUtils.loadAnimation(this, R.anim.clockwise_refresh);
            animatingRefreshIcon.startAnimation(clockwiseRotation);
            refreshItem.setActionView(animatingRefreshIcon);

            // getActionView() always returns null in the else block, so it's assigned to a member variable.
            refreshItemActionView = refreshItem.getActionView();
        }

        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        Log.d(TAG, "isMobileDataEnabled   = " + isMobileDataEnabled(connectivityManager));
        Log.d(TAG, "isMobileDataConnected = " + isMobileDataConnected(telephonyManager));
        Log.d(TAG, "isWifiConnected       = " + isWifiConnected(wifiManager));

        switch (id) {
        case R.id.action_refresh:
            Log.i(TAG, "taskQueue = " + String.valueOf(taskQueue.size()));
            Log.i(TAG, "ongoingTasks = " + String.valueOf(ongoingTasks));

            // Remind user to disable airplane mode if it's on.
            if (isAirplaneModeOn(this)) {
                Toast.makeText(this, R.string.disable_airplane_mode, Toast.LENGTH_LONG).show();
                break;
            }

            // Remind user to enable mobile data if it's disabled.
            if (!isMobileDataEnabled(connectivityManager)) {
                Toast.makeText(this, R.string.enable_mobile_data, Toast.LENGTH_LONG).show();
                break;
            }

            if (!taskQueue.isEmpty()) {
                // Finish any already queued tasks.
                runTasks();
            } else if (ongoingTasks == 0) {
                // Add tasks to queue since the queue is empty and there are no ongoing tasks.
                taskQueue.add(new DataUsageTask());
                taskQueue.add(new VoiceUsageTask());
                taskQueue.add(new MessagingUsageTask());
                runTasks();
            } else {
                // Wait for tasks to complete.
                Toast.makeText(this, R.string.wait_for_tasks, Toast.LENGTH_LONG).show();
            }
            break;

        case R.id.action_settings:
            Intent settingsIntent = new Intent(this, SettingsActivity.class);
            startActivity(settingsIntent);
            break;

        case R.id.action_about:
            final Long recommendedBytes = DownloadManager.getRecommendedMaxBytesOverMobile(this);
            final Long maximumBytes = DownloadManager.getMaxBytesOverMobile(this);
            Log.i(TAG, "recommendedBytes = " + recommendedBytes);
            Log.i(TAG, "maximumBytes = " + maximumBytes);
            break;

        case R.id.action_exit:
            this.finish();
            break;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        if (feesPanel.getPanelState() == PanelState.EXPANDED) {
            feesPanel.collapsePanel();
        } else {
            super.onBackPressed();
        }
    }

    /**
     * Run queued tasks provided that mobile data is enabled and connected. If it is not enabled,
     * prompt the user to enable it so that the device can connect to data. If it is enabled but
     * connected to WiFi instead, disable WiFi first. Once a data connection is established, the
     * PhoneStateListener will call this method.
     */
    private void runTasks() {
        if (isMobileDataEnabled(connectivityManager)) {
            Log.i(TAG, "runTasks: Data is enabled.");
            if (isMobileDataConnected(telephonyManager)) {
                Log.i(TAG, "runTasks: Data is connected.");

                // Empty and execute tasks in the task queue.
                while (!taskQueue.isEmpty())
                    taskQueue.remove().execute();
            } else {
                Log.i(TAG, "runTasks: Data is disconnected.");

                if (taskQueue.isEmpty())
                    return;

                if (isWifiConnected(wifiManager)) {
                    Log.i(TAG, "runTasks: Disabling WiFi...");

                    // Start prepare refresh animation in the app bar via onPrepareOptionsMenu().
                    supportInvalidateOptionsMenu();

                    // Disable WiFi, and call runTasks() again after data is connected.
                    telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
                    telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
                    Toast.makeText(this, R.string.disabling_wifi, Toast.LENGTH_SHORT).show();
                    wifiManager.setWifiEnabled(false);
                    wifiWasEnabled = true;
                }
            }
        } else {
            Log.i(TAG, "runTasks: Data is disabled.");

            Toast.makeText(this, R.string.enable_mobile_data, Toast.LENGTH_LONG).show();
        }
    }

    private void onAllTasksComplete() {
        // Stop refresh animation in the app bar via onPrepareOptionsMenu().
        supportInvalidateOptionsMenu();

        if (wifiWasEnabled) {
            Toast.makeText(this, R.string.enabling_wifi, Toast.LENGTH_SHORT).show();
            wifiManager.setWifiEnabled(true);
            wifiWasEnabled = false;
        }
    }

    private class BalanceTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            // Increment ongoingTasks.
            ongoingTasks += 1;

            // Start refresh animation in the app bar via onPrepareOptionsMenu().
            supportInvalidateOptionsMenu();

            // Set UI values to loading.
            totalAmountDueValue.setText(R.string.loading_value);
            paymentDueDateValue.setText(R.string.loading_value);
            nextBillDateValue.setText(R.string.loading_value);
        }

        @Override
        protected Void doInBackground(Void... params) {
            try {
                BalanceParser.parseBalance(getApplicationContext(), networkBrand);
            } catch (UnknownHostException e) {
                Log.e(TAG, "BalanceTask: UnknownHostException");
                Log.e(TAG, e.getMessage());
                taskQueue.add(new BalanceTask());
                runTasks();
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, e.getMessage());
            } catch (NullPointerException e) {
                Log.e(TAG, e.getMessage());
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);

            // Decrement ongoingTasks and call onAllTasksComplete().
            ongoingTasks -= 1;
            if ((taskQueue.isEmpty()) && (ongoingTasks == 0)) {
                onAllTasksComplete();
            }

            // Refresh UI.
            refreshUiSection(getApplicationContext(), Constants.BALANCE);
        }
    }

    private class DataUsageTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            // Increment ongoingTasks.
            ongoingTasks += 1;

            // Start refresh animation in the app bar via onPrepareOptionsMenu().
            supportInvalidateOptionsMenu();

            // Set progress bar to indeterminate state to have a loading animation.
            dataUsageProgress.setIndeterminate(true);
        }

        @Override
        protected Void doInBackground(Void... params) {
            try {
                DataParser.parseDataUsage(getApplicationContext(), networkBrand);
            } catch (UnknownHostException e) {
                Log.e(TAG, "DataUsageTask: UnknownHostException");
                Log.e(TAG, e.getMessage());
                taskQueue.add(new DataUsageTask());
                runTasks();
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, e.getMessage());
            } catch (NullPointerException e) {
                Log.e(TAG, e.getMessage());
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);

            // Decrement ongoingTasks and call onAllTasksComplete().
            ongoingTasks -= 1;
            if ((taskQueue.isEmpty()) && (ongoingTasks == 0)) {
                onAllTasksComplete();
            }

            // Set progress bar to determinate state to disable the loading animation.
            dataUsageProgress.setIndeterminate(false);

            // Refresh UI.
            refreshUiSection(getApplicationContext(), Constants.DATA);
            refreshUiSection(getApplicationContext(), Constants.OVERAGE_FEES);
        }
    }

    private class VoiceUsageTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            // Increment ongoingTasks.
            ongoingTasks += 1;

            // Start refresh animation in the app bar via onPrepareOptionsMenu().
            supportInvalidateOptionsMenu();

            // Set progress bar to indeterminate state to have a loading animation.
            voiceUsageProgress.setIndeterminate(true);
        }

        @Override
        protected Void doInBackground(Void... params) {
            try {
                VoiceParser.parsePeakTimeUsage(getApplicationContext(), networkBrand);
            } catch (UnknownHostException e) {
                Log.e(TAG, "VoiceUsageTask: UnknownHostException");
                Log.e(TAG, e.getMessage());
                taskQueue.add(new VoiceUsageTask());
                runTasks();
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, e.getMessage());
            } catch (NullPointerException e) {
                Log.e(TAG, e.getMessage());
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);

            // Decrement ongoingTasks and call onAllTasksComplete().
            ongoingTasks -= 1;
            if ((taskQueue.isEmpty()) && (ongoingTasks == 0)) {
                onAllTasksComplete();
            }

            // Set progress bar to determinate state to disable the loading animation.
            voiceUsageProgress.setIndeterminate(false);

            // Refresh UI.
            refreshUiSection(getApplicationContext(), Constants.VOICE_PEAKTIME);
            refreshUiSection(getApplicationContext(), Constants.OVERAGE_FEES);
        }
    }

    private class MessagingUsageTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            // Increment ongoingTasks.
            ongoingTasks += 1;

            // Start refresh animation in the app bar via onPrepareOptionsMenu().
            supportInvalidateOptionsMenu();

            // Set progress bar to indeterminate state to have a loading animation.
            messagingUsageProgress.setIndeterminate(true);
        }

        @Override
        protected Void doInBackground(Void... params) {
            try {
                MessageParser.parseTextUsage(getApplicationContext(), networkBrand);
            } catch (UnknownHostException e) {
                Log.e(TAG, "MessagingUsageTask: UnknownHostException");
                Log.e(TAG, e.getMessage());
                taskQueue.add(new MessagingUsageTask());
                runTasks();
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, e.getMessage());
            } catch (NullPointerException e) {
                Log.e(TAG, e.getMessage());
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);

            // Decrement ongoingTasks and call onAllTasksComplete().
            ongoingTasks -= 1;
            if ((taskQueue.isEmpty()) && (ongoingTasks == 0)) {
                onAllTasksComplete();
            }

            // Set progress bar to determinate state to disable the loading animation.
            messagingUsageProgress.setIndeterminate(false);

            // Refresh UI.
            refreshUiSection(getApplicationContext(), Constants.MESSAGING_TEXT);
            refreshUiSection(getApplicationContext(), Constants.OVERAGE_FEES);
        }
    }

}