Java tutorial
/* * 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(); } } } }