Java tutorial
/* * enviroCar 2013 * Copyright (C) 2013 * Martin Dueren, Jakob Moellers, Gerald Pape, Christopher Stephan * * 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, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ package org.envirocar.app.activity; import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import org.envirocar.app.R; import org.envirocar.app.application.ContextInternetAccessProvider; import org.envirocar.app.application.ECApplication; import org.envirocar.app.application.TermsOfUseManager; import org.envirocar.app.application.TrackUploadFinishedHandler; import org.envirocar.app.application.UploadManager; import org.envirocar.app.application.UserManager; import org.envirocar.app.dao.DAOProvider; import org.envirocar.app.dao.DAOProvider.AsyncExecutionWithCallback; import org.envirocar.app.dao.exception.DAOException; import org.envirocar.app.dao.exception.NotConnectedException; import org.envirocar.app.dao.TrackDAO; import org.envirocar.app.exception.FuelConsumptionException; import org.envirocar.app.exception.MeasurementsException; import org.envirocar.app.exception.ServerException; import org.envirocar.app.json.TrackWithoutMeasurementsException; import org.envirocar.app.logging.Logger; import org.envirocar.app.model.Car; import org.envirocar.app.model.TermsOfUseInstance; import org.envirocar.app.model.User; import org.envirocar.app.network.WPSClient; import org.envirocar.app.network.WPSClient.ResultCallback; import org.envirocar.app.protocol.algorithm.UnsupportedFuelTypeException; import org.envirocar.app.storage.DbAdapter; import org.envirocar.app.storage.DbAdapterImpl; import org.envirocar.app.storage.Measurement; import org.envirocar.app.storage.RemoteTrack; import org.envirocar.app.storage.Track; import org.envirocar.app.util.NamedThreadFactory; import org.envirocar.app.util.Util; import org.envirocar.app.views.TypefaceEC; import org.json.JSONException; import android.annotation.SuppressLint; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Color; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.preference.PreferenceManager; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.BaseExpandableListAdapter; import android.widget.EditText; import android.widget.ExpandableListView; import android.widget.ImageView; import android.widget.TextView; import com.actionbarsherlock.app.SherlockFragment; import com.actionbarsherlock.view.Menu; import de.keyboardsurfer.android.widget.crouton.Crouton; import de.keyboardsurfer.android.widget.crouton.Style; /** * List Fragement that displays local and remote tracks. * @author jakob * @author gerald * */ public class ListTracksFragment extends SherlockFragment { // Measurements and tracks private List<Track> tracksList; private TracksListAdapter trackListAdapter; private DbAdapter dbAdapter; // UI Elements private ExpandableListView trackListView; private int itemSelect; private Menu menu; private TextView statusText; private View statusProgressBar; private AtomicInteger remoteTrackCount = new AtomicInteger(-1); protected static final Logger logger = Logger.getLogger(ListTracksFragment.class); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); dbAdapter = DbAdapterImpl.instance(); } public View onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup container, android.os.Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); setHasOptionsMenu(true); View v = inflater.inflate(R.layout.list_tracks_layout, null); trackListView = (ExpandableListView) v.findViewById(R.id.list); statusProgressBar = v.findViewById(R.id.list_tracks_status_progress); statusText = (TextView) v.findViewById(R.id.list_tracks_status_text); setProgressStatusText(R.string.fetching_tracks); trackListView.setEmptyView(v.findViewById(R.id.empty)); registerForContextMenu(trackListView); trackListView.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { itemSelect = ExpandableListView.getPackedPositionGroup(id); logger.info(String.valueOf("Selected item: " + itemSelect)); return false; } }); return v; }; @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); logger.info("Create view ListTracksFragment"); super.onViewCreated(view, savedInstanceState); trackListView.setGroupIndicator(getResources().getDrawable(R.drawable.group_indicator)); trackListView.setChildDivider(getResources().getDrawable(android.R.color.transparent)); tracksList = new ArrayList<Track>(); if (trackListAdapter == null) { trackListAdapter = new TracksListAdapter(); trackListView.setAdapter(trackListAdapter); } startTracksRetrieval(); } @Override public void onCreateOptionsMenu(Menu menu, com.actionbarsherlock.view.MenuInflater inflater) { inflater.inflate(R.menu.menu_tracks, (com.actionbarsherlock.view.Menu) menu); super.onCreateOptionsMenu(menu, inflater); } @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); this.menu = menu; } private void updateUsabilityOfMenuItems() { if (menu == null) return; if (!isAdded()) return; getActivity().runOnUiThread(new Runnable() { @Override public void run() { Menu theMenu = menu; if (theMenu == null) { return; } if (dbAdapter.getAllLocalTracks().size() > 0) { setItemEnabled(theMenu.findItem(R.id.menu_delete_all), true); setItemEnabled(theMenu.findItem(R.id.menu_upload), UserManager.instance().isLoggedIn()); } else { setItemEnabled(theMenu.findItem(R.id.menu_upload), false); setItemEnabled(theMenu.findItem(R.id.menu_delete_all), false); } } private void setItemEnabled(com.actionbarsherlock.view.MenuItem item, boolean b) { if (item != null) { item.setEnabled(b); } } }); } /** * Method to remove all tracks of the logged in user from the listview and from the internal database. * Tracks which are locally on the device, are not removed. */ public void clearRemoteTracks() { //remove tracks in a safe way Iterator<Track> trackIterator = tracksList.iterator(); while (trackIterator.hasNext()) { Track track = (Track) trackIterator.next(); if (!track.isLocalTrack()) { trackIterator.remove(); } } dbAdapter.deleteAllRemoteTracks(); updateTrackListView(); } public void notifyDataSetChanged(Track track) { updateUsabilityOfMenuItems(); updateTrackListView(); } /** * Edit all tracks */ @Override public boolean onOptionsItemSelected(com.actionbarsherlock.view.MenuItem item) { switch (item.getItemId()) { //Upload all tracks case R.id.menu_upload: if (UserManager.instance().isLoggedIn()) { startTrackUpload(true, null); } else { Crouton.showText(getActivity(), R.string.hint_login_first, Style.INFO); } return true; //Delete all tracks case R.id.menu_delete_all: DbAdapterImpl.instance().deleteAllLocalTracks(); Crouton.makeText(getActivity(), R.string.not_yet_supported, Style.CONFIRM).show(); return true; } return false; } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); MenuInflater inflater = getSherlockActivity().getMenuInflater(); final Track track = tracksList.get(itemSelect); if (track.isLocalTrack()) { inflater.inflate(R.menu.context_item, menu); } else { inflater.inflate(R.menu.context_item_remote, menu); } } /** * Change one item */ @Override public boolean onContextItemSelected(MenuItem item) { final Track track; synchronized (this) { track = tracksList.get(itemSelect); } switch (item.getItemId()) { // Edit the trackname case R.id.editName: if (track.isLocalTrack()) { logger.info("editing track: " + itemSelect); final EditText input = new EditText(getActivity()); new AlertDialog.Builder(getActivity()).setTitle(getString(R.string.editTrack)) .setMessage(getString(R.string.enterTrackName)).setView(input) .setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String value = input.getText().toString(); logger.info("New name: " + value.toString()); track.setName(value); dbAdapter.updateTrack(track); tracksList.get(itemSelect).setName(value); updateTrackListView(); Crouton.showText(getActivity(), getString(R.string.nameChanged), Style.INFO); } }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // Do nothing. } }).show(); } else { Crouton.showText(getActivity(), R.string.not_possible_for_remote, Style.INFO); } return true; // Edit the track description case R.id.editDescription: if (track.isLocalTrack()) { logger.info("editing track: " + itemSelect); final EditText input2 = new EditText(getActivity()); new AlertDialog.Builder(getActivity()).setTitle(getString(R.string.editTrack)) .setMessage(getString(R.string.enterTrackDescription)).setView(input2) .setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String value = input2.getText().toString(); logger.info("New description: " + value.toString()); track.setDescription(value); dbAdapter.updateTrack(track); trackListView.collapseGroup(itemSelect); updateTrackListView(); trackListAdapter.updateTrackChildView(track); Crouton.showText(getActivity(), getString(R.string.descriptionChanged), Style.INFO); } }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // Do nothing. } }).show(); } else { Crouton.showText(getActivity(), R.string.not_possible_for_remote, Style.INFO); } return true; // Show that track in the map case R.id.startMap: logger.info("Show in Map"); logger.info(Environment.getExternalStorageDirectory().toString()); File f = new File(Environment.getExternalStorageDirectory() + "/Android"); if (f.isDirectory()) { // if (track.isLazyLoadingMeasurements()) { // dbAdapter.loadMeasurements(track); // } List<Measurement> measurements = track.getMeasurements(); logger.info("Count of measurements in the track: " + String.valueOf(measurements.size())); String[] trackCoordinates = extractCoordinates(measurements); if (trackCoordinates.length != 0) { logger.info(String.valueOf(trackCoordinates.length)); Intent intent = new Intent(getActivity().getApplicationContext(), org.envirocar.app.activity.Map.class); Bundle bundle = new Bundle(); bundle.putStringArray("coordinates", trackCoordinates); intent.putExtras(bundle); startActivity(intent); } else { Crouton.showText(getActivity(), getString(R.string.trackContainsNoCoordinates), Style.INFO); } } else { Crouton.showText(getActivity(), getString(R.string.noSdCard), Style.INFO); } return true; // Delete only selected track case R.id.deleteTrack: /* * we need to check the database if the track might have * transisted to a remote track due to uploading */ Track dbRefTrack = dbAdapter.getTrack(track.getTrackId(), true); if (dbRefTrack.isLocalTrack()) { logger.info("deleting item: " + itemSelect); dbAdapter.deleteTrack(track.getTrackId()); Crouton.showText(getActivity(), getString(R.string.trackDeleted), Style.INFO); tracksList.remove(itemSelect); updateTrackListView(); } else { createRemoteDeleteDialog(track, (RemoteTrack) dbRefTrack); } return true; // Share track case R.id.shareTrack: try { // if (track.isLazyLoadingMeasurements()) { // dbAdapter.loadMeasurements(track); // } Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); sharingIntent.setType("application/json"); Uri shareBody = Uri.fromFile(Util.saveTrackAndReturnFile(track, isObfuscationEnabled()).getFile()); sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "EnviroCar Track " + track.getName()); sharingIntent.putExtra(android.content.Intent.EXTRA_STREAM, shareBody); startActivity(Intent.createChooser(sharingIntent, "Share via")); } catch (JSONException e) { logger.warn(e.getMessage(), e); Crouton.showText(getActivity(), R.string.error_json, Style.ALERT); } catch (IOException e) { logger.warn(e.getMessage(), e); Crouton.showText(getActivity(), R.string.error_io, Style.ALERT); } catch (TrackWithoutMeasurementsException e) { logger.warn(e.getMessage(), e); if (isObfuscationEnabled()) { Crouton.showText(getActivity(), R.string.uploading_track_no_measurements_after_obfuscation_long, Style.ALERT); } else { Crouton.showText(getActivity(), R.string.uploading_track_no_measurements_after_obfuscation_long, Style.ALERT); } } return true; // Upload track case R.id.uploadTrack: if (UserManager.instance().isLoggedIn()) { // if (track.isLazyLoadingMeasurements()) { // dbAdapter.loadMeasurements(track); // } startTrackUpload(false, track); } else { Crouton.showText(getActivity(), R.string.hint_login_first, Style.INFO); } return true; default: return super.onContextItemSelected(item); } } public boolean isObfuscationEnabled() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); return prefs.getBoolean(SettingsActivity.OBFUSCATE_POSITION, false); } /** * starts the uploading mechanism. It asserts the Terms of Use * acceptance state. * * @param all if all local tracks should be uploaded * @param track a single track to upload */ private void startTrackUpload(final boolean all, final Track track) { final User user = UserManager.instance().getUser(); boolean verified = false; try { verified = TermsOfUseManager.verifyTermsUseOfVersion(user.getTouVersion()); } catch (ServerException e) { logger.warn(e.getMessage(), e); Crouton.makeText(getActivity(), getString(R.string.server_error_please_try_later), Style.ALERT).show(); return; } if (!verified) { final TermsOfUseInstance current; try { current = TermsOfUseManager.instance().getCurrentTermsOfUse(); } catch (ServerException e) { logger.warn("This should never happen!", e); return; } DialogUtil.createTermsOfUseDialog(current, user.getTouVersion() == null, new DialogUtil.PositiveNegativeCallback() { @Override public void negative() { logger.info("User did not accept the ToU."); Crouton.makeText(getActivity(), getString(R.string.terms_of_use_info), Style.ALERT) .show(); } @Override public void positive() { TermsOfUseManager.instance().userAcceptedTermsOfUse(user, current.getIssuedDate()); uploadTracks(all, track); } }, getActivity()); } else { uploadTracks(all, track); } } /** * executes the actual track uploading * * @param all if all local tracks should be uploaded * @param track a single track to upload */ private void uploadTracks(boolean all, Track track) { TrackUploadFinishedHandler callback = new TrackUploadFinishedHandler() { @Override public void onSuccessfulUpload(Track track) { trackListAdapter.updateTrackGroupView(track); remoteTrackCount.getAndIncrement(); updateStatusLayout(); } }; if (all) { new UploadManager(((ECApplication) getActivity().getApplication())).uploadAllTracks(callback); } else { new UploadManager(((ECApplication) getActivity().getApplication())).uploadSingleTrack(track, callback); } } /** * @param track the track object as used in the list adapter * @param dbRefTrack the database reference, representing the most up-to-date version * of the track (might have transisted from local to remote due to upload) */ private void createRemoteDeleteDialog(final Track track, final RemoteTrack dbRefTrack) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage(R.string.deleteRemoteTrackQuestion) .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { final TrackDAO dao = DAOProvider.instance().getTrackDAO(); DAOProvider.async(new AsyncExecutionWithCallback<Void>() { @Override public Void execute() throws DAOException { dao.deleteTrack(dbRefTrack.getRemoteID()); return null; } @Override public Void onResult(Void result, boolean fail, Exception ex) { if (!fail) { dbAdapter.deleteTrack(track.getTrackId()); removeRemoteTrackFromView(track); } else { logger.warn(ex.getMessage(), ex); } return null; } }); } }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // do nothing } }); builder.create().show(); } private void removeRemoteTrackFromView(final Track track) { if (tracksList.remove(track)) { updateTrackListView(); getActivity().runOnUiThread(new Runnable() { @Override public void run() { Crouton.showText(getActivity(), getString(R.string.remoteTrackDeleted), Style.INFO); } }); } } /** * Returns an StringArray of coordinates for the mpa * * @param measurements * arraylist with all measurements * @return string array with coordinates */ private String[] extractCoordinates(List<Measurement> measurements) { ArrayList<String> coordinates = new ArrayList<String>(); for (Measurement measurement : measurements) { String lat = String.valueOf(measurement.getLatitude()); String lon = String.valueOf(measurement.getLongitude()); coordinates.add(lat); coordinates.add(lon); } return coordinates.toArray(new String[coordinates.size()]); } protected void updateStatusLayout() { if (!isAdded()) return; getActivity().runOnUiThread(new Runnable() { @Override public void run() { statusProgressBar.setVisibility(View.INVISIBLE); statusText.setText(getResources().getString(R.string.track_list_count_text, resolveTrackCount(false), resolveTrackCount(true), createRemoteTrackCountString())); } }); } protected String createRemoteTrackCountString() { if (remoteTrackCount.get() < 0) { return "?"; } if (remoteTrackCount.get() < 100) { return Integer.toString(remoteTrackCount.get()); } return "100+"; } private int resolveTrackCount(boolean remote) { int result = 0; for (Track t : tracksList) { if (t.isRemoteTrack() && remote) { result++; } else if (t.isLocalTrack() && !remote) { result++; } } return result; } protected void setProgressStatusText(int resId) { if (isAdded()) { final String str = getString(resId); getActivity().runOnUiThread(new Runnable() { @Override public void run() { statusText.setText(str); TypefaceEC.applyCustomFont(statusText, TypefaceEC.Newscycle(getActivity())); } }); } } protected void updateTrackListView() { if (!isAdded()) return; synchronized (this) { Collections.sort(tracksList); } getActivity().runOnUiThread(new Runnable() { @Override public void run() { trackListAdapter.notifyDataSetChanged(); } }); } /** * Download remote tracks from the server and include them in the track list */ @SuppressLint("NewApi") private void startTracksRetrieval() { AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { Thread.currentThread().setName("TrackList-TrackRetriever" + Thread.currentThread().getId()); setProgressStatusText(R.string.fetching_tracks_local); //fetch db tracks (local+remote) List<Track> tracks = dbAdapter.getAllTracks(true); for (Track t : tracks) { synchronized (ListTracksFragment.this) { tracksList.add(t); } } updateTrackListView(); logger.info("Number of tracks in the List: " + tracksList.size()); if (UserManager.instance().isLoggedIn()) { setProgressStatusText(R.string.fetching_tracks_remote); downloadTracks(); } else { updateStatusLayout(); } return null; } }; Util.execute(task); } private void downloadTracks() { if (!(new ContextInternetAccessProvider(getActivity()).isConnected())) { updateStatusLayout(); return; } resolveTotalRemoteTrackCount(new AsyncExecutionWithCallback<Void>() { @Override public Void onResult(Void result, boolean fail, Exception exception) { downloadTracks(5, 1); return result; } @Override public Void execute() throws DAOException { return null; } }); } private void resolveTotalRemoteTrackCount(final AsyncExecutionWithCallback<?> callback) { DAOProvider.async(new AsyncExecutionWithCallback<Integer>() { @Override public Integer execute() throws DAOException { return DAOProvider.instance().getTrackDAO().getUserTrackCount(); } @Override public Integer onResult(Integer result, boolean fail, Exception e) { if (!fail) { remoteTrackCount.set(result.intValue()); } else { logger.warn(e.getMessage(), e); } callback.onResult(null, false, null); return null; } }); } private void downloadTracks(final int limit, final int page) { if (!UserManager.instance().isLoggedIn()) { logger.info("cancelling download of tracks: not logged in."); } DAOProvider.async(new AsyncExecutionWithCallback<List<String>>() { @Override public List<String> execute() throws DAOException { return DAOProvider.instance().getTrackDAO().getTrackIds(limit, page); } @Override public List<String> onResult(List<String> trackIdList, boolean fail, Exception exception) { if (!fail) { if (trackIdList.size() == 0) { updateStatusLayout(); } Set<String> localRemoteIds = new HashSet<String>(); synchronized (ListTracksFragment.this) { for (Track t : tracksList) { if (t.isRemoteTrack()) { localRemoteIds.add(((RemoteTrack) t).getRemoteID()); } } } logger.info("found " + localRemoteIds.size() + " local tracks which have remoteIds."); List<String> tracksToDownload = new ArrayList<String>(); for (int i = 0; i < trackIdList.size(); i++) { String remoteId = trackIdList.get(i); // check if track is listed. if, continue if (localRemoteIds.contains(remoteId)) { logger.info("Skipping track with remoteId " + remoteId); } else { tracksToDownload.add(remoteId); } } if (!tracksToDownload.isEmpty()) { logger.info("Starting download of " + tracksToDownload.size() + " tracks"); } for (String trackId : tracksToDownload) { Track t; try { /* * we do not need async, we are still in the outer AsyncTask */ t = DAOProvider.instance().getTrackDAO().getTrack(trackId); DbAdapterImpl.instance().insertTrack(t, true); synchronized (ListTracksFragment.this) { tracksList.add(t); } updateTrackListView(); } catch (NotConnectedException e) { logger.warn(e.getMessage(), e); } } updateStatusLayout(); updateUsabilityOfMenuItems(); } else { logger.warn("Could not retrieve the track ids: " + exception.getMessage(), exception); } return null; } // private synchronized void afterOneTrack() { // // View empty = getView().findViewById(android.R.id.empty); // if (empty != null) { // empty.setVisibility(View.GONE); // } // // if (--unprocessedTrackCount == 0) { // logger.info("Finished fetching tracks."); // updateStatusLayout(); // updateTrackListView(); // updateUsabilityOfMenuItems(); // } // } }); } /** * This method requests the current fuel price of a given fuel type from a * WPS that caches prices from export.benzinpreis-aktuell.de, calculates the * total fuel price using the fuel consumption and length of track and sets * the text of the respective <code>Textview</code>. * * @param fuelCostView * The <code>Textview</code> the fuel price will be written to. * @param twoDForm * Used for rounding the fuel price. * @param t * the track */ private void getEstimatedFuelCost(final TextView fuelCostView, final DecimalFormat twoDForm, Track t) { WPSClient.calculateFuelCosts(t, new ResultCallback<Double>() { @Override public void onResultAvailable(Double result) { if (result.equals(Double.NaN)) { fuelCostView.setText(R.string.error_calculating_fuel_costs); fuelCostView.setTextColor(Color.RED); } else { fuelCostView .setText(twoDForm.format(result) + " " + getActivity().getString(R.string.euro_sign)); } } }); } private class TracksListAdapter extends BaseExpandableListAdapter { private ExecutorService lazyLoader = Executors .newSingleThreadScheduledExecutor(new NamedThreadFactory("MeasurementLazyLoader")); private static final int GROUP_VIEW_BASE_ID = 10000000; private static final int CHILD_VIEW_BASE_ID = 10001000; private java.util.Map<Track, View> trackToGroupViewMap; private Map<Track, View> trackToChildViewMap; public TracksListAdapter() { trackToGroupViewMap = new HashMap<Track, View>(); trackToChildViewMap = new HashMap<Track, View>(); } @Override public int getGroupCount() { return tracksList.size(); } @Override public int getChildrenCount(int i) { return 1; } @Override public Object getGroup(int i) { return tracksList.get(i); } @Override public Object getChild(int i, int i1) { return tracksList.get(i); } @Override public long getGroupId(int i) { return i; } @Override public long getChildId(int i, int i1) { return i; } @Override public boolean hasStableIds() { return true; } public void updateTrackGroupView(final Track t) { View groupRow = trackToGroupViewMap.get(t); if (groupRow == null) { /* * fallback, unknown object id, but could be in the db */ for (Track tmp : trackToGroupViewMap.keySet()) { if (tmp.getTrackId().equals(t.getTrackId())) { groupRow = trackToGroupViewMap.get(tmp); break; } } } final View groupToAdjut = groupRow; if (groupRow != null && isAdded() && getActivity() != null) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { setTrackTypeImage(dbAdapter.getTrack(t.getTrackId(), true), groupToAdjut); groupToAdjut.invalidate(); } }); } } public void updateTrackChildView(final Track t) { View childView = trackToChildViewMap.get(t); if (childView == null) { /* * fallback, unknown object id, but could be in the db */ for (Track tmp : trackToChildViewMap.keySet()) { if (tmp.getTrackId().equals(t.getTrackId())) { childView = trackToChildViewMap.get(tmp); break; } } } final View viewoAdjust = childView; if (viewoAdjust != null) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { setTrackChildFields(t, viewoAdjust); viewoAdjust.invalidate(); } }); } } @Override public View getGroupView(int i, boolean b, View view, ViewGroup viewGroup) { Track t; synchronized (ListTracksFragment.this) { t = tracksList.get(i); if (!trackToGroupViewMap.containsKey(t)) { View groupRow = ViewGroup.inflate(getActivity(), R.layout.list_tracks_group_layout, null); setTrackTypeImage(t, groupRow); groupRow.setId(GROUP_VIEW_BASE_ID + i); TypefaceEC.applyCustomFont((ViewGroup) groupRow, TypefaceEC.Newscycle(getActivity())); trackToGroupViewMap.put(t, groupRow); } } return trackToGroupViewMap.get(t); } private void setTrackTypeImage(Track t, View groupRow) { TextView textView = (TextView) groupRow.findViewById(R.id.track_name_textview); textView.setText(t.getName()); ImageView imageView = (ImageView) groupRow.findViewById(R.id.track_icon_view); if (t.isLocalTrack()) { imageView.setImageDrawable(getResources().getDrawable(R.drawable.mobile)); } else { imageView.setImageDrawable(getResources().getDrawable(R.drawable.server)); } } @Override public View getChildView(int position, int i1, boolean b, View view, ViewGroup viewGroup) { final Track t; synchronized (ListTracksFragment.this) { t = tracksList.get(position); if (!trackToChildViewMap.containsKey(t)) { final View row = ViewGroup.inflate(getActivity(), R.layout.list_tracks_item_layout, null); setTrackChildFields(t, row); if (t.isLazyLoadingMeasurements()) { lazyLoader.submit(new Runnable() { @Override public void run() { t.getMeasurements(); getActivity().runOnUiThread(new Runnable() { @Override public void run() { setTrackChildFields(t, row); } }); } }); } row.setId(CHILD_VIEW_BASE_ID + position + i1); TypefaceEC.applyCustomFont((ViewGroup) row, TypefaceEC.Newscycle(getActivity())); trackToChildViewMap.put(t, row); } } return trackToChildViewMap.get(t); } private void setTrackChildFields(Track t, View childView) { TextView startView = (TextView) childView.findViewById(R.id.track_details_start_textview); TextView endView = (TextView) childView.findViewById(R.id.track_details_end_textview); TextView lengthView = (TextView) childView.findViewById(R.id.track_details_length_textview); TextView carView = (TextView) childView.findViewById(R.id.track_details_car_textview); TextView durationView = (TextView) childView.findViewById(R.id.track_details_duration_textview); TextView co2View = (TextView) childView.findViewById(R.id.track_details_co2_textview); TextView consumptionView = (TextView) childView.findViewById(R.id.track_details_consumption_textview); TextView fuelCostView = (TextView) childView.findViewById(R.id.track_details_fuel_cost_textview); TextView descriptionView = (TextView) childView.findViewById(R.id.track_details_description_textview); Car car = t.getCar(); carView.setText(car.toString()); descriptionView.setText(t.getDescription()); if (t.isLazyLoadingMeasurements()) { String loading = "loading..."; setTextViewContent(startView, loading); setTextViewContent(endView, loading); setTextViewContent(lengthView, loading); setTextViewContent(durationView, loading); setTextViewContent(co2View, loading); setTextViewContent(consumptionView, loading); } else { try { DateFormat sdf = DateFormat.getDateTimeInstance(); DecimalFormat twoDForm = new DecimalFormat("#.##"); DateFormat dfDuration = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH); dfDuration.setTimeZone(TimeZone.getTimeZone("UTC")); startView.setText(sdf.format(t.getStartTime())); endView.setText(sdf.format(t.getEndTime())); Date durationMillis = new Date(t.getDurationInMillis()); durationView.setText(dfDuration.format(durationMillis)); if (!PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()) .getBoolean(SettingsActivity.IMPERIAL_UNIT, false)) { lengthView.setText(twoDForm.format(t.getLengthOfTrack()) + " km"); } else { lengthView.setText(twoDForm.format(t.getLengthOfTrack() / 1.6) + " miles"); } try { double consumption = t.getFuelConsumptionPerHour(); double literOn100km = t.getLiterPerHundredKm(); co2View.setText(twoDForm.format(t.getGramsPerKm()) + " g/km"); consumptionView.setText(twoDForm.format(consumption) + " l/h (" + twoDForm.format(literOn100km) + " l/100 km)"); if (fuelCostView.getText() == null || fuelCostView.getText().equals("")) { fuelCostView.setText(R.string.calculating); getEstimatedFuelCost(fuelCostView, twoDForm, t); } } catch (UnsupportedFuelTypeException e) { logger.warn(e.getMessage()); } catch (MeasurementsException e) { logger.warn(e.getMessage()); } catch (FuelConsumptionException e) { logger.warn(e.getMessage()); } } catch (MeasurementsException e) { logger.warn(e.getMessage(), e); } } } private void setTextViewContent(TextView tv, String string) { tv.setText(string); } @Override public boolean isChildSelectable(int i, int i1) { return false; } } }