com.swisscom.safeconnect.activity.DashboardActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.swisscom.safeconnect.activity.DashboardActivity.java

Source

/*
 Swisscom Safe Connect
 Copyright (C) 2014 Swisscom
    
 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.swisscom.safeconnect.activity;

import android.app.Dialog;
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.LabeledIntent;
import android.net.Uri;
import android.net.VpnService;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.AlertDialog;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.swisscom.safeconnect.BuildConfig;
import com.swisscom.safeconnect.R;
import com.swisscom.safeconnect.backend.BackendConnector;
import com.swisscom.safeconnect.backend.SubscriptionsManager;
import com.swisscom.safeconnect.billing.PurchaseManager;
import com.swisscom.safeconnect.fragment.InfoFragment;
import com.swisscom.safeconnect.fragment.PipeDialogFragment;
import com.swisscom.safeconnect.fragment.StatisticsFragment;
import com.swisscom.safeconnect.fragment.VpnInfoDlgFragment;
import com.swisscom.safeconnect.fragment.WaitingIndicatorFragment;
import com.swisscom.safeconnect.fragment.WhatsnewDialogFragment;
import com.swisscom.safeconnect.loader.ConnectionsListLoader;
import com.swisscom.safeconnect.model.PlumberAuthResponse;
import com.swisscom.safeconnect.model.PlumberLastConnectionLogResponseList;
import com.swisscom.safeconnect.model.Subscription;
import com.swisscom.safeconnect.sharing.FacebookSharing;
import com.swisscom.safeconnect.sharing.Sharing;
import com.swisscom.safeconnect.sharing.SharingAdapter;
import com.swisscom.safeconnect.sharing.TwitterSharing;
import com.swisscom.safeconnect.utils.Config;
import com.swisscom.safeconnect.utils.Logger;
import com.swisscom.safeconnect.utils.VpnConfigurator;
import com.swisscom.safeconnect.view.ConnectionStatusView;
import com.swisscom.safeconnect.view.SubscriptionStatusView;
import com.swisscom.safeconnect.view.WorldMapView;

import org.strongswan.android.data.LogContentProvider;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.logic.CharonVpnService;
import org.strongswan.android.logic.VpnStateService;
import org.strongswan.android.ui.LogActivity;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class DashboardActivity extends PipeActivity implements VpnStateService.VpnStateListener,
        LoaderManager.LoaderCallbacks<PlumberLastConnectionLogResponseList> {
    public static final String KEY_OPEN_SUBSCRIPTIONS = "DashboardActivity.openSubscriptions";
    public static final String KEY_CONN_STATUS = "DashboardActivity.KEY_CONN_STATUS";

    private static final int PREPARE_VPN_SERVICE = 0;
    private static final String DIALOG_TAG = "Dialog";
    private static final int LOADER_ID = "DashboardActivity".hashCode();
    private static final long SUBCR_FETCH_REPEAT_INTERVAL = 2000;

    private VpnProfile mProfile;
    private VpnStateService mService;
    private VpnStateService.State mVpnState = VpnStateService.State.DISABLED;
    private BackendConnector backendConnector = new BackendConnector(this);

    private ConnectionStatusView viewConnStatus;
    private SubscriptionStatusView viewSubscrStatus;
    private WorldMapView viewMap;
    private Button btnRescaleMap;

    private WaitingIndicatorFragment waitingFragment = new WaitingIndicatorFragment();

    private Subscription[] subscriptions;
    private SubscriptionsManager subscrManager;
    private boolean mOpenSubscriptions;

    private PauseHandler handler = new PauseHandler();

    public interface VpnServiceIntentProvider {
        public Intent getIntent();
    }

    private VpnServiceIntentProvider vpnServiceIntentProvider;

    private Runnable subscriptionsFetchRunnable = new Runnable() {
        @Override
        public void run() {
            subscrManager.init(new PurchaseManager.Callback<Subscription[]>() {
                @Override
                public void onRequestCompleted(Subscription[] result) {
                    subscriptions = result;
                    getOwnSubscription();
                }

                @Override
                public void onRequestFailed(int code) {
                    // failed to get the subscriptions, disconnect VPN and try again
                    if (mVpnState == VpnStateService.State.CONNECTED) {
                        disconnectVpn();
                    } else {
                        showNoConnection();
                    }
                }
            });
        }
    };

    private final ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = ((VpnStateService.LocalBinder) service).getService();
            mService.registerListener(DashboardActivity.this);
            syncVpnState();
        }
    };

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        subscrManager.onSaveInstanceState(outState);
        outState.putBoolean(KEY_OPEN_SUBSCRIPTIONS, mOpenSubscriptions);
        outState.putSerializable(KEY_CONN_STATUS, viewConnStatus.getConnectionState());
    }

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

        setContentView(R.layout.dashboard);

        vpnServiceIntentProvider = new VpnServiceIntentProvider() {
            @Override
            public Intent getIntent() {
                Intent intent = new Intent(DashboardActivity.this, CharonVpnService.class);
                intent.putExtra(Config.VPN_PROFILE_BUNDLE_KEY, mProfile);
                return intent;
            }
        };

        btnRescaleMap = (Button) findViewById(R.id.btn_rescale_map);
        btnRescaleMap.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                btnRescaleMap.setVisibility(View.GONE);
                viewMap.setAllowRecalcBounds();
            }
        });

        viewSubscrStatus = (SubscriptionStatusView) findViewById(R.id.view_subscr_status);
        viewSubscrStatus.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOpenSubscriptions)
                    return;

                // if subscription expired, make sure that the VPN is disconnected before
                // opening the subscriptions activity
                Subscription cur = viewSubscrStatus.getCurrentSubscription();
                if (cur != null && cur.getValidTill() * 1000 <= System.currentTimeMillis()
                        && mVpnState != VpnStateService.State.DISABLED) {
                    mOpenSubscriptions = true;
                    disconnectVpn();
                } else {
                    openSubscriptionsActivity();
                }
            }
        });

        viewConnStatus = (ConnectionStatusView) findViewById(R.id.view_conn_status);
        viewConnStatus.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switch (viewConnStatus.getConnectionState()) {
                case CONNECTED:
                    disconnectVpn();
                    break;
                case DISCONNECTED:
                case UNAVAILABLE:
                    connectVpn();
                    break;
                default:
                    break;
                }
            }
        });

        viewMap = (WorldMapView) findViewById(R.id.view_world_map);
        viewMap.setOnUserMapInteractionListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                btnRescaleMap.setVisibility(View.VISIBLE);
            }
        });

        if (!Config.getInstance().getAuthToken().isEmpty()) {
            bindVpnService();
        }

        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction().replace(R.id.frame_sidebar, new InfoFragment()).commit();

            // in case DashboardActivity gets killed in the meantime, the intent of a
            // newly created DashboardActivity will still contain the old value in its
            // intent. onSaveInstanceState works though.
            mOpenSubscriptions = getIntent() != null && (getIntent().getBooleanExtra(KEY_OPEN_SUBSCRIPTIONS, false)
                    || getIntent().getAction() == Intent.ACTION_VIEW && getIntent().getDataString() != null
                            && getIntent().getDataString().contains("subscribe"));

        } else {
            mOpenSubscriptions = savedInstanceState.getBoolean(KEY_OPEN_SUBSCRIPTIONS, false);
            viewConnStatus
                    .setConnectionState((ConnectionStatusView.VpnState) savedInstanceState.get(KEY_CONN_STATUS));
        }

        subscrManager = new SubscriptionsManager(this, savedInstanceState);
        handler.postRunnable(subscriptionsFetchRunnable);

        // show what's new if needed
        if (Config.getInstance().isAppGotUpdated()) {
            Config.getInstance().clearAppUpdatedFlag();

            WhatsnewDialogFragment dlg = new WhatsnewDialogFragment();
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            Fragment prev = getSupportFragmentManager().findFragmentByTag("whatsnew");
            if (prev != null) {
                ft.remove(prev);
            }
            ft.addToBackStack(null);
            dlg.show(ft, "whatsnew");
        }
    }

    private void openSubscriptionsActivity() {
        if (subscriptions == null)
            return;
        Intent subscriptionIntent = new Intent(this, SubscriptionActivity.class);
        subscriptionIntent.putExtra(SubscriptionsManager.KEY_SUBSCRIPTIONS, subscriptions);
        subscriptionIntent.putExtra(SubscriptionsManager.KEY_CURRENT_SUBSCR,
                viewSubscrStatus.getCurrentSubscription());
        startActivity(subscriptionIntent);
    }

    @Override
    protected void onPause() {
        super.onPause();
        handler.pause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        handler.resume();
        getOwnSubscription();
        syncVpnState();
    }

    private void bindVpnService() {
        Context context = getApplicationContext();
        context.bindService(new Intent(context, VpnStateService.class), mServiceConnection, BIND_AUTO_CREATE);
    }

    @Override
    public void onStart() {
        super.onStart();
        if (mService != null) {
            mService.registerListener(this);
            syncVpnState();
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mService != null) {
            mService.unregisterListener(this);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mService != null) {
            getApplicationContext().unbindService(mServiceConnection);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);

        if (BuildConfig.TESTING) {
            toolbar.getMenu().findItem(R.id.menu_show_log).setVisible(false);
            toolbar.getMenu().findItem(R.id.menu_change_num).setVisible(false);
        } else if (!BuildConfig.DEBUG) {
            toolbar.getMenu().findItem(R.id.menu_show_log).setVisible(false);
            toolbar.getMenu().findItem(R.id.menu_change_num).setVisible(false);
            toolbar.getMenu().findItem(R.id.menu_send_log).setVisible(false);
        }

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_show_log:
            Intent logIntent = new Intent(this, LogActivity.class);
            startActivity(logIntent);
            return true;
        case R.id.menu_send_log:
            File log = Logger.saveLogs(this);
            File charonlog = new File(getFilesDir(), CharonVpnService.LOG_FILE);

            Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
            intent.putExtra(Intent.EXTRA_EMAIL, new String[] { "manuel.cianci1@swisscom.com" });
            intent.putExtra(Intent.EXTRA_SUBJECT, "PipeOfTrust LogFiles");
            intent.setType("text/plain");

            ArrayList<Uri> files = new ArrayList<Uri>();
            if (charonlog.exists() && charonlog.length() > 0) {
                files.add(LogContentProvider.createContentUri());
            }
            if (log.exists() && log.length() > 0) {
                files.add(Uri.fromFile(log));
            }
            if (files.size() == 0) {
                Toast.makeText(this, "Log is empty!", Toast.LENGTH_SHORT).show();
                return true;
            }

            intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, files);
            startActivity(Intent.createChooser(intent, getString(R.string.send_log)));
            return true;
        case R.id.menu_change_num:
            if (mService.getState() == VpnStateService.State.CONNECTED) {
                mService.disconnect();
            }
            intent = new Intent(this, RegistrationActivity.class);
            intent.putExtra(RegistrationActivity.FORCE_ACTIVITY, true);
            startActivity(intent);
            finish();
            return true;
        case R.id.menu_help:
            startActivity(new Intent(this, FaqActivity.class));
            return true;
        case R.id.menu_settings:
            Intent settingsIntent = new Intent(this, SettingsActivity.class);
            startActivity(settingsIntent);
            return true;
        case R.id.menu_share:
            share();
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void stateChanged() {
        syncVpnState();
    }

    public void startVpnTunnel(VpnProfile profile) {
        prepareVpnService(profile);
    }

    /**
     * Prepare the VpnService. If this succeeds the current VPN profile is
     * started.
     * @param profile bundle containing the information about the profile to be started
     */
    protected void prepareVpnService(VpnProfile profile) {
        Intent intent;
        try {
            intent = VpnService.prepare(this);
        } catch (IllegalStateException ex) {
            /* this happens if the always-on VPN feature (Android 4.2+) is activated */
            VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_during_lockdown);
            return;
        }
        /* store profile info until the user grants us permission */
        mProfile = profile;
        if (intent != null) {
            try {
                startActivityForResult(intent, PREPARE_VPN_SERVICE);
            } catch (ActivityNotFoundException ex) {
                /* it seems some devices, even though they come with Android 4,
                 * don't have the VPN components built into the system image.
                 * com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog
                 * will not be found then */
                VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported);
            }
        } else {
            /* user already granted permission to use VpnService */
            onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case PREPARE_VPN_SERVICE:
            if (resultCode == RESULT_OK && mProfile != null) {
                startService(vpnServiceIntentProvider.getIntent());
            }
            break;
        default:
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    @Override
    public Loader<PlumberLastConnectionLogResponseList> onCreateLoader(int id, Bundle args) {
        return new ConnectionsListLoader(this);
    }

    @Override
    public void onLoadFinished(Loader<PlumberLastConnectionLogResponseList> loader,
            PlumberLastConnectionLogResponseList data) {
        if (data != null && data.getConnectionLogs() != null) {
            viewMap.setConnPoints(data);
        }
    }

    @Override
    public void onLoaderReset(Loader<PlumberLastConnectionLogResponseList> loader) {

    }

    /**
     * Class representing an error message which is displayed if VpnService is
     * not supported on the current device.
     */
    public static class VpnNotSupportedError extends DialogFragment {
        static final String ERROR_MESSAGE_ID = "org.strongswan.android.VpnNotSupportedError.MessageId";

        public static void showWithMessage(FragmentActivity activity, int messageId) {
            Bundle bundle = new Bundle();
            bundle.putInt(ERROR_MESSAGE_ID, messageId);
            VpnNotSupportedError dialog = new VpnNotSupportedError();
            dialog.setArguments(bundle);
            dialog.show(activity.getSupportFragmentManager(), DIALOG_TAG);
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            final Bundle arguments = getArguments();
            final int messageId = arguments.getInt(ERROR_MESSAGE_ID);
            return new AlertDialog.Builder(getActivity(), R.style.AppCompat_Pipe_Dialog_Alert)
                    .setTitle(R.string.vpn_not_supported_title).setMessage(messageId).setCancelable(false)
                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int id) {
                            dialog.dismiss();
                        }
                    }).create();
        }
    }

    public VpnStateService getMService() {
        return mService;
    }

    public void runBackendConnection(final String token) {
        viewConnStatus.setConnectionState(ConnectionStatusView.VpnState.CONNECTED);
        showWaiting();
        backendConnector.authenticateUser(Config.getInstance().getPhoneNumber(), Config.getInstance().getDeviceId(),
                token, new BackendConnector.ResponseCallback<PlumberAuthResponse>() {
                    @Override
                    public void onRequestComplete(int statusCode, final PlumberAuthResponse response) {
                        if (isFinishing())
                            return;

                        switch (statusCode) {
                        case 0:
                            // connections error, most likely no network
                            showNoConnection();
                            break;
                        case 404:
                            showAuthError();
                            break;
                        case 200:
                            final Runnable vpnConnectRunnable = new Runnable() {
                                @Override
                                public void run() {
                                    showVpnConnected();
                                    viewSubscrStatus.setVisibility(View.VISIBLE);
                                    //save new auth token for next use!
                                    Config.getInstance().saveAuthToken(response.getUserToken());
                                    VpnProfile mProfile = VpnConfigurator.getVpnProfile(response.getUsername(),
                                            response.getPassword(), response.getPsk());
                                    startVpnTunnel(mProfile);
                                }
                            };

                            if (Config.getInstance().isFirstVpnLaunch()) {
                                handler.postRunnable(new Runnable() {
                                    @Override
                                    public void run() {
                                        VpnInfoDlgFragment vpnDlg = new VpnInfoDlgFragment();
                                        vpnDlg.setClickRunnable(vpnConnectRunnable);
                                        vpnDlg.show(getSupportFragmentManager(), "vpn_dlg");
                                        Config.getInstance().setFirstVpnLaunch(false);
                                    }
                                });
                            } else {
                                vpnConnectRunnable.run();
                            }
                            break;
                        default:
                            // this should never be reached
                            showNoConnection();
                            break;
                        }
                    }
                });
    }

    private void showAuthError() {
        handler.postRunnable(new Runnable() {
            @Override
            public void run() {
                PipeDialogFragment dlg = new PipeDialogFragment();
                dlg.setTitle(getString(R.string.lab_dlg_rereg_title));
                dlg.setMessage(getString(R.string.lab_dlg_rereg_msg));
                dlg.setOnPositiveClickListener(new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Intent intent = new Intent(getApplicationContext(), RegistrationActivity.class);
                        intent.putExtra(RegistrationActivity.FORCE_ACTIVITY, true);
                        startActivity(intent);
                        finish();
                    }
                });
                dlg.show(getSupportFragmentManager(), "login_error");
            }
        });
    }

    private void getOwnSubscription() {
        subscrManager.getOwnSubscription(new PurchaseManager.Callback<Subscription>() {
            private void changeState() {
                if (isFinishing())
                    return;

                boolean subscrValid = viewSubscrStatus.isSubscriptionValid();
                viewConnStatus.setActionVisible(subscrValid);
                if (!subscrValid) {
                    setInfo(InfoFragment.State.NO_SUBSCIPTION, false);
                } else {
                    setInfo(InfoFragment.State.DISCONNECTED, false);
                }
            }

            @Override
            public void onRequestCompleted(Subscription result) {
                if (isFinishing())
                    return;

                if (viewConnStatus.getConnectionState() == ConnectionStatusView.VpnState.UNAVAILABLE) {
                    viewConnStatus.setConnectionState(ConnectionStatusView.VpnState.DISCONNECTED);
                    viewMap.showDisconnected();
                }

                viewSubscrStatus.setVisibility(View.VISIBLE);

                viewSubscrStatus.setSubscription(result);
                changeState();

                if (mOpenSubscriptions) {
                    // disconnect vpn if no subscription. Subscriptions activity will be
                    // opened once it's disconnected
                    if (result.getValidTill() * 1000 <= System.currentTimeMillis()
                            && mVpnState != VpnStateService.State.DISABLED) {
                        disconnectVpn();
                    } else {
                        mOpenSubscriptions = false;
                        openSubscriptionsActivity();
                    }
                }
            }

            @Override
            public void onRequestFailed(int code) {
                // failed to get the subscriptions, disconnect VPN and try again
                if (mVpnState == VpnStateService.State.CONNECTED && subscrManager.isFailed()) {
                    disconnectVpn();
                }

                if (code == 404) {
                    showAuthError();
                } else {
                    showNoConnection();
                }
            }
        });
    }

    private void share() {
        final String appUrl = Config.GPLAY_URL;
        final String content = getString(R.string.share_content);
        final String subject = getString(R.string.share_subject);

        List<Intent> shareIntents = new ArrayList<Intent>();
        //fb & twitter
        shareIntents.add(new FacebookSharing(this).getIntent(content, appUrl));
        shareIntents.add(new TwitterSharing(this).getIntent(content, appUrl));

        //favorite sharing apps
        for (String packageName : Sharing.favoriteApps) {
            Intent i = Sharing.getSharingApp(packageName, subject, content + ' ' + appUrl, this);
            if (i != null) {
                shareIntents.add(i);
            }
        }

        //trigger to load all sharing apps
        LabeledIntent share = new LabeledIntent(new Intent(), "", getString(R.string.share_more_apps), 0);
        share.putExtra("more", true);
        shareIntents.add(share);

        AlertDialog.Builder builder = new AlertDialog.Builder(DashboardActivity.this,
                R.style.AppCompat_Pipe_Dialog_Alert);
        builder.setTitle(R.string.share_with);
        final SharingAdapter adapter = new SharingAdapter(this, R.layout.item_sharing, shareIntents);
        builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Intent i = adapter.getItem(which);

                //Trigger to load the standard android sharing dialog
                if (i.getBooleanExtra("more", false)) {
                    Intent intent = Intent.createChooser(Sharing.getSharingIntent(subject, content + ' ' + appUrl),
                            getString(R.string.share_with));
                    startActivity(intent);
                    return;
                }

                //fix exception when launching labeled-intent
                if (i instanceof LabeledIntent) {
                    i = new Intent(i);
                }
                startActivity(i);
            }
        });

        builder.show();
    }

    private void setInfo(final InfoFragment.State state, final boolean forceFragment) {
        handler.postRunnable(new Runnable() {
            @Override
            public void run() {
                if (isFinishing())
                    return;
                Fragment curFragment = getSupportFragmentManager().findFragmentById(R.id.frame_sidebar);
                if (curFragment instanceof InfoFragment) {
                    ((InfoFragment) curFragment).setState(state);
                } else if (forceFragment) {
                    InfoFragment frag = new InfoFragment();
                    getSupportFragmentManager().beginTransaction().replace(R.id.frame_sidebar, frag).commit();
                    frag.setState(state);
                }
            }
        });
    }

    public void connectVpn() {
        if (mService != null) {
            runBackendConnection(Config.getInstance().getAuthToken());
        }
    }

    public void disconnectVpn() {
        showVpnDisconnected();
        if (mService != null) {
            mService.disconnect();
        }
    }

    private void showVpnConnected() {
        handler.postRunnable(new Runnable() {
            @Override
            public void run() {
                if (getSupportFragmentManager().findFragmentById(R.id.frame_sidebar) instanceof StatisticsFragment)
                    return;
                getSupportFragmentManager().beginTransaction().replace(R.id.frame_sidebar, new StatisticsFragment())
                        .commit();
                startConnectionsLoader();
            }
        });
    }

    private void showVpnDisconnected() {
        handler.postRunnable(new Runnable() {
            @Override
            public void run() {
                if (getSupportFragmentManager().findFragmentById(R.id.frame_sidebar) instanceof InfoFragment)
                    return;

                viewConnStatus.setConnectionState(ConnectionStatusView.VpnState.DISCONNECTED);
                viewConnStatus.setConnectionState(ConnectionStatusView.VpnState.WAITING);
                InfoFragment frag = new InfoFragment();
                getSupportFragmentManager().beginTransaction().replace(R.id.frame_sidebar, frag).commit();
                stopConnectionsLoader();
                viewMap.showDisconnected();
                frag.setState(viewSubscrStatus.isSubscriptionValid() ? InfoFragment.State.DISCONNECTED
                        : InfoFragment.State.NO_SUBSCIPTION);
            }
        });
    }

    public void syncVpnState() {
        VpnStateService.State curVpnState = VpnStateService.State.DISABLED;
        if (mService != null) {
            curVpnState = mService.getState();
        } else {
            return;
        }

        // state change detected
        if (curVpnState != mVpnState
                || getSupportFragmentManager()
                        .findFragmentById(R.id.frame_sidebar) instanceof WaitingIndicatorFragment
                || viewConnStatus.getConnectionState() == ConnectionStatusView.VpnState.WAITING) {
            switch (curVpnState) {
            case DISABLED:
                showVpnDisconnected();
                handler.postRunnable(new Runnable() {
                    @Override
                    public void run() {
                        viewConnStatus.setConnectionState(ConnectionStatusView.VpnState.DISCONNECTED);
                        if (subscrManager.isFailed()) {
                            handler.postRunnable(subscriptionsFetchRunnable);
                        } else if (mOpenSubscriptions) {
                            mOpenSubscriptions = false;
                            openSubscriptionsActivity();
                        }
                    }
                });
                break;
            case CONNECTING:
                if (!(getSupportFragmentManager()
                        .findFragmentById(R.id.frame_sidebar) instanceof StatisticsFragment)) {
                    showWaiting();
                }
                break;
            case CONNECTED:
                viewConnStatus.setConnectionState(ConnectionStatusView.VpnState.CONNECTED);
                showVpnConnected();
                break;
            case DISCONNECTING:
                if (!(getSupportFragmentManager().findFragmentById(R.id.frame_sidebar) instanceof InfoFragment)) {
                    showWaiting();
                }
                break;
            }
        }

        mVpnState = curVpnState;
    }

    private void showWaiting() {
        viewConnStatus.setConnectionState(ConnectionStatusView.VpnState.WAITING);
        if (!waitingFragment.isVisible()) {
            handler.postRunnable(new Runnable() {
                @Override
                public void run() {
                    getSupportFragmentManager().beginTransaction().replace(R.id.frame_sidebar, waitingFragment)
                            .commit();
                }
            });
        }
    }

    private void startConnectionsLoader() {
        viewMap.setAllowRecalcBounds();
        btnRescaleMap.setVisibility(View.GONE);
        getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
    }

    private void showNoConnection() {
        handler.postDelayedRunnable(subscriptionsFetchRunnable, SUBCR_FETCH_REPEAT_INTERVAL, true);
        viewConnStatus.setConnectionState(ConnectionStatusView.VpnState.UNAVAILABLE);
        viewMap.showNoNetwork();
        viewSubscrStatus.setVisibility(View.GONE);
        setInfo(InfoFragment.State.NO_NETWORK, true);
    }

    private void stopConnectionsLoader() {
        getSupportLoaderManager().destroyLoader(LOADER_ID);
    }
}