package org.starfishrespect.myconsumption.android.ui;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import org.starfishrespect.myconsumption.android.R;
import org.starfishrespect.myconsumption.android.SingleInstance;
import org.starfishrespect.myconsumption.android.events.BuildAlertEvent;
import org.starfishrespect.myconsumption.android.tasks.GCMRegister;
import org.starfishrespect.myconsumption.android.tasks.UserUpdater;
import org.starfishrespect.myconsumption.android.tasks.ConfigUpdater;
import org.starfishrespect.myconsumption.android.dao.SensorValuesDao;
import org.starfishrespect.myconsumption.android.tasks.SensorValuesUpdater;
import org.starfishrespect.myconsumption.android.tasks.StatValuesUpdater;
import org.starfishrespect.myconsumption.android.data.UserData;
import org.starfishrespect.myconsumption.android.events.ReloadConfigEvent;
import org.starfishrespect.myconsumption.android.events.ReloadStatEvent;
import org.starfishrespect.myconsumption.android.events.ReloadUserEvent;
import org.starfishrespect.myconsumption.android.ui.widget.ScrimInsetsScrollView;
import org.starfishrespect.myconsumption.android.util.MiscFunctions;
import org.starfishrespect.myconsumption.android.util.PlayServicesUtils;
import org.starfishrespect.myconsumption.android.util.PrefUtils;
import org.starfishrespect.myconsumption.android.util.UIUtils;
import java.util.ArrayList;
import de.greenrobot.event.EventBus;
import static org.starfishrespect.myconsumption.android.util.LogUtils.LOGD;
import static org.starfishrespect.myconsumption.android.util.LogUtils.LOGI;
import static org.starfishrespect.myconsumption.android.util.LogUtils.LOGW;
import static org.starfishrespect.myconsumption.android.util.LogUtils.makeLogTag;
/**
* Every important activity of the application extends the BaseActivity. It is an abstract class
* which purpose is to regroup the elements that are reused in every Activity. Two important widgets
* are defined and handle in BaseActivity: the header bar and the navigation drawer. Moreover,
* common features of the app, such as the reloading option, are also implemented there.
*
* S23Y (2015). Licensed under the Apache License, Version 2.0.
* Adapted from Google I/O 2014 Android App by Thibaud Ledent
*/
public abstract class BaseActivity extends ActionBarActivity implements SensorValuesUpdater.UpdateFinishedCallback,
UserUpdater.GetUserCallback, StatValuesUpdater.StatUpdateFinishedCallback,
ConfigUpdater.ConfigUpdateFinishedCallback{
private static final String TAG = makeLogTag(BaseActivity.class);
private ObjectAnimator mStatusBarColorAnimator;
private Handler mHandler;
private Handler mReloadHandler;
// Primary toolbar and drawer toggle
private Toolbar mActionBarToolbar;
// Navigation drawer:
private DrawerLayout mDrawerLayout;
// A Runnable that we should execute when the navigation drawer finishes its closing animation
private Runnable mDeferredOnDrawerClosedRunnable;
// variables that control the Action Bar auto hide behavior (aka "quick recall")
private boolean mActionBarAutoHideEnabled = false;
private boolean mActionBarShown = true;
// views that correspond to each navdrawer item, null if not yet created
private View[] mNavDrawerItemViews = null;
// list of navdrawer items that were actually added to the navdrawer, in order
private ArrayList<Integer> mNavDrawerItems = new ArrayList<Integer>();
private ViewGroup mDrawerItemsListContainer;
private int mNormalStatusBarColor;
private int mThemedStatusBarColor;
private static final TypeEvaluator ARGB_EVALUATOR = new ArgbEvaluator();
// When set, these components will be shown/hidden in sync with the action bar
// to implement the "quick recall" effect (the Action Bar and the header views disappear
// when you scroll down a list, and reappear quickly when you scroll up).
private ArrayList<View> mHideableHeaderViews = new ArrayList<View>();
// Durations for certain animations we use:
private static final int HEADER_HIDE_ANIM_DURATION = 300;
// symbols for navdrawer items (indices must correspond to array below). This is
// not a list of items that are necessarily *present* in the Nav Drawer; rather,
// it's a list of all possible items.
protected static final int NAVDRAWER_ITEM_CHART = 0;
protected static final int NAVDRAWER_ITEM_STATS = 1;
protected static final int NAVDRAWER_ITEM_COMPARISON = 2;
protected static final int NAVDRAWER_ITEM_ADD_SENSOR = 4;
protected static final int NAVDRAWER_ITEM_SETTINGS = 5;
protected static final int NAVDRAWER_ITEM_INVALID = -1;
protected static final int NAVDRAWER_ITEM_SEPARATOR = -2;
protected static final int NAVDRAWER_ITEM_SEPARATOR_SPECIAL = -3;
// titles for navdrawer items (indices must correspond to the above)
private static final int[] NAVDRAWER_TITLE_RES_ID = new int[]{
R.string.navdrawer_item_chart,
R.string.navdrawer_item_stat,
R.string.navdrawer_item_comparison,
R.string.navdrawer_item_sign_in,
R.string.navdrawer_item_add_sensor,
R.string.navdrawer_item_settings
};
// icons for navdrawer items (indices must correspond to above array)
private static final int[] NAVDRAWER_ICON_RES_ID = new int[] {
R.drawable.ic_drawer_chart, // Chart
R.drawable.ic_drawer_stat, // Stat
R.drawable.ic_comparison, // Comparison
0, // Sign in
R.drawable.ic_add, // Add sensor
R.drawable.ic_drawer_settings
};
// delay to launch nav drawer item, to allow close animation to play
private static final int NAVDRAWER_LAUNCH_DELAY = 250;
// fade in and fade out durations for the main content when switching between
// different Activities of the app through the Nav Drawer
private static final int MAIN_CONTENT_FADEOUT_DURATION = 150;
private static final int MAIN_CONTENT_FADEIN_DURATION = 250;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler();
// Enable or disable each Activity depending on the form factor. This is necessary
// because this app uses many implicit intents where we don't name the exact Activity
// in the Intent, so there should only be one enabled Activity that handles each
// Intent in the app.
UIUtils.enableDisableActivitiesByFormFactor(this);
// Initialize context, database helper, user and so on...
SingleInstance.init(this);
if (savedInstanceState == null) {
registerGCMClient();
}
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
}
mReloadHandler = new Handler();
mReloadHandler.postDelayed(runnable, 1000 * 60 * PrefUtils.getSyncRefresh(this));
mThemedStatusBarColor = getResources().getColor(R.color.theme_primary_dark);
mNormalStatusBarColor = mThemedStatusBarColor;
}
private Runnable runnable = new Runnable() {
@Override
public void run() {
refreshData();
mReloadHandler.postDelayed(this, 1000 * 60 * PrefUtils.getSyncRefresh(BaseActivity.this));
}
};
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
setupNavDrawer();
View mainContent = findViewById(R.id.main_content);
if (mainContent != null) {
mainContent.setAlpha(0);
mainContent.animate().alpha(1).setDuration(MAIN_CONTENT_FADEIN_DURATION);
} else {
LOGW(TAG, "No view with ID main_content to fade in.");
}
}
@Override
protected void onResume() {
super.onResume();
// Verifies the proper version of Google Play Services exists on the device.
PlayServicesUtils.checkGooglePlaySevices(this);
}
/**
* Sets up the navigation drawer as appropriate. Note that the nav drawer will be
* different depending on whether the attendee indicated that they are attending the
* event on-site vs. attending remotely.
*/
private void setupNavDrawer() {
// What nav drawer item should be selected?
int selfItem = getSelfNavDrawerItem();
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
if (mDrawerLayout == null) {
return;
}
mDrawerLayout.setStatusBarBackgroundColor(
getResources().getColor(R.color.theme_primary_dark));
ScrimInsetsScrollView navDrawer = (ScrimInsetsScrollView)
mDrawerLayout.findViewById(R.id.navdrawer);
if (selfItem == NAVDRAWER_ITEM_INVALID) {
// do not show a nav drawer
if (navDrawer != null) {
((ViewGroup) navDrawer.getParent()).removeView(navDrawer);
}
mDrawerLayout = null;
return;
}
if (navDrawer != null) {
final View chosenAccountContentView = findViewById(R.id.chosen_account_content_view);
final View chosenAccountView = findViewById(R.id.chosen_account_view);
final int navDrawerChosenAccountHeight = getResources().getDimensionPixelSize(
R.dimen.navdrawer_chosen_account_height);
navDrawer.setOnInsetsCallback(new ScrimInsetsScrollView.OnInsetsCallback() {
@Override
public void onInsetsChanged(Rect insets) {
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)
chosenAccountContentView.getLayoutParams();
lp.topMargin = insets.top;
chosenAccountContentView.setLayoutParams(lp);
ViewGroup.LayoutParams lp2 = chosenAccountView.getLayoutParams();
lp2.height = navDrawerChosenAccountHeight + insets.top;
chosenAccountView.setLayoutParams(lp2);
}
});
}
if (mActionBarToolbar != null) {
mActionBarToolbar.setNavigationIcon(R.drawable.ic_drawer);
mActionBarToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mDrawerLayout.openDrawer(Gravity.START);
}
});
}
mDrawerLayout.setDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerClosed(View drawerView) {
// run deferred action, if we have one
if (mDeferredOnDrawerClosedRunnable != null) {
mDeferredOnDrawerClosedRunnable.run();
mDeferredOnDrawerClosedRunnable = null;
}
onNavDrawerStateChanged(false, false);
}
@Override
public void onDrawerOpened(View drawerView) {
onNavDrawerStateChanged(true, false);
}
@Override
public void onDrawerStateChanged(int newState) {
onNavDrawerStateChanged(isNavDrawerOpen(), newState != DrawerLayout.STATE_IDLE);
}
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
onNavDrawerSlide(slideOffset);
}
});
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, Gravity.START);
// populate the nav drawer with the correct items
populateNavDrawer();
// TODO could be interesting
// // When the user runs the app for the first time, we want to land them with the
// // navigation drawer open. But just the first time.
// if (!PrefUtils.isWelcomeDone(this)) {
// // first run of the app starts with the nav drawer open
// PrefUtils.markWelcomeDone(this);
// mDrawerLayout.openDrawer(Gravity.START);
// }
}
protected void autoShowOrHideActionBar(boolean show) {
if (show == mActionBarShown) {
return;
}
mActionBarShown = show;
onActionBarAutoShowOrHide(show);
}
// Subclasses can override this for custom behavior
protected void onNavDrawerStateChanged(boolean isOpen, boolean isAnimating) {
if (mActionBarAutoHideEnabled && isOpen) {
autoShowOrHideActionBar(true);
}
}
protected void onNavDrawerSlide(float offset) {}
protected boolean isNavDrawerOpen() {
return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(Gravity.START);
}
/** Populates the navigation drawer with the appropriate items. */
private void populateNavDrawer() {
mNavDrawerItems.clear();
// decide which items will appear in the nav drawer
// Chart is always shown
mNavDrawerItems.add(NAVDRAWER_ITEM_CHART);
mNavDrawerItems.add(NAVDRAWER_ITEM_STATS);
mNavDrawerItems.add(NAVDRAWER_ITEM_COMPARISON);
mNavDrawerItems.add(NAVDRAWER_ITEM_SEPARATOR);
mNavDrawerItems.add(NAVDRAWER_ITEM_ADD_SENSOR);
mNavDrawerItems.add(NAVDRAWER_ITEM_SEPARATOR_SPECIAL);
mNavDrawerItems.add(NAVDRAWER_ITEM_SETTINGS);
createNavDrawerItems();
}
private void createNavDrawerItems() {
mDrawerItemsListContainer = (ViewGroup) findViewById(R.id.navdrawer_items_list);
if (mDrawerItemsListContainer == null) {
return;
}
mNavDrawerItemViews = new View[mNavDrawerItems.size()];
mDrawerItemsListContainer.removeAllViews();
int i = 0;
for (int itemId : mNavDrawerItems) {
mNavDrawerItemViews[i] = makeNavDrawerItem(itemId, mDrawerItemsListContainer);
mDrawerItemsListContainer.addView(mNavDrawerItemViews[i]);
++i;
}
}
private View makeNavDrawerItem(final int itemId, ViewGroup container) {
boolean selected = getSelfNavDrawerItem() == itemId;
int layoutToInflate = 0;
if (itemId == NAVDRAWER_ITEM_SEPARATOR) {
layoutToInflate = R.layout.navdrawer_separator;
} else if (itemId == NAVDRAWER_ITEM_SEPARATOR_SPECIAL) {
layoutToInflate = R.layout.navdrawer_separator;
} else {
layoutToInflate = R.layout.navdrawer_item;
}
View view = getLayoutInflater().inflate(layoutToInflate, container, false);
if (isSeparator(itemId)) {
// we are done
UIUtils.setAccessibilityIgnore(view);
return view;
}
ImageView iconView = (ImageView) view.findViewById(R.id.icon);
TextView titleView = (TextView) view.findViewById(R.id.title);
int iconId = itemId >= 0 && itemId < NAVDRAWER_ICON_RES_ID.length ?
NAVDRAWER_ICON_RES_ID[itemId] : 0;
int titleId = itemId >= 0 && itemId < NAVDRAWER_TITLE_RES_ID.length ?
NAVDRAWER_TITLE_RES_ID[itemId] : 0;
// set icon and text
iconView.setVisibility(iconId > 0 ? View.VISIBLE : View.GONE);
if (iconId > 0) {
iconView.setImageResource(iconId);
}
titleView.setText(getString(titleId));
formatNavDrawerItem(view, itemId, selected);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onNavDrawerItemClicked(itemId);
}
});
return view;
}
private void onNavDrawerItemClicked(final int itemId) {
if (itemId == getSelfNavDrawerItem()) {
mDrawerLayout.closeDrawer(Gravity.START);
return;
}
if (isSpecialItem(itemId)) {
goToNavDrawerItem(itemId);
} else {
// launch the target Activity after a short delay, to allow the close animation to play
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
goToNavDrawerItem(itemId);
}
}, NAVDRAWER_LAUNCH_DELAY);
// change the active item on the list so the user can see the item changed
setSelectedNavDrawerItem(itemId);
// fade out the main content
View mainContent = findViewById(R.id.main_content);
if (mainContent != null) {
mainContent.animate().alpha(0).setDuration(MAIN_CONTENT_FADEOUT_DURATION);
}
}
mDrawerLayout.closeDrawer(Gravity.START);
}
/**
* Sets up the given navdrawer item's appearance to the selected state. Note: this could
* also be accomplished (perhaps more cleanly) with state-based layouts.
*/
private void setSelectedNavDrawerItem(int itemId) {
if (mNavDrawerItemViews != null) {
for (int i = 0; i < mNavDrawerItemViews.length; i++) {
if (i < mNavDrawerItems.size()) {
int thisItemId = mNavDrawerItems.get(i);
formatNavDrawerItem(mNavDrawerItemViews[i], thisItemId, itemId == thisItemId);
}
}
}
}
private void goToNavDrawerItem(int item) {
Intent intent;
switch (item) {
case NAVDRAWER_ITEM_CHART:
intent = new Intent(this, ChartActivity.class);
startActivity(intent);
finish();
break;
case NAVDRAWER_ITEM_STATS:
intent = new Intent(this, StatActivity.class);
startActivity(intent);
finish();
break;
case NAVDRAWER_ITEM_COMPARISON:
intent = new Intent(this, ComparisonActivity.class);
startActivity(intent);
finish();
break;
case NAVDRAWER_ITEM_ADD_SENSOR:
intent = new Intent(this, AddSensorActivity.class);
startActivity(intent);
break;
case NAVDRAWER_ITEM_SETTINGS:
intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
break;
}
}
private boolean isSpecialItem(int itemId) {
return itemId == NAVDRAWER_ITEM_SETTINGS;
}
private void formatNavDrawerItem(View view, int itemId, boolean selected) {
if (isSeparator(itemId)) {
// not applicable
return;
}
ImageView iconView = (ImageView) view.findViewById(R.id.icon);
TextView titleView = (TextView) view.findViewById(R.id.title);
if (selected) {
view.setBackgroundResource(R.drawable.selected_navdrawer_item_background);
}
// configure its appearance according to whether or not it's selected
titleView.setTextColor(selected ?
getResources().getColor(R.color.navdrawer_text_color_selected) :
getResources().getColor(R.color.navdrawer_text_color));
iconView.setColorFilter(selected ?
getResources().getColor(R.color.navdrawer_icon_tint_selected) :
getResources().getColor(R.color.navdrawer_icon_tint));
}
/** Registers device on the GCM server, if necessary. */
private void registerGCMClient() {
// Check device for Play Services APK. If check succeeds, proceed with
// GCM registration.
if (PlayServicesUtils.checkGooglePlaySevices(this)) {
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
String regid = PrefUtils.getRegistrationId(this);
if (regid.isEmpty()) {
GCMRegister task = new GCMRegister();
task.registerInBackground(this);
}
} else {
LOGI(TAG, "No valid Google Play Services APK found.");
}
}
@Override
protected void onDestroy() {
super.onDestroy();
}
private boolean isSeparator(int itemId) {
return itemId == NAVDRAWER_ITEM_SEPARATOR || itemId == NAVDRAWER_ITEM_SEPARATOR_SPECIAL;
}
protected Toolbar getActionBarToolbar() {
if (mActionBarToolbar == null) {
mActionBarToolbar = (Toolbar) findViewById(R.id.toolbar_actionbar);
if (mActionBarToolbar != null) {
setSupportActionBar(mActionBarToolbar);
}
}
return mActionBarToolbar;
}
protected void onActionBarAutoShowOrHide(boolean shown) {
if (mStatusBarColorAnimator != null) {
mStatusBarColorAnimator.cancel();
}
mStatusBarColorAnimator = ObjectAnimator.ofInt(
mDrawerLayout,
(mDrawerLayout != null) ? "statusBarBackgroundColor" : "statusBarColor",
shown ? Color.BLACK : mNormalStatusBarColor,
shown ? mNormalStatusBarColor : Color.BLACK)
.setDuration(250);
if (mDrawerLayout != null) {
mStatusBarColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
ViewCompat.postInvalidateOnAnimation(mDrawerLayout);
}
});
}
mStatusBarColorAnimator.setEvaluator(ARGB_EVALUATOR);
mStatusBarColorAnimator.start();
for (View view : mHideableHeaderViews) {
if (shown) {
view.animate()
.translationY(0)
.alpha(1)
.setDuration(HEADER_HIDE_ANIM_DURATION)
.setInterpolator(new DecelerateInterpolator());
} else {
view.animate()
.translationY(-view.getBottom())
.alpha(0)
.setDuration(HEADER_HIDE_ANIM_DURATION)
.setInterpolator(new DecelerateInterpolator());
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.main, menu);
configureStandardMenuItems(menu);
return true;
}
protected void configureStandardMenuItems(Menu menu) {
MenuItem refreshItem = menu.findItem(R.id.menu_refresh);
if (refreshItem != null) {
refreshItem.setVisible(true);
}
MenuItem disconnectItem = menu.findItem(R.id.menu_disconnect);
if (disconnectItem != null) {
disconnectItem.setVisible(true);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.menu_refresh:
refreshData();
LOGD(TAG, "menu refresh clicked");
return true;
case R.id.menu_disconnect:
SingleInstance.disconnect();
LOGD(TAG, "menu disconnect clicked");
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Returns the navigation drawer item that corresponds to this Activity. Subclasses
* of BaseActivity override this to indicate what nav drawer item corresponds to them
* Return NAVDRAWER_ITEM_INVALID to mean that this Activity should not have a Nav Drawer.
*/
protected int getSelfNavDrawerItem() {
return NAVDRAWER_ITEM_INVALID;
}
/**
* Refresh data from server to the local Android db.
*/
public void refreshData() {
if (!MiscFunctions.isOnline(this)) {
MiscFunctions.makeOfflineDialog(this).show();
return;
}
showReloadLayout(true);
// Reload the user from server to see if new sensors have been added
UserUpdater userUpdater = new UserUpdater(SingleInstance.getUserController().getUser().getName(),
SingleInstance.getUserController().getUser().getPassword());
userUpdater.setGetUserCallback(this);
userUpdater.execute();
}
/**
* Show or hide a reload layout while loading data from server.
* @param visible
*/
private void showReloadLayout(boolean visible) {
if (visible) {
findViewById(R.id.layoutGlobalReloading).setVisibility(View.VISIBLE);
} else {
findViewById(R.id.layoutGlobalReloading).setVisibility(View.GONE);
}
}
// from callback of GetUserAsyncTask
@Override
public void userFound(UserData user) {
new SensorValuesDao(SingleInstance.getDatabaseHelper()).updateSensorList(user.getSensors());
// Fetch sensor values from server
SensorValuesUpdater updater = new SensorValuesUpdater();
updater.setUpdateFinishedCallback(this);
updater.refreshDB();
// Fetch the stats from the server
StatValuesUpdater statUpdater = new StatValuesUpdater();
statUpdater.setUpdateFinishedCallback(this);
statUpdater.refreshDB();
// Fetch the config from the server
ConfigUpdater configUpdater = new ConfigUpdater();
configUpdater.setUpdateFinishedCallback(this);
configUpdater.refreshDB();
}
// from callback of GetUserAsyncTask
@Override
public void userRetrieveError(Exception e) {
new AlertDialog.Builder(this)
.setTitle(R.string.dialog_title_error)
.setMessage(getString(R.string.dialog_error_update_data_error))
.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
showReloadLayout(false);
}
@Override
public void onUpdateFinished() {
SingleInstance.getUserController().loadUser();
showReloadLayout(false);
EventBus.getDefault().post(new ReloadUserEvent(false));
}
@Override
public void onStatUpdateFinished() {
EventBus.getDefault().post(new ReloadStatEvent(true));
}
@Override
public void onConfigUpdateFinished() {
EventBus.getDefault().post(new ReloadConfigEvent(true));
}
public void onEvent(BuildAlertEvent event) {
if (!event.buildAlert())
return;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.dialog_title_error)
.setMessage(this.getString(R.string.dialog_message_error_when_loading_please_reconnect))
.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
SingleInstance.disconnect();
}
});
builder.show();
}
}