/*
* This file is part of Prepay Credit for Android
*
* Copyright © 2013 Damien O'Reilly
*
* Prepay Credit for Android 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.
*
* Prepay Credit for Android 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 Prepay Credit for Android. If not, see <http://www.gnu.org/licenses/>.
*
* Report bugs or new features at: https://github.com/DamienOReilly/PrepayCredit
* Contact the author at: damienreilly@gmail.com
*/
package damo.three.ie.activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.FragmentManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.*;
import damo.three.ie.R;
import damo.three.ie.fragment.UpdateFragment;
import damo.three.ie.net.ThreeHttpClient;
import damo.three.ie.prepay.Constants;
import damo.three.ie.prepayusage.BasicUsageItem;
import damo.three.ie.prepayusage.BasicUsageItemsGrouped;
import damo.three.ie.prepayusage.UsageItem;
import damo.three.ie.prepayusage.items.OutOfBundle;
import damo.three.ie.ui.BasicUsageLayout;
import damo.three.ie.ui.ExtendedScrollView;
import damo.three.ie.ui.OutOfBundleLayout;
import damo.three.ie.util.DateUtils;
import damo.three.ie.util.JSONUtils;
import damo.three.ie.util.PrepayException;
import damo.three.ie.util.UsageUtils;
import org.ocpsoft.prettytime.PrettyTime;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Date;
import java.util.List;
public class PrepayCreditActivity extends ActionBarActivity implements
UpdateFragment.AccountProcessorListener {
private boolean working = false;
private boolean refreshedOnStart = false;
private boolean refreshDoneSinceLoadingPersistedData = false;
private SharedPreferences sharedPreferences;
private SharedPreferences usageSharedPreferences;
private SwipeRefreshLayout swipeRefreshLayout;
private LinearLayout baseUsageView;
private RelativeLayout errorLayout;
private ExtendedScrollView scrollView;
private UpdateFragment updateFragment;
private TextView lastRefreshed;
/**
* Called when the activity is first created or re-created due to configuration change. e.g. device rotation
*
* @param savedInstanceState Saved Bundle
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
refreshedOnStart = savedInstanceState.getBoolean("refreshed_on_start", false);
refreshDoneSinceLoadingPersistedData = savedInstanceState.getBoolean("loaded_persisted_on_start", false);
}
PreferenceManager.setDefaultValues(this, R.xml.settings, false);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
usageSharedPreferences = getSharedPreferences("damo.three.ie.previous_usage", Context.MODE_PRIVATE);
// Register or clear background update alarms depending if they are enabled or not.
boolean backgroundUpdate = sharedPreferences.getBoolean(getString(R.string.backgroundupdate), true);
UsageUtils.setupBackgroundUpdateAlarms(getApplicationContext(), backgroundUpdate);
ActionBar actionBar = getSupportActionBar();
actionBar.show();
setContentView(R.layout.main_usage_layout);
lastRefreshed = (TextView) findViewById(R.id.textview_last_refreshed);
lastRefreshed.setOnClickListener(new OnLastRefreshedTextViewClickListener());
errorLayout = (RelativeLayout) findViewById(R.id.error_layout);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container);
swipeRefreshLayout.setOnRefreshListener(new OnSwipeRefreshLayoutListener());
swipeRefreshLayout.setColorSchemeResources(R.color.holo_purple, R.color.holo_green_light,
R.color.holo_orange_light, R.color.holo_red_light);
scrollView = (ExtendedScrollView) findViewById(R.id.usage_scroll_view);
scrollView.setOnScrollViewListener(new ExtendedScrollView.OnScrollViewListener() {
@Override
public void onScrollChanged(ExtendedScrollView v, int l, int t, int oldl, int oldt) {
View view = scrollView.getChildAt(0);
// If we are at top of scrollview, enable the swipe refresh layout again, else keep disabled to prevent
// scrolling the scrollview triggering a refresh.
if (view.getTop() == scrollView.getScrollY()) {
swipeRefreshLayout.setEnabled(true);
} else {
swipeRefreshLayout.setEnabled(false);
}
}
});
baseUsageView = (LinearLayout) findViewById(R.id.usage_layout);
// maybe user rotated the device and fragment already exists?
FragmentManager fm = getSupportFragmentManager();
updateFragment = (UpdateFragment) fm.findFragmentByTag("usage_fetcher");
if (updateFragment == null) {
updateFragment = new UpdateFragment();
// consider that activity may be destroyed
fm.beginTransaction().add(updateFragment, "usage_fetcher").commitAllowingStateLoss();
}
// if we had already fetched usages, show them on the newly created activity
if (updateFragment.getItems() != null) {
displayUsages(updateFragment.getItems());
updateLastRefreshedTextView(new PrettyTime().format(new Date((updateFragment.getDateTime().getMillis()))));
}
//####################################################################################
/**
* Temporary bug workaround for:
* https://code.google.com/p/android/issues/detail?id=77712
*/
TypedValue typed_value = new TypedValue();
getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.actionBarSize, typed_value, true);
swipeRefreshLayout.setProgressViewOffset(false, 0,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources().getDisplayMetrics()));
//####################################################################################
/**
* if screen was rotated and Activity was re-created while we were fetching usage info, then enable the swipe
* refresh animation.
*/
if (updateFragment.isWorking()) {
swipeRefreshLayout.setRefreshing(true);
working = true;
}
}
@Override
protected void onResume() {
super.onResume();
/**
* If previous usage info was persisted, show it. If we refreshed usages since opening app, then don't fall in
* here as we will get the usages from our AccountProcessorFragment in onCreate() instead.
*/
if (!refreshDoneSinceLoadingPersistedData) {
loadPersistedUsages();
}
/**
* refresh usage on startup ?
* Check if we already refreshed. Activity is re-created each rotate, so checked persisted value we stored
* onSaveInstanceState()
*/
if ((sharedPreferences.getBoolean("refresh", false)) && (!refreshedOnStart)) {
getCreditInfo();
refreshedOnStart = true;
}
}
/**
* Load usages if we have them persisted.
*/
private void loadPersistedUsages() {
String usage = usageSharedPreferences.getString("usage_info", null);
// first check if anything was persisted
if (usage != null) {
List<UsageItem> usageItems = JSONUtils.jsonToUsageItems(usage);
// check array size in-case it was just an empty json string stored
if (usageItems != null && usageItems.size() > 0) {
updateLastRefreshedTextView(new PrettyTime().format(new Date((usageSharedPreferences.getLong
("last_refreshed_milliseconds", 0L)))));
displayUsages(usageItems);
}
}
}
/**
* Update the LastRefreshed TextView
*
* @param last String representation of a date when a last refresh was performed
*/
private void updateLastRefreshedTextView(String last) {
// non line breaking space appended to the end to prevent last italic char been clipped
lastRefreshed.setText("Last refreshed " + last + "\u00A0");
lastRefreshed.setVisibility(View.VISIBLE);
}
/**
* Activity is going down. Save data that we want to reload when the activity is re-created.
*
* @param outState State to be saved
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("refreshed_on_start", refreshedOnStart);
// signal to possibly re-load on rotate as onResume() is called each configuration change
outState.putBoolean("loaded_persisted_on_start", false);
}
/**
* Setup the action icon's for the ActionBar.
*
* @param menu Menu
* @return boolean
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
if (!working) {
getCreditInfo();
}
return true;
case R.id.menu_settings:
/**
* Needed as once off. If user enables refresh on start, refresh
* would happen when they close SettingsActivity as onResume() here
* is called as soon as user closes SettingsActivity
*/
//refreshedOnStart = true;
Intent settings = new Intent(this, SettingsActivity.class);
startActivity(settings);
return true;
case R.id.menu_about:
Intent about = new Intent(this, AboutActivity.class);
startActivity(about);
return true;
case R.id.menu_my3_website:
goToMy3Website();
return true;
case R.id.menu_logout:
logOut();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
// Clear credentials, stored usage info, cookies and go back to login screen.
private void logOut() {
SharedPreferences.Editor sharedPrefsEditor = sharedPreferences.edit();
sharedPrefsEditor.remove("mobile");
sharedPrefsEditor.remove("password");
sharedPrefsEditor.commit();
SharedPreferences.Editor usageSharedPrefsEditor = usageSharedPreferences.edit();
usageSharedPrefsEditor.remove("last_refreshed_milliseconds");
usageSharedPrefsEditor.remove("usage_info");
usageSharedPrefsEditor.commit();
try {
ThreeHttpClient.getInstance(getApplicationContext()).getHttpClient().getCookieStore().clear();
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Intent i = new Intent(this, LoginActivity.class);
startActivity(i);
finish();
}
private void goToMy3Website() {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(Constants.MY3_MAIN_PAGE));
startActivity(i);
}
/**
* Initiate the request to get users usage information
*/
private void getCreditInfo() {
if (sharedPreferences.getString("mobile", "").equals("") ||
sharedPreferences.getString("password", "").equals("")) {
showWarning(getString(R.string.no_account_credentials));
} else {
working = true;
clearErrorLayout();
if (!swipeRefreshLayout.isRefreshing()) {
swipeRefreshLayout.setRefreshing(true);
}
updateFragment.execute();
}
}
/**
* Callback function to handle when usage has been retrieved and parsed
*/
@Override
public void onAccountUsageReceived() {
List<UsageItem> usageItems = updateFragment.getItems();
if (usageItems != null) {
updateLastRefreshedTextView(new PrettyTime().format(new Date(updateFragment.getDateTime().getMillis())));
displayUsages(usageItems);
}
swipeRefreshLayout.setRefreshing(false);
working = false;
}
/**
* Exception callback receiver
*
* @param exception Exception from fetching usages
*/
@Override
public void onAccountUsageExceptionReceived(Exception exception) {
working = false;
swipeRefreshLayout.setRefreshing(false);
if ((exception instanceof IOException) || (exception instanceof PrepayException)) {
showCriticalError(exception);
} else {
showWarning(exception);
}
}
/**
* Show an message. Message shows until user exits it, or refreshes again.
*/
private void showCriticalError(Exception exception) {
String msg = String.format(getString(R.string.my3_connection_error), exception.getLocalizedMessage());
setupErrorLayout(msg, new OnErrorCloseClickListener(), View.VISIBLE, new OnErrorLayoutClickListener(), 0);
}
/**
* Setup an error layout based on supplied criteria.
*
* @param msg Message to show.
* @param imgButtonOnClickListener OnClickListener when user clicks X button. Supply null for no action.
* @param imgButtonVisible Close button is visible or not.
* @param errorLayoutOnClickListener OnClickListener when user clicks layout. Supply null for no action.
* @param duration Duration (in seconds) for the layout to retain on screen before disappearing.
* Use 0 to disable.
*/
private void setupErrorLayout(String msg, View.OnClickListener imgButtonOnClickListener, int imgButtonVisible,
View.OnClickListener errorLayoutOnClickListener, int duration) {
TextView errorTextView = (TextView) findViewById(R.id.error_text);
errorTextView.setText(msg);
ImageButton imageButton = (ImageButton) errorLayout.findViewById(R.id.error_close_button);
imageButton.setOnClickListener(imgButtonOnClickListener);
imageButton.setVisibility(imgButtonVisible);
errorLayout.setOnClickListener(errorLayoutOnClickListener);
errorLayout.setVisibility(View.VISIBLE);
if (duration > 0) {
Handler handler = new Handler();
Runnable runnable = new Runnable() {
@Override
public void run() {
clearErrorLayout();
}
};
handler.removeCallbacks(runnable);
handler.postDelayed(runnable, 5 * 1000);
}
}
/**
* Show a warning message. Behaviour is to disappear after a few seconds.
*/
private void showWarning(String msg) {
setupErrorLayout(msg, null, View.INVISIBLE, null, 5);
}
private void showWarning(Exception exception) {
showWarning(exception.getLocalizedMessage());
}
private void clearErrorLayout() {
errorLayout.setVisibility(View.GONE);
}
/**
* Display the users usages.
*
* @param usageItems Usages Retrieved
*/
private void displayUsages(List<UsageItem> usageItems) {
if (usageItems != null) {
baseUsageView.removeAllViews();
// Out of bundle items
List<OutOfBundle> outOfBundleItems = UsageUtils.getAllOutOfBundleItems(usageItems);
if (outOfBundleItems.size() > 0) {
OutOfBundleLayout outOfBundleLayout = new OutOfBundleLayout(this, outOfBundleItems);
baseUsageView.addView(outOfBundleLayout);
}
// Basic usage items
List<BasicUsageItem> basicUsageItems = UsageUtils.getAllBasicItems(usageItems);
List<BasicUsageItemsGrouped> basicUsageItemsGrouped = UsageUtils.groupUsages(basicUsageItems);
for (BasicUsageItemsGrouped b : basicUsageItemsGrouped) {
/**
* check if usage is already expired (cached usages maybe no-longer relevant if the user hasn't
* refreshed in some time).
*/
if (b.isNotExpired()) {
BasicUsageLayout l = new BasicUsageLayout(getBaseContext(), b);
baseUsageView.addView(l);
}
}
refreshDoneSinceLoadingPersistedData = true;
}
}
/**
* Listener class. Action to perform when the error layout is clicked.
*/
private class OnErrorLayoutClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
errorLayout.setVisibility(View.GONE);
goToMy3Website();
}
}
/**
* Listener class. Action to perform when the user 'swipes' down to refresh.
*/
private class OnSwipeRefreshLayoutListener implements SwipeRefreshLayout.OnRefreshListener {
@Override
public void onRefresh() {
if (!working) {
getCreditInfo();
}
}
}
/**
* Listener class. Action to perform when the close button on error layout is clicked.
*/
private class OnErrorCloseClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
errorLayout.setVisibility(View.GONE);
}
}
private class OnLastRefreshedTextViewClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
long lastRefreshed = usageSharedPreferences.getLong("last_refreshed_milliseconds", 0L);
if (lastRefreshed > 0) {
Toast.makeText(getApplicationContext(), "Last refreshed on " + DateUtils.formatDateTime
(lastRefreshed), Toast.LENGTH_LONG).show();
}
}
}
}