Java tutorial
/* * Copyright (C) 2009 Johan Nilsson <http://markupartist.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.markupartist.sthlmtraveling; import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.ViewPager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager.BadTokenException; import android.widget.ArrayAdapter; import android.widget.ProgressBar; import android.widget.TextView; import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.app.SherlockListFragment; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.Window; import com.markupartist.sthlmtraveling.provider.HistoryDbAdapter; import com.markupartist.sthlmtraveling.provider.PlacesProvider.Place.Places; import com.markupartist.sthlmtraveling.provider.TransportMode; import com.markupartist.sthlmtraveling.provider.departure.DeparturesStore; import com.markupartist.sthlmtraveling.provider.departure.DeparturesStore.Departure; import com.markupartist.sthlmtraveling.provider.departure.DeparturesStore.Departures; import com.markupartist.sthlmtraveling.provider.site.Site; import com.markupartist.sthlmtraveling.provider.site.SitesStore; import java.io.IOException; import java.util.ArrayList; public class DeparturesActivity extends BaseFragmentActivity { static String EXTRA_SITE_NAME = "com.markupartist.sthlmtraveling.siteName"; static String EXTRA_SITE = "com.markupartist.sthlmtraveling.site"; private static final String STATE_GET_SITES_IN_PROGRESS = "com.markupartist.sthlmtraveling.getsites.inprogress"; private static final String STATE_GET_DEPARTURES_IN_PROGRESS = "com.markupartist.sthlmtraveling.getdepartures.inprogress"; private static final String STATE_SITE = "com.markupartist.sthlmtraveling.site"; private static final String STATE_PREFERRED_TRANSPORT_MODE = "com.markupartist.sthlmtraveling.departures.transportmode"; static String TAG = "DeparturesActivity"; private static final int DIALOG_SITE_ALTERNATIVES = 0; private static final int DIALOG_GET_SITES_NETWORK_PROBLEM = 1; private static final int DIALOG_GET_DEPARTURES_NETWORK_PROBLEM = 3; private Site mSite; private static ArrayList<Site> mSiteAlternatives; private ProgressDialog mProgress; private GetSitesTask mGetSitesTask; private GetDeparturesTask mGetDeparturesTask; private String mSiteName; private int mSiteId; private Departures mDepartureResult; private Bundle mSavedState; //private DepartureAdapter mSectionedAdapter; private int mPreferredTrafficMode = TransportMode.METRO_INDEX; private int mPlaceId = -1; private HistoryDbAdapter mHistoryDbAdapter; private ViewPager mPager; private PageFragmentAdapter mPageAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.departures); registerScreen("Departures"); Bundle extras = getIntent().getExtras(); if (extras.containsKey(EXTRA_SITE)) { mSite = extras.getParcelable(EXTRA_SITE); setTitle(mSite.getName()); } else if (extras.containsKey(EXTRA_SITE_NAME)) { mSiteName = extras.getString(EXTRA_SITE_NAME); setTitle(mSiteName); } else { Log.e(TAG, "Could not open activity, missing site id or name."); finish(); } mPageAdapter = new PageFragmentAdapter(this, getSupportFragmentManager()); mPageAdapter.setCount(4); Bundle metroArg = new Bundle(); metroArg.putInt(DepartureFragment.ARG_TRANSPORT_TYPE, TransportMode.METRO_INDEX); mPageAdapter.addPage(new PageInfo(getString(R.string.departure_metro), DepartureFragment.class, metroArg)); Bundle busArg = new Bundle(); busArg.putInt(DepartureFragment.ARG_TRANSPORT_TYPE, TransportMode.BUS_INDEX); mPageAdapter.addPage(new PageInfo(getString(R.string.departure_bus), DepartureFragment.class, busArg)); Bundle trainArg = new Bundle(); trainArg.putInt(DepartureFragment.ARG_TRANSPORT_TYPE, TransportMode.TRAIN_INDEX); mPageAdapter.addPage(new PageInfo(getString(R.string.departures), DepartureFragment.class, trainArg)); Bundle tramArg = new Bundle(); tramArg.putInt(DepartureFragment.ARG_TRANSPORT_TYPE, TransportMode.LOKALBANA_INDEX); mPageAdapter.addPage(new PageInfo(getString(R.string.deviations_label), DepartureFragment.class, tramArg)); mPager = (ViewPager) findViewById(R.id.pager); mPager.setPageMarginDrawable(R.color.light_grey); mPager.setPageMargin(25); // TODO: Compensate with denisity to get it right on all screens mPager.setAdapter(mPageAdapter); mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { mPreferredTrafficMode = position; getSupportActionBar().setSelectedNavigationItem(position); } }); initActionBar(); // Not ideal. mHistoryDbAdapter = new HistoryDbAdapter(this).open(); } @Override protected ActionBar initActionBar() { ActionBar ab = super.initActionBar(); ab.setDisplayHomeAsUpEnabled(true); ab.setDisplayUseLogoEnabled(false); ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); ActionBar.TabListener tabListener = new ActionBar.TabListener() { public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { mPager.setCurrentItem(tab.getPosition()); mPreferredTrafficMode = tab.getPosition(); } public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { } public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { } }; ab.addTab(ab.newTab().setIcon(R.drawable.transport_metro).setTabListener(tabListener)); ab.addTab(ab.newTab().setIcon(R.drawable.transport_bus).setTabListener(tabListener)); ab.addTab(ab.newTab().setIcon(R.drawable.transport_train).setTabListener(tabListener)); ab.addTab(ab.newTab().setIcon(R.drawable.transport_tram_car).setTabListener(tabListener)); return ab; } private void selectPreferredTransport() { int selectedItem = 0; switch (mPreferredTrafficMode) { case TransportMode.METRO_INDEX: selectedItem = 0; break; case TransportMode.BUS_INDEX: selectedItem = 1; break; case TransportMode.TRAIN_INDEX: selectedItem = 2; break; case TransportMode.LOKALBANA_INDEX: selectedItem = 3; break; } mPager.setCurrentItem(selectedItem, true); getSupportActionBar().setSelectedNavigationItem(selectedItem); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getSupportMenuInflater(); inflater.inflate(R.menu.actionbar_departures, menu); return true; } @Override public void setTitle(CharSequence title) { getSupportActionBar().setTitle(title); } @Override public void onBackPressed() { super.onBackPressed(); overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: onBackPressed(); return true; case R.id.actionbar_item_refresh: new GetDeparturesTask().execute(mSite); return true; } return super.onOptionsItemSelected(item); } /** * We need to call loadDeapartures after restoreLocalState that's * why we need to override this method. Only needed for 1.5 devices though. * * @see android.app.Activity#onPostCreate(android.os.Bundle) */ @Override protected void onPostCreate(Bundle savedInstanceState) { loadDepartures(); super.onPostCreate(savedInstanceState); } private void loadDepartures() { @SuppressWarnings("unchecked") // final Departures departureResult = // (Departures) getLastNonConfigurationInstance(); Departures departureResult = null; if (departureResult != null) { onData(departureResult); } else if (mSite != null && mSite.getId() > 0) { mGetDeparturesTask = new GetDeparturesTask(); mGetDeparturesTask.execute(mSite); } else { // Handle legacy history that does not have the site id set. if (mSite != null) { mSiteName = mSite.getName(); } mGetSitesTask = new GetSitesTask(); mGetSitesTask.execute(mSiteName); } } @Override protected void onResume() { super.onResume(); if (mSavedState != null) restoreLocalState(mSavedState); } @Override protected void onPause() { super.onPause(); onCancelGetDeparturesTask(); onCancelGetSitesTask(); dismissProgress(); int preferredTransportType = mPreferredTrafficMode; // TODO: Do in background thread. if (mSite != null) { if (mPlaceId == -1) { ContentValues values = new ContentValues(); values.put(Places.NAME, mSite.getName()); values.put(Places.SITE_ID, mSite.getId()); values.put(Places.PREFERRED_TRANSPORT_MODE, preferredTransportType); Uri uri = getContentResolver().insert(Places.CONTENT_URI, values); } else { ContentValues values = new ContentValues(); values.put(Places.NAME, mSite.getName()); values.put(Places.SITE_ID, mSite.getId()); values.put(Places.PREFERRED_TRANSPORT_MODE, preferredTransportType); int updated = getContentResolver().update(Places.CONTENT_URI, values, Places.SITE_ID + "= ?", new String[] { String.valueOf(mSite.getId()) }); } } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mSite != null) { outState.putParcelable(STATE_SITE, mSite); } outState.putInt(STATE_PREFERRED_TRANSPORT_MODE, mPreferredTrafficMode); saveGetSitesTask(outState); saveGetDeparturesTask(outState); mSavedState = outState; } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); restoreLocalState(savedInstanceState); mSavedState = null; } /** * Restores any local state, if any. * * @param savedInstanceState the bundle containing the saved state */ private void restoreLocalState(Bundle savedInstanceState) { if (savedInstanceState.containsKey(STATE_SITE)) { mSite = savedInstanceState.getParcelable(STATE_SITE); } if (savedInstanceState.containsKey(STATE_PREFERRED_TRANSPORT_MODE)) { mPreferredTrafficMode = savedInstanceState.getInt(STATE_PREFERRED_TRANSPORT_MODE); } restoreGetSitesTask(savedInstanceState); restoreGetDeparturesTask(savedInstanceState); } /** * Cancels the {@link GetSitesTask} if it is running. */ private void onCancelGetSitesTask() { if (mGetSitesTask != null && mGetSitesTask.getStatus() == AsyncTask.Status.RUNNING) { Log.i(TAG, "Cancels GetSitesTask."); mGetSitesTask.cancel(true); mGetSitesTask = null; } } /** * Restores the {@link GetSitesTask}. * * @param savedInstanceState the saved state */ private void restoreGetSitesTask(Bundle savedInstanceState) { if (savedInstanceState.getBoolean(STATE_GET_SITES_IN_PROGRESS)) { mSiteName = savedInstanceState.getString(EXTRA_SITE_NAME); Log.d(TAG, "restoring getSitesTask"); mGetSitesTask = new GetSitesTask(); mGetSitesTask.execute(mSiteName); } } /** * Saves the state of {@link GetSitesTask}. * * @param outState */ private void saveGetSitesTask(Bundle outState) { final GetSitesTask task = mGetSitesTask; if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) { Log.d(TAG, "saving GetSitesTask"); task.cancel(true); mGetSitesTask = null; outState.putBoolean(STATE_GET_SITES_IN_PROGRESS, true); outState.putString(EXTRA_SITE_NAME, mSiteName); } } /** * Cancels the {@link GetDeparturesTask} if it is running. */ private void onCancelGetDeparturesTask() { if (mGetDeparturesTask != null/* && mGetDeparturesTask.getStatus() == AsyncTask.Status.RUNNING*/) { Log.i(TAG, "Cancels GetDeparturesTask."); mGetDeparturesTask.cancel(true); mGetDeparturesTask = null; } } /** * Restores the {@link GetDeparturesTask}. * * @param savedInstanceState the saved state */ private void restoreGetDeparturesTask(Bundle savedInstanceState) { if (savedInstanceState.getBoolean(STATE_GET_DEPARTURES_IN_PROGRESS)) { Log.d(TAG, "restoring getSitesTask"); mGetDeparturesTask = new GetDeparturesTask(); mGetDeparturesTask.execute(mSite); } } /** * Saves the state of {@link GetDeparturesTask}. * * @param outState */ private void saveGetDeparturesTask(Bundle outState) { final GetDeparturesTask task = mGetDeparturesTask; if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) { Log.d(TAG, "saving GetDeparturesState"); task.cancel(true); mGetSitesTask = null; outState.putBoolean(STATE_GET_DEPARTURES_IN_PROGRESS, true); } } @Override protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_SITE_ALTERNATIVES: ArrayAdapter<Site> siteAdapter = new ArrayAdapter<Site>(this, R.layout.simple_dropdown_item_1line, mSiteAlternatives); return new AlertDialog.Builder(this).setTitle(R.string.did_you_mean) .setAdapter(siteAdapter, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { new GetDeparturesTask().execute(mSiteAlternatives.get(which)); } }).setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { // We need a site to proceed, finish the activity here // is most likely what the user intended. finish(); } }).create(); case DIALOG_GET_SITES_NETWORK_PROBLEM: return DialogHelper.createNetworkProblemDialog(this, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mGetSitesTask = new GetSitesTask(); mGetSitesTask.execute(mSiteName); } }); case DIALOG_GET_DEPARTURES_NETWORK_PROBLEM: return DialogHelper.createNetworkProblemDialog(this, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mGetDeparturesTask = new GetDeparturesTask(); mGetDeparturesTask.execute(mSite); } }); } return null; } /** * Show progress dialog. */ private void showProgress() { setSupportProgressBarIndeterminateVisibility(true); } /** * Dismiss the progress dialog. */ private void dismissProgress() { setSupportProgressBarIndeterminateVisibility(false); } @Override protected void onDestroy() { super.onDestroy(); onCancelGetSitesTask(); onCancelGetDeparturesTask(); dismissProgress(); } @Override public boolean onSearchRequested() { Intent i = new Intent(this, StartActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(i); return true; } /** * Background job for getting {@link Site}s. */ private class GetSitesTask extends AsyncTask<String, Void, ArrayList<Site>> { private boolean mWasSuccess = true; private String mSearchQuery; @Override public void onPreExecute() { showProgress(); } @Override protected ArrayList<Site> doInBackground(String... params) { mSearchQuery = params[0]; try { return SitesStore.getInstance().getSite(DeparturesActivity.this, mSearchQuery); } catch (IOException e) { mWasSuccess = false; return null; } } @Override protected void onPostExecute(ArrayList<Site> result) { dismissProgress(); if (result != null && !result.isEmpty()) { if (result.size() == 1) { new GetDeparturesTask().execute(result.get(0)); } else { // Has exact match? for (Site site : result) { if (site.getName().equals(mSearchQuery)) { new GetDeparturesTask().execute(result.get(0)); return; } } mSiteAlternatives = result; try { showDialog(DIALOG_SITE_ALTERNATIVES); } catch (BadTokenException e) { Log.w(TAG, "Caught BadTokenException when trying to show sites dialog."); } } } else if (!mWasSuccess) { try { showDialog(DIALOG_GET_SITES_NETWORK_PROBLEM); } catch (BadTokenException e) { Log.w(TAG, "Caught BadTokenException when trying to show network error dialog."); } } else { // onNoRoutesDetailsResult(); } } } /** * Background job for getting {@link Departure}s. */ private class GetDeparturesTask extends AsyncTask<Site, Void, Departures> { private boolean mWasSuccess = true; protected boolean wasSuccess() { return mWasSuccess; } @Override public void onPreExecute() { showProgress(); } @Override protected Departures doInBackground(Site... params) { try { mSite = params[0]; // Update history if site id is not set, we do this to patch old versions // that will end up here without site id set. if (mSite.getId() > 0) { mHistoryDbAdapter.create(HistoryDbAdapter.TYPE_DEPARTURE_SITE, mSite); } DeparturesStore departures = new DeparturesStore(); Departures result = departures.find(DeparturesActivity.this, params[0]); if (mPreferredTrafficMode < 1 && !result.servesTypes.isEmpty()) { String transportMode = result.servesTypes.get(0); mPreferredTrafficMode = TransportMode.getIndex(transportMode); } if (mPlaceId == -1) { String[] projection = new String[] { Places._ID, Places.NAME, Places.PREFERRED_TRANSPORT_MODE, Places.SITE_ID }; Uri sitesUri = Places.CONTENT_URI; Cursor sitesCursor = managedQuery(sitesUri, projection, Places.SITE_ID + "= ?", new String[] { String.valueOf(mSite.getId()) }, Places.NAME + " asc"); if (sitesCursor.moveToFirst()) { mPlaceId = sitesCursor.getInt(sitesCursor.getColumnIndex(Places._ID)); mPreferredTrafficMode = sitesCursor .getInt(sitesCursor.getColumnIndex(Places.PREFERRED_TRANSPORT_MODE)); } } return result; } catch (IllegalArgumentException e) { mWasSuccess = false; return null; } catch (IOException e) { mWasSuccess = false; return null; } } @Override protected void onPostExecute(Departures result) { dismissProgress(); if (mWasSuccess) { onData(result); } else { try { showDialog(DIALOG_GET_DEPARTURES_NETWORK_PROBLEM); } catch (BadTokenException e) { Log.w(TAG, "Caught BadTokenException when trying to show network error dialog."); } } } } private void onData(Departures result) { selectPreferredTransport(); // TODO: Fix this mess. Bundle b0 = mPageAdapter.getPageArgs(0); b0.putSerializable(DepartureFragment.ARG_DEPARTURES, result); mPageAdapter.updatePageArgs(0, b0); Bundle b1 = mPageAdapter.getPageArgs(1); b1.putSerializable(DepartureFragment.ARG_DEPARTURES, result); mPageAdapter.updatePageArgs(1, b1); Bundle b2 = mPageAdapter.getPageArgs(2); b2.putSerializable(DepartureFragment.ARG_DEPARTURES, result); mPageAdapter.updatePageArgs(2, b2); Bundle b3 = mPageAdapter.getPageArgs(3); b3.putSerializable(DepartureFragment.ARG_DEPARTURES, result); mPageAdapter.updatePageArgs(3, b3); updateFragment(0, result); updateFragment(1, result); updateFragment(2, result); updateFragment(3, result); } private void updateFragment(int position, Departures departures) { Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(position)); if (f != null) { ((DepartureFragment) f).update(departures); } } /** * Get fragment by the same naming pattern that ViewPager use internally. * <p/> * We should not do this since might break when their api changes. * * @param pos position of the fragment tag * @return A fragment tag */ private String getFragmentTag(int pos) { return "android:switcher:" + R.id.pager + ":" + pos; } public class PageFragmentAdapter extends FragmentPagerAdapter /*implements TitleProvider*/ { private ArrayList<PageInfo> mPages = new ArrayList<PageInfo>(); private Context mContext; private int mCount; public PageFragmentAdapter(Context activity, FragmentManager fm) { super(fm); mContext = activity; } public void setCount(final int count) { mCount = count; } public Bundle getPageArgs(int position) { return mPages.get(position).mArgs; } public void updatePageArgs(int position, Bundle args) { mPages.get(position).mArgs = args; } public void addPage(PageInfo page) { mPages.add(page); } @Override public Fragment getItem(int position) { PageInfo page = mPages.get(position); return Fragment.instantiate(mContext, page.getFragmentClass().getName(), page.getArgs()); } @Override public int getCount() { return mCount; } @Override public CharSequence getPageTitle(int position) { return mPages.get(position).getTextResource().toUpperCase(); } } public class PageInfo { private String mTextResource; private Class<?> mFragmentClass; private Bundle mArgs; public PageInfo(String textResource, Class<?> fragmentClass, Bundle args) { mTextResource = textResource; mFragmentClass = fragmentClass; mArgs = args; } public String getTextResource() { return mTextResource; } public Class<?> getFragmentClass() { return mFragmentClass; } public Bundle getArgs() { return mArgs; } } public static class DepartureFragment extends SherlockListFragment { public static String ARG_DEPARTURES = "ARG_DEPARTURES"; public static String ARG_TRANSPORT_TYPE = "ARG_TRANSPORT_TYPE"; private DepartureAdapter mSectionedAdapter; private Departures mDepartureResult; public DepartureFragment() { setRetainInstance(true); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); restoreDepartures(savedInstanceState); updateViews(); } private void restoreDepartures(Bundle savedInstanceState) { if (savedInstanceState != null) { mDepartureResult = (Departures) savedInstanceState.getSerializable(ARG_DEPARTURES); } if (mDepartureResult == null) { Bundle args = getArguments(); if (args != null) { mDepartureResult = (Departures) args.getSerializable(ARG_DEPARTURES); } } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.departures_list, container, false); return view; } private void updateViews() { mSectionedAdapter = new DepartureAdapter(getActivity()); View emptyView = getListView().getEmptyView(); getListView().setEmptyView(emptyView); if (mDepartureResult != null) { TextView text = (TextView) emptyView.findViewById(R.id.search_progress_text); text.setText(R.string.no_departures_for_transport_type); ProgressBar progressBar = (ProgressBar) emptyView.findViewById(R.id.search_progress_bar); progressBar.setVisibility(View.GONE); mSectionedAdapter.fillDepartures(mDepartureResult, getArguments().getInt(ARG_TRANSPORT_TYPE)); } setListAdapter(mSectionedAdapter); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mDepartureResult != null) { outState.putSerializable(ARG_DEPARTURES, mDepartureResult); } } public void update(Departures result) { mDepartureResult = result; if (isVisible()) { updateViews(); } } } }