/**
Copyright 2015 Tim Engler, Rareventure LLC
This file is part of Tiny Travel Tracker.
Tiny Travel Tracker 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.
Tiny Travel Tracker 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 Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>.
*/
package com.rareventure.gps2.reviewer.map;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.location.Location;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.TranslateAnimation;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ZoomControls;
import com.mapzen.tangram.LngLat;
import com.rareventure.android.AndroidPreferenceSet.AndroidPreferences;
import com.rareventure.android.DbUtil;
import com.rareventure.android.FatalErrorActivity;
import com.rareventure.android.ProgressDialogActivity;
import com.rareventure.android.SuperThread;
import com.rareventure.android.SuperThreadManager;
import com.rareventure.android.Util;
import com.rareventure.android.widget.InfoNoticeStatusFragment;
import com.rareventure.android.widget.OngoingProcessStatusFragment;
import com.rareventure.android.widget.ToolTipFragment;
import com.rareventure.android.widget.ToolTipFragment.UserAction;
import com.rareventure.gps2.GTG;
import com.rareventure.gps2.GpsTrailerService;
import com.rareventure.gps2.GTG.GTGEvent;
import com.rareventure.gps2.GTG.GTGEventListener;
import com.rareventure.gps2.GTG.Requirement;
import com.rareventure.gps2.R;
import com.rareventure.gps2.database.TimeZoneTimeRow;
import com.rareventure.gps2.database.cache.AreaPanel;
import com.rareventure.gps2.database.cache.AreaPanelSpaceTimeBox;
import com.rareventure.gps2.reviewer.EnterFromDateToToDateActivity;
import com.rareventure.gps2.reviewer.SettingsActivity;
import com.rareventure.gps2.reviewer.ShowManual;
import com.rareventure.gps2.reviewer.TrialExpiredActivity;
import com.rareventure.gps2.reviewer.map.sas.TimeRange;
import com.rareventure.gps2.reviewer.timeview.TimeView;
import com.rareventure.gps2.reviewer.timeview.TimeView.Listener;
//TODO 4: Satellite, street view??, traffic
public class OsmMapGpsTrailerReviewerMapActivity extends ProgressDialogActivity implements OnClickListener, Listener,
GTGEventListener
{
private ImageButton menuButton;
private GpsLocationOverlay locationOverlay;
private static enum SasPanelState { GONE, TAB, FULL;
};
private SasPanelState sasPanelState = SasPanelState.GONE;
private TranslateAnimation slideSasFullToTab;
private TranslateAnimation slideSasTabToFull;
private TranslateAnimation slideSasFullToNone;
private TranslateAnimation slideSasNoneToFull;
private TranslateAnimation slideSasNoneToTab;
private TranslateAnimation slideSasTabToNone;
private TextView distanceView;
@Override
public void doOnCreate(Bundle savedInstanceState)
{
super.doOnCreate(savedInstanceState);
startService(new Intent(this, GpsTrailerService.class));
timeAndDateSdf = new SimpleDateFormat(getString(R.string.time_and_date_format));
GTG.cacheCreatorLock.registerReadingThread();
try {
/* ttt_installer:remove_line */Log.d(GTG.TAG,"OsmMapGpsTrailerReviewerMapActivity.onCreate()");
//sometimes onDestroy forgets to be called, so we need to check for this and cleanup after the last instance
if(reviewerStillRunning)
{
Log.w(GTG.TAG,"OsmMapGpsTrailerReviewerMapActivity: onDestroy() forgot to be called!");
cleanup();
}
reviewerStillRunning = true;
setContentView(R.layout.osm_gps_trailer_reviewer);
osmMapView = (OsmMapView) findViewById(R.id.osmmapview);
osmMapView.onCreate(savedInstanceState);
initUI();
ViewTreeObserver vto = this.findViewById(android.R.id.content).getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
initWithWorkingGetWidth();
findViewById(android.R.id.content).getViewTreeObserver().removeGlobalOnLayoutListener(this);
osmMapView.initAfterLayout();
}
});
}
finally {
GTG.cacheCreatorLock.unregisterReadingThread();
}
}
protected void initWithWorkingGetWidth() {
slideSasFullToTab = new TranslateAnimation(0, sasPanelButton.getWidth() - sasPanel.getWidth(), 0, 0);
slideSasTabToFull = new TranslateAnimation(sasPanelButton.getWidth() - sasPanel.getWidth(), 0, 0, 0);
slideSasTabToNone = new TranslateAnimation(sasPanelButton.getWidth() - sasPanel.getWidth(), - sasPanel.getWidth(), 0, 0);
slideSasFullToNone = new TranslateAnimation(0, -sasPanel.getWidth(), 0, 0);
slideSasNoneToFull = new TranslateAnimation(-sasPanel.getWidth(), 0, 0, 0);
slideSasNoneToTab = new TranslateAnimation(-sasPanel.getWidth(),
sasPanelButton.getWidth() - sasPanel.getWidth(), 0, 0);
slideSasFullToTab.setDuration(500);
slideSasTabToFull.setDuration(500);
slideSasTabToNone.setDuration(200);
slideSasFullToNone.setDuration(500);
slideSasNoneToFull.setDuration(500);
slideSasNoneToTab.setDuration(200);
osmMapView.setZoomCenter(osmMapView.getWidth()/2,
findViewById(R.id.timeview_layout).getTop()/2);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear();
menu.add(R.string.settings);
if(prefs.showPhotos)
{
menu.add(R.string.turn_off_photos);
}
else
{
menu.add(R.string.turn_on_photos);
}
menu.add(R.string.help);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
GTG.cacheCreatorLock.registerReadingThread();
try {
if(item.getTitle().equals(getText(R.string.settings)))
{
startInternalActivity(new Intent(this, SettingsActivity.class));
return true;
}
else if(item.getTitle().equals(getText(R.string.turn_off_photos)))
{
prefs.showPhotos = false;
gpsTrailerOverlay.notifyViewNodesChanged();
if(mediaGalleryFragment != null)
mediaGalleryFragment.finishBrowsing();
return true;
}
else if(item.getTitle().equals(getText(R.string.turn_on_photos)))
{
prefs.showPhotos = true;
gpsTrailerOverlay.notifyViewNodesChanged();
return true;
}
else if(item.getTitle().equals(getText(R.string.help)))
{
startInternalActivity(new Intent(this, ShowManual.class));
return true;
}
return super.onOptionsItemSelected(item);
}
finally {
GTG.cacheCreatorLock.unregisterReadingThread();
}
}
private static final int DIALOG_DELETE_NAMED_LOCATION = 1;
private static final int INITIAL_SETUP_REQUEST = 0;
private static final long MAX_SANE_PERIOD_MS = Util.MS_PER_YEAR * 10;
public final Runnable NOTIFY_HAS_DRAWN_RUNNABLE = new Runnable() {
@Override
public void run() {
notifyHasDrawn();
}
};
private ImageView autoZoom;
public GpsTrailerOverlay gpsTrailerOverlay;
private long minRecordedTimeMs;
private long maxRecordedTimeMs;
public static Preferences prefs = new Preferences();
OsmMapView osmMapView;
MapScaleWidget scaleWidget;
private GpsClickData gpsClickData;
private Dialog currentDialog;
public ImageView panToLocation;
private boolean timeViewDisplayed;
TimeView timeView;
View datePicker;
private OngoingProcessStatusFragment gtgStatus;
ToolTipFragment toolTip;
ZoomControls zoomControls;
private InfoNoticeStatusFragment infoNotice;
// private CheckBox selectedAreaAddLock;
private View sasPanel;
private ImageButton sasPanelButton;
// private ImageButton selectedAreaView;
//this is static because sometimes onDestroy from the last time this
//activity was run isn't called. In this case we want to make sure to
//be able to kill the threads before starting again
// and it does create a completely new instance of this class
private static SuperThreadManager superThreadManager;
private static boolean reviewerStillRunning;
/**
* Notifies the activity that the selected areas have been
* changed by the user.
* @param isSet true if there are any selected areas set after
* the operation.
*/
public void notifySelectedAreasChanged(boolean isSet)
{
if(isSet)
{
if(sasPanelState == sasPanelState.GONE)
{
slideSas(SasPanelState.FULL);
}
}
else
{
slideSas(SasPanelState.GONE);
}
}
private void slideSas(final SasPanelState newState) {
final TranslateAnimation anim;
if(newState == SasPanelState.FULL)
{
if(sasPanelState == SasPanelState.GONE)
anim = slideSasNoneToFull;
else if (sasPanelState == SasPanelState.TAB)
{
anim = slideSasTabToFull;
}
else
return; //nothing to do
}
else if(newState == SasPanelState.TAB)
{
if(sasPanelState == SasPanelState.GONE)
anim = slideSasNoneToTab;
// throw new IllegalStateException("not supported");
else if (sasPanelState == SasPanelState.FULL)
anim = slideSasFullToTab;
else
return; //nothing to do
}
else //if(newState == SasPanelState.GONE)
{
if(sasPanelState == SasPanelState.TAB)
anim = slideSasTabToNone;
else if (sasPanelState == SasPanelState.FULL)
anim = slideSasFullToNone;
else
return; //nothing to do
}
if(sasPanelState == SasPanelState.TAB)
sasPanel.findViewById(R.id.sas_main_panel).setVisibility(View.VISIBLE);
sasPanel.startAnimation(anim);
anim.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if(newState == SasPanelState.TAB)
{
sasPanel.findViewById(R.id.sas_main_panel).setVisibility(View.GONE);
sasPanelButton.setBackgroundResource(R.drawable.sas_tab_out);
}
else
sasPanelButton.setBackgroundResource(R.drawable.sas_tab_in);
sasPanel.setVisibility(newState != SasPanelState.GONE ? View.VISIBLE : View.INVISIBLE);
sasPanelState = newState;
}
});
}
protected void notifyHasDrawn() {
TimeZoneTimeRow newTimeZone = GTG.tztSet.getTimeZoneCovering(gpsTrailerOverlay.closestToCenterTimeSec);
timeView.updateTimeZone(newTimeZone);
osmMapView.invalidate();
}
private void initUI()
{
TextView ty = (TextView) findViewById(R.id.thankyou);
ty.setMovementMethod(LinkMovementMethod.getInstance());
sasPanelButton = (ImageButton) findViewById(R.id.sas_open_close_button);
sasPanelButton.setOnClickListener(this);
gtgStatus = (OngoingProcessStatusFragment) getSupportFragmentManager().
findFragmentById(R.id.status);
toolTip = (ToolTipFragment) getSupportFragmentManager().
findFragmentById(R.id.tooltip);
infoNotice = (InfoNoticeStatusFragment) getSupportFragmentManager().
findFragmentById(R.id.infoNotice);
superThreadManager = new SuperThreadManager();
SuperThread cpuThread = new SuperThread(superThreadManager);
SuperThread fileIOThread = new SuperThread(superThreadManager);
fileIOThread.start();
cpuThread.start();
//co: this has been replaced by notifyProcessing() method
// superThreadManager.setSleepingThreadsListener(new SuperThreadManager.SleepingThreadsListener() {
//
// @Override
// public void notifySleepingThreadsChanged(final boolean allThreadsAreSleeping) {
// runOnUiThread(new Runnable() {
//
// @Override
// public void run() {
// setProgressBarIndeterminateVisibility(!allThreadsAreSleeping);
// }
// });
// }
// });
zoomControls = (ZoomControls)this.findViewById(R.id.zoomControls);
zoomControls.setOnZoomInClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
osmMapView.zoomIn();
toolTip.setAction(UserAction.ZOOM_IN);
}
});
zoomControls.setOnZoomOutClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
osmMapView.zoomOut();
toolTip.setAction(UserAction.ZOOM_OUT);
}
});
osmMapView.addOverlay(gpsTrailerOverlay = new GpsTrailerOverlay(this, cpuThread, osmMapView));
osmMapView.addOverlay(locationOverlay = new GpsLocationOverlay(this));
osmMapView.init(fileIOThread, this);
scaleWidget = (MapScaleWidget) this.findViewById(R.id.scaleWidget);
osmMapView.setScaleWidget(scaleWidget);
panToLocation = (ImageView)this.findViewById(R.id.pan_to_location);
panToLocation.setOnClickListener(this);
autoZoom = (ImageView)this.findViewById(R.id.AutoZoom);
autoZoom.setOnClickListener(this);
menuButton = (ImageButton) findViewById(R.id.menu_button);
menuButton.setOnClickListener(this);
datePicker = findViewById(R.id.date_picker);
datePicker.setOnClickListener(this);
timeView = (TimeView) findViewById(R.id.timeview);
timeView.setListener(this);
timeView.setActivity(this);
// selectedAreaAddLock = (CheckBox) findViewById(R.id.selected_areas_add_lock);
// selectedAreaAddLock.setOnCheckedChangeListener(new OnCheckedChangeListener() {
//
// @Override
// public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// GpsTrailerOverlayDrawer.doMethodTracing = true;
// if(isChecked)
// {
// toolTip.setAction(ToolTipFragment.UserAction.SELECTED_AREA_ADD_LOCKED);
// gpsTrailerOverlay.setSelectedAreaAddLock(true);
// }
// else
// {
// toolTip.setAction(ToolTipFragment.UserAction.SELECTED_AREA_ADD_UNLOCKED);
// gpsTrailerOverlay.setSelectedAreaAddLock(false);
// }
// }
// });
//
sasPanel = findViewById(R.id.sas_panel);
sasPanel.setVisibility(View.INVISIBLE);
distanceView = (TextView)findViewById(R.id.dist_traveled);
updateDistanceView(-1);
initSasPanel();
}
private SimpleDateFormat timeAndDateSdf;
private MediaGalleryFragment mediaGalleryFragment;
private boolean locationKnown;
private void initSasPanel() {
ListView sasPanelList = (ListView) findViewById(R.id.sas_grid);
sasPanelList.setOnItemClickListener(new OnItemClickListener() {
private static final int MIN_TR_TIMESPAN_SEC = Util.SECONDS_IN_DAY;
private static final int TR_TIMESPAN_MULTIPLIER = 3;
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//note that getTimeRange reuses the same tr instance, so we have to be careful
// when we call it twice
TimeRange tr = gpsTrailerOverlay.sas.getTimeRange(position-1);
int currStartSec = tr.startTimeSec;
int currEndSec = tr.endTimeSec;
//co: we just want the time inside the box
// int timeSpan;
//
// if(tr.fullRangeEndSec - tr.fullRangeStartSec < MIN_TR_TIMESPAN_SEC / TR_TIMESPAN_MULTIPLIER)
// {
// timeSpan = MIN_TR_TIMESPAN_SEC;
// }
// else
// {
// timeSpan = (tr.fullRangeEndSec - tr.fullRangeStartSec) * TR_TIMESPAN_MULTIPLIER;
// }
//
// int currStartSec = (int) Math.max(- timeSpan /2 + (tr.endTimeSec/2 + tr.startTimeSec/2), getStartTimeMs()/1000);
// int currEndSec = (int) Math.min(timeSpan /2 + (tr.endTimeSec/2 + tr.startTimeSec/2), getEndTimeMs()/1000);
//
// if(position > 0)
// {
// TimeRange prevTr = gpsTrailerOverlay.sas.getTimeRange(position-1);
// currStartSec = Math.max(prevTr.endTimeSec, currStartSec);
// }
//
// if(position < gpsTrailerOverlay.sas.getTimeRangeCount() - 1)
// {
// TimeRange nextTr = gpsTrailerOverlay.sas.getTimeRange(position+1);
// currEndSec = Math.min(nextTr.startTimeSec, currEndSec);
// }
//
if(currEndSec - currStartSec < timeView.getMinSelectableTimeSec())
{
currEndSec = currStartSec + timeView.getMinSelectableTimeSec();
}
setStartAndEndTimeSec(currStartSec, currEndSec);
updateTimeViewTime();
}
});
sasPanelList.setAdapter(new ListAdapter() {
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
gpsTrailerOverlay.sas.unregisterDataSetObserver(observer);
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
gpsTrailerOverlay.sas.registerDataSetObserver(observer);
}
@Override
public boolean isEmpty() {
return gpsTrailerOverlay.sas.isEmpty();
}
@Override
public boolean hasStableIds() {
return false; //because we might merge timeviews
}
@Override
public int getViewTypeCount() {
return 2;
}
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null)
{
// Log.d(GTG.TAG,"SASPanel: Creating new view");
if(position == 0)
convertView = getLayoutInflater().
inflate(R.layout.selected_area_info_top_row, null);
else
convertView = getLayoutInflater().
inflate(R.layout.selected_area_info_row, null);
}
// else
// Log.d(GTG.TAG,"SASPanel: Reusing view");
if(position == 0)
{
((TextView)convertView.findViewById(R.id.totalTime)).setText(Util.convertMsToText(gpsTrailerOverlay.sas.getTotalTimeSecs()*1000l));
((TextView)convertView.findViewById(R.id.totalDist)).setText(MapScaleWidget.calcLabelForLength(gpsTrailerOverlay.sas.getTotalDistM(),
GTG.prefs.useMetric));
((TextView)convertView.findViewById(R.id.timesInArea)).setText(String.valueOf(gpsTrailerOverlay.sas.getTimesInArea()));
((TextView)convertView.findViewById(R.id.timesInArea)).setText(String.valueOf(gpsTrailerOverlay.sas.getTimesInArea()));
TimeZone tz = gpsTrailerOverlay.sas.timeZone;
if(tz == null || tz.hasSameRules(Util.getCurrTimeZone()))
{
convertView.findViewById(R.id.timeZoneTableRow).setVisibility(View.GONE);
}
else
{
convertView.findViewById(R.id.timeZoneTableRow).setVisibility(View.VISIBLE);
((TextView)convertView.findViewById(R.id.timezone)).setText(tz.getDisplayName());
}
return convertView;
}
TimeRange tr = gpsTrailerOverlay.sas.getTimeRange(position-1);
timeAndDateSdf.setTimeZone(gpsTrailerOverlay.sas.timeZone);
String startText = timeAndDateSdf.format(new Date(tr.startTimeSec * 1000l));
String endText = timeAndDateSdf.format(new Date(tr.endTimeSec * 1000l));
((TextView)convertView.findViewById(R.id.startTime)).setText(startText);
((TextView)convertView.findViewById(R.id.endTime)).setText(endText);
String distText;
if(tr.dist == -1)
distText = "--";
else
{
distText = MapScaleWidget.calcLabelForLength(tr.dist, GTG.prefs.useMetric);
}
((TextView)convertView.findViewById(R.id.distance)).setText(distText);
//this fixes a bug where sometimes if the last row is deleted and readded, it isn't
//layedout again, causing the date/times to be cut off
convertView.requestLayout();
// ((TextView)convertView.findViewById(R.id.timeLength)).setText("a certain amount of time");
return convertView;
}
@Override
public int getItemViewType(int position) {
if(position == 0)
return 0;
return 1;
}
@Override
public long getItemId(int position) {
if(position == 0)
return Long.MIN_VALUE;
TimeRange tr = gpsTrailerOverlay.sas.getTimeRange(position-1);
return tr.fullRangeStartSec;
}
@Override
public Object getItem(int position) {
if(position == 0)
return null;
return gpsTrailerOverlay.sas.getTimeRange(position-1);
}
@Override
public int getCount() {
int count = gpsTrailerOverlay.sas.getTimeRangeCount();
// Log.d(GTG.TAG,"item count is "+count);
if(count >= 1)
return count+1;
return 0;
}
@Override
public boolean isEnabled(int position) {
if(position == 0)
return false;
return true;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
});
}
// protected Dialog onCreateDialog(int id) {
// if(currentDialog != null)
// currentDialog.dismiss();
//
// switch (id) {
//
// case DIALOG_DELETE_NAMED_LOCATION:
// this.currentDialog = new AlertDialog.Builder(this)
// .setTitle(Util.varReplace(this.getString(R.string.delete_named_location),
// "item", gpsClickData.name))
// .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int whichButton) {
// deleteNamedLocation();
// }
//
// })
// .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int whichButton) {
// }
// })
// .create();
// }
//
// return null;
// }
//TODO 3: have a clearish dial. So the dial darkens only when you drape your finger over the screen
// otherwise it is clear, or minimized so that it doesn't take up too much screen real estate.
@Override
public void onClick(View v) {
GTG.cacheCreatorLock.registerReadingThread();
try {
if(v == panToLocation)
{
if(locationKnown)
{
osmMapView.panTo(locationOverlay.getLastLoc());
toolTip.setAction(ToolTipFragment.UserAction.PAN_TO_LOCATION_BUTTON);
}
else
{
Toast.makeText(OsmMapGpsTrailerReviewerMapActivity.this, R.string.location_unknown, Toast.LENGTH_SHORT).show();
}
}
else if(v == datePicker)
{
startInternalActivity(new Intent(this, EnterFromDateToToDateActivity.class));
toolTip.setAction(ToolTipFragment.UserAction.DATE_PICKER);
}
else if(v == autoZoom)
{
// GpsTrailerOverlayDrawer.turnOnMethodTracing = true;
doAutozoom(true);
toolTip.setAction(ToolTipFragment.UserAction.AUTOZOOM_BUTTON);
}
else if(v == menuButton)
{
openOptionsMenu();
}
else if(v == sasPanelButton)
{
if(sasPanelState == SasPanelState.FULL)
slideSas(SasPanelState.TAB);
else
slideSas(SasPanelState.FULL);
}
// else if(v == selectedAreaView)
// {
// datePicker.setVisibility(View.INVISIBLE);
// timeView.setVisibility(View.INVISIBLE);
// toolTip.getView().setVisibility(View.INVISIBLE);
//
//// SelectedAreaInfoActivity.sas = gpsTrailerOverlay.sas;
//// startActivity(new Intent(this, SelectedAreaInfoActivity.class));
// }
}
finally {
GTG.cacheCreatorLock.unregisterReadingThread();
}
}
private void doAutozoom(boolean showToastOnFailure) {
AreaPanelSpaceTimeBox stBox;
gpsTrailerOverlay.sas.rwtm.registerReadingThread();
try {
stBox = GTG.apCache.getAutoZoomArea(prefs.currTimePosSec,
prefs.currTimePosSec + prefs.currTimePeriodSec, gpsTrailerOverlay.sas.getResultPaths());
if(stBox == null)
{
if(showToastOnFailure)
Toast.makeText(OsmMapGpsTrailerReviewerMapActivity.this, getText(R.string.auto_zoom_no_data), Toast.LENGTH_SHORT).show();
return;
}
osmMapView.panAndZoom(stBox.minX,stBox.minY,stBox.maxX,stBox.maxY);
}
finally
{
gpsTrailerOverlay.sas.rwtm.unregisterReadingThread();
}
}
private Float determineSpeed(long lastId, Integer lastLatM, Integer lastLonM, Long lastTime,
Integer latm, Integer lonm, Long time) {
Cursor c = null;
try {
if(lastLatM == null)
{
c = GTG.db.rawQuery(
"select lat_md, lon_md, time from GPS_LOCATION_TIME where _id = ?", new String[] { String.valueOf(lastId) });
if(!c.moveToNext()) //if we can't find the previous id
return null;
lastLatM = c.getInt(0);
lastLonM = c.getInt(1);
lastTime = c.getLong(2);
}
if(latm == null)
{
c = GTG.db.rawQuery(
"select lat_md, lon_md, time from GPS_LOCATION_TIME where _id = ?",
new String[] { String.valueOf(lastId + 1) });
if(!c.moveToNext()) //if we can't find the previous id
return null;
latm = c.getInt(0);
lonm = c.getInt(1);
time = c.getLong(2);
}
Location l1 = new Location("me");
l1.setLatitude(lastLatM/1000000f);
l1.setLongitude(lastLonM/1000000f);
Location l2 = new Location("me");
l2.setLatitude(latm/1000000f);
l2.setLongitude(lonm/1000000f);
long period = time - lastTime;
if(period <=0) period = 1;
return l1.distanceTo(l2) / period;
}
finally {
DbUtil.closeCursors(c);
}
}
public void redrawMap() {
osmMapView.redrawMap();
}
public static class Preferences implements AndroidPreferences
{
public long minTimePeriodMs = 300*1000;
public boolean showPhotos = true;
/**
* The amount of scaling of a panel, in terms of zoom level. ie. 2 would equal 2x, and would mean
* the smallest tile would be spread out over twice the area in x and y.
*/
public int panelScale = 2;
public long lastCheckedGpsLocationIdForUIManager = Long.MIN_VALUE;
/**
* The amount of padding
*/
public float zoomPaddingPerc = .2f;
public int [] allColorRanges =
new int [] { 0xff000000, 0xff808080, 0xffff0000, 0xffff8000, 0xffffff00, 0xff80ff00, 0xff00ff00,
0xff00ff80,
0xff00ffff, 0xff0080ff, 0xff0000ff, 0xffffffff};
//to change default colors, make sure to also update selectedColorRangesBitmap
public int [] colorRange =
new int [] { 0xffff0000, 0xffff8000, 0xffffff00, 0xff80ff00, 0xff00ff00,
0xff00ff80,
0xff00ffff, 0xff0080ff, 0xff0000ff };
public int currTimePosSec = (int)(System.currentTimeMillis()/1000l) - Util.SECONDS_IN_DAY, currTimePeriodSec = Util.SECONDS_IN_DAY*3;
/**
* Last location of screen when TTT was last visited.
*/
public double lastLon, lastLat;
public float lastZoom;
/**
* Boolean map of color ranges that are in use in allColorRanges
*/
//no black, grey or white
public int selectedColorRangesBitmap = ((1<<12)-1)&(~(2|1|(1<<11)));
public boolean enableToolTips = true;
public void updateColorRangeBitmap(int newColorRangesBitmap) {
int [] newColorRange = new int[allColorRanges.length];
int ncrIndex = 0;
for(int i = 0; i < allColorRanges.length; i++)
{
if(((1<<i)&newColorRangesBitmap) != 0)
newColorRange[ncrIndex++] = allColorRanges[i];
}
colorRange = new int[ncrIndex];
System.arraycopy(newColorRange, 0, colorRange, 0, ncrIndex);
this.selectedColorRangesBitmap = newColorRangesBitmap;
}
}
public void editUserLocation(GpsClickData gpsClickData) {
GTG.cacheCreatorLock.registerReadingThread();
try {
this.gpsClickData = gpsClickData;
turnOnSpeechBubble();
}
finally {
GTG.cacheCreatorLock.unregisterReadingThread();
}
}
public void createNewUserLocation(int lastCalculatedGpsLatM, int lastCalculatedGpsLonM, float lastCalculatedRadius) {
GTG.cacheCreatorLock.registerReadingThread();
try {
gpsClickData = new GpsClickData();
gpsClickData.latm = lastCalculatedGpsLatM;
gpsClickData.lonm = lastCalculatedGpsLonM;
gpsClickData.radius = lastCalculatedRadius;
turnOnSpeechBubble();
}
finally {
GTG.cacheCreatorLock.unregisterReadingThread();
}
}
private void turnOnSpeechBubble() {
//TODO 3: make this work again
// Point p = speechBubble.getPoint();
//
// Point clickPoint = new Point();
//
// osmMapView.getProjection().toPixels(new MaplessGeoPoint(gpsClickData.latm,
// gpsClickData.lonm), clickPoint);
//
// //we want to pan the screen over so the point is in the lower right corner and we type a name
// int pixelXToPanTo = clickPoint.x + osmMapView.getView().getWidth()/2
// - p.x;
// int pixelYToPanTo = clickPoint.y + osmMapView.getView().getHeight()/2
// - p.y;
//
// MaplessGeoPoint pointToPanTo = osmMapView.getProjection().fromPixels(pixelXToPanTo, pixelYToPanTo);
//
// osmMapView.getController().animateTo(pointToPanTo,new Runnable()
// {
//
// @Override
// public void run() {
// notifySpeechBubbleToBeDisplayed();
// }
// }
// );
//
}
public static class GpsClickData
{
public boolean isEdit;
public int id;
public int lonm;
public int latm;
/**
* The size of the location the user clicked on based on the points
* that were close by.
*/
public double radius;
private String name;
public static GpsClickData createUserLocation(int latm, int lonm, double radius)
{
GpsClickData gcd = new GpsClickData();
gcd.id = Integer.MIN_VALUE;
gcd.name = "";
gcd.latm = latm;
gcd.lonm = lonm;
return gcd;
}
public static GpsClickData editUserLocation(int id, String name, int latm, int lonm) {
GpsClickData gcd = new GpsClickData();
gcd.id = id;
gcd.name = name;
gcd.lonm = lonm;
gcd.latm = latm;
gcd.isEdit = true;
return gcd;
}
}
@Override
public void doOnResume() {
super.doOnResume();
datePicker.setVisibility(View.VISIBLE);
timeView.setVisibility(View.VISIBLE);
toolTip.getView().setVisibility(View.VISIBLE);
int days = checkExpiry();
// Log.d(GTG.TAG,"Days before expiry "+days);
updateInfoNoticeForTrialInfo(days <= 0, days);
toolTip.setEnabled(prefs.enableToolTips);
scaleWidget.setUnitsToMetric(GTG.prefs.useMetric);
GTG.cacheCreatorLock.registerReadingThread();
try {
GTG.reviewerMapResumeId++;
//PERF: we should only notify media dirty when we didn't go
// to settings or select time/date
GTG.cacheCreator.setGtum(this);
GTG.cacheCreator.notifyMediaDirty();
GTG.mediaLocTimeMap.notifyResume();
//note, this is with the gtg cache creator because drawer
//won't pause while its holding onto a cache creator lock
superThreadManager.resumeAllSuperThreads();
osmMapView.onResume();
// we do this so that if the user deleted a picture it will be
//removed from the display.. it may be null if we are actually going to the start
//screen instead, or we haven't drawn at all
if(gpsTrailerOverlay != null)
{
gpsTrailerOverlay.notifyViewNodesChanged();
//just in case it was changed
//TODO 1.5 FIXME
// gpsTrailerOverlay.updateForColorRangeChange();
}
if(GTG.lastSuccessfulAction == GTG.GTGAction.SET_FROM_AND_TO_DATES)
{
updateTimeViewTime();
doAutozoom(false);
GTG.lastSuccessfulAction = null;
}
updateTimeViewMinMaxTime();
timeView.ovalDrawer.updateColorRange();
timeView.invalidate();
}
finally {
GTG.cacheCreatorLock.unregisterReadingThread();
}
//check all events and update accordingly
updateStatusFromGTGEvent(null);
GTG.addGTGEventListener(this);
}
/**
* Updates the time view min and max time. If minTime != maxTime (ie there are
* at least one point cached.) We also update the selected time view to be within
* the min max range
*/
private void updateTimeViewMinMaxTime() {
GTG.cacheCreatorLock.registerReadingThread();
try {
if(GTG.cacheCreator.minTimeSec != GTG.cacheCreator.maxTimeSec)
{
//if for whatever reason the selected times were outside of min/max
//we need to update them
//note that we updateTimeViewTime() here even though the TimeView may not be layed out and this
//may not have any effect, because if the page is paused and then resumed, the normal way
// of calling updateTimeViewTime() (on layout of the TimeView) will not happen
boolean timeChanged = false;
if(OsmMapGpsTrailerReviewerMapActivity.prefs.currTimePosSec > GTG.cacheCreator.maxTimeSec)
{
OsmMapGpsTrailerReviewerMapActivity.prefs.currTimePosSec = GTG.cacheCreator.maxTimeSec;
timeChanged = true;
}
if(prefs.currTimePosSec + prefs.currTimePeriodSec < GTG.cacheCreator.minTimeSec)
{
prefs.currTimePosSec -= GTG.cacheCreator.minTimeSec - (prefs.currTimePosSec + prefs.currTimePeriodSec);
timeChanged = true;
}
if(timeChanged)
updateTimeViewTime();
}
timeView.setMinMaxTime(GTG.cacheCreator.minTimeSec, GTG.cacheCreator.maxTimeSec);
}
finally {
GTG.cacheCreatorLock.unregisterReadingThread();
}
}
/**
* Checks for and handles expiration of trial product
*/
private int checkExpiry() {
if(GTG.IS_PREMIUM == -42)
return Integer.MAX_VALUE;
int days = GTG.calcDaysBeforeTrialExpired();
if(days == 0)
{
//note, we set trial expired like this, because it depends on when the first point
//was recorded. This information is not available if only the gps service is running
//(and the gps service must shutoff if we're expired) so therefore we set it
//explicitly here.
Requirement.NOT_TRIAL_EXPIRED.reset();
GTG.alert(GTG.GTGEvent.TRIAL_PERIOD_EXPIRED);
finish();
startInternalActivity(new Intent(this, TrialExpiredActivity.class));
}
return days;
}
@Override
public void doOnPause(boolean doOnResumeCalled) {
super.doOnPause(doOnResumeCalled);
GTG.removeGTGEventListener(this);
GTG.cacheCreatorLock.registerReadingThread();
try {
if(currentDialog != null)
{
currentDialog.dismiss();
currentDialog = null;
}
if(osmMapView != null) {
osmMapView.onPause();
//sometimes on pause gets called when we're not fully started up
if(osmMapView.getMapController() != null) {
LngLat lp = osmMapView.getMapController().getPosition();
float lastZoom = osmMapView.getMapController().getZoom();
prefs.lastLat = lp.latitude;
prefs.lastLon = lp.longitude;
prefs.lastZoom = lastZoom;
GTG.runBackgroundTask(new Runnable() {
@Override
public void run() {
GTG.savePreferences(OsmMapGpsTrailerReviewerMapActivity.this);
}
});
}
}
if(GTG.cacheCreator != null)
GTG.cacheCreator.setGtum(null);
if(superThreadManager != null)
superThreadManager.pauseAllSuperThreads();
}
finally {
GTG.cacheCreatorLock.unregisterReadingThread();
}
}
public void onDestroy() {
/* ttt_installer:remove_line */Log.d(GTG.TAG,"OsmMapGpsTrailerReviewerMapActivity.onDestory() start");
super.onDestroy();
if(osmMapView != null)
osmMapView.onDestroy();
GTG.cacheCreatorLock.registerReadingThread();
try {
cleanup();
/* ttt_installer:remove_line */Log.d(GTG.TAG,"OsmMapGpsTrailerReviewerMapActivity.onDestory() end");
}
finally {
GTG.cacheCreatorLock.unregisterReadingThread();
}
}
@Override
public void onLowMemory() {
super.onLowMemory();
if(osmMapView != null)
osmMapView.onLowMemory();
}
private void cleanup()
{
if(superThreadManager != null)
superThreadManager.shutdownAllSuperThreads();
//this happens when finish() has been called (or the system wants us dead), so
//clear out the database and crypt instance, to force user to reenter password
//co:we don't want to close and reopen the database because this takes forever
// if(GTG.db != null)
// GTG.db.close();
// GTG.db = null;
//TODO 2.5 why is this null sometimes?
if(gpsTrailerOverlay != null)
gpsTrailerOverlay.shutdown();
reviewerStillRunning = false;
}
@Override
public void notifyTimeViewChange() {
GTG.cacheCreatorLock.registerReadingThread();
try {
recalculateStartAndEndTimeFromTimeView();
redrawMap();
if(GTG.apCache.hasGpsPoints())
toolTip.setAction(UserAction.TIME_VIEW_CHANGE);
}
finally {
GTG.cacheCreatorLock.unregisterReadingThread();
}
}
/**
* May be called from another thread
*/
public void notifyMediaChanged()
{
runOnUiThread(new Runnable() {
@Override
public void run() {
if(gpsTrailerOverlay != null)
//PERF we could have a special method for pictures, because this
//updates distance, too
gpsTrailerOverlay.notifyViewNodesChanged();
//TODO 2.2 we could update the fragment rather than dismissing it
//co: this causes the much worse problem of the pictures being dismissed every few seconds
//when indexing a lot of pictures and videos (ie right after a restore)
// if(mediaGalleryFragment != null)
// mediaGalleryFragment.finishBrowsing();
}
});
}
@Override
public void notifyTimeViewReady() {
updateTimeViewTime();
}
private void updateInfoNoticeForTrialInfo(boolean expired, int daysBeforeExpiry) {
infoNotice.unregisterProcess(InfoNoticeStatusFragment.NO_GPS_POINTS);
infoNotice.unregisterProcess(InfoNoticeStatusFragment.FREE_VERSION);
infoNotice.unregisterProcess(InfoNoticeStatusFragment.UNLICENSED);
if(GTGEvent.ERROR_UNLICENSED.isOn)
{
//co: we initially don't want to do anything if the user is unlicensed,
// just find out how much piracy is a problem
// infoNotice.registerProcess(InfoNoticeStatusFragment.UNLICENSED, getString(R.string.info_notice_cant_verify_license), null, null);
// return;
}
if(!GTG.gpsLocCache.hasGpsPoints())
{
if(!GTG.prefs.isCollectData)
{
Intent intent = new Intent(this, SettingsActivity.class);
infoNotice.registerProcess(InfoNoticeStatusFragment.NO_GPS_POINTS,
getString(R.string.no_points_no_collect), intent, null);
}
else
infoNotice.registerProcess(InfoNoticeStatusFragment.NO_GPS_POINTS,
getString(R.string.no_points_with_collect), null, null);
}
if(GTG.IS_PREMIUM != -42 )
{
//co : no more expiry
// if(expired)
// infoNotice.registerProcess(InfoNoticeStatusFragment.FREE_VERSION,
// getString(R.string.free_version_after_expiry), GTG.BUY_PREMIUM_INTENT, null);
// else
// {
// infoNotice.registerProcess(InfoNoticeStatusFragment.FREE_VERSION,
// String.format(getString(daysBeforeExpiry != 1 ? R.string.free_version_before_expiry :
// R.string.free_version_before_expiry_1_day), daysBeforeExpiry), GTG.BUY_PREMIUM_INTENT, null);
//
// }
}
}
/**
* Should be called only by ui thread
*/
public void notifyMaxTimeChanged() {
//note that this also updates the selected time if out of range. The reason that may happen,
// is that we just did a restore (or for some reason the cache was deleted), and then
// some points were finally cached (if there are no points cached then the selected times
// are not updated
updateTimeViewMinMaxTime();
}
private void updateTimeViewTime() {
GTG.cacheCreatorLock.registerReadingThread();
try {
if(timeView.getWidth() != 0)
timeView.setSelectedStartAndEndTime(prefs.currTimePosSec,
prefs.currTimePosSec + prefs.currTimePeriodSec
);
//time view may adjust the end time if it is below the minimum (or above the maximum) that
//it can display
recalculateStartAndEndTimeFromTimeView();
}
finally {
GTG.cacheCreatorLock.unregisterReadingThread();
}
}
private void recalculateStartAndEndTimeFromTimeView() {
setStartAndEndTimeSec(timeView.selectedTimeStart, timeView.selectedTimeEnd );
}
public static synchronized void setStartAndEndTimeSec(int startTimeSec, int endTimeSec) {
prefs.currTimePosSec = startTimeSec;
prefs.currTimePeriodSec = endTimeSec - startTimeSec;
}
public void notifyLocationKnown() {
GTG.cacheCreatorLock.registerReadingThread();
try {
panToLocation.setBackgroundResource(R.drawable.pan_to_location);
locationKnown = true;
}
finally {
GTG.cacheCreatorLock.unregisterReadingThread();
}
}
public static enum OngoingProcessEnum
{
PROCESS_GPS_POINTS(R.string.process_gps_points),
DRAW_POINTS(R.string.drawing_points), LOADING_MEDIA(R.string.loading_media);
Integer resourceId;
private OngoingProcessEnum(int resourceId)
{
this.resourceId = resourceId;
}
};
public void notifyProcessing(OngoingProcessEnum ope, String text) {
gtgStatus.registerProcess(ope.resourceId, text, null, null);
}
public void notifyProcessing(OngoingProcessEnum ope) {
gtgStatus.registerProcess(ope.resourceId, getText(ope.resourceId), null, null);
}
public void notifyDoneProcessing(OngoingProcessEnum ope) {
gtgStatus.unregisterProcess(ope.resourceId);
}
@Override
public boolean onGTGEvent(final GTGEvent event) {
return updateStatusFromGTGEvent(event);
}
/**
* @param event if event is specified, only it will be updated, otherwise
* all events are checked if they are on or off
*/
private boolean updateStatusFromGTGEvent(final GTGEvent event) {
if (event == null || event == GTGEvent.ERROR_LOW_FREE_SPACE) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (GTGEvent.ERROR_LOW_FREE_SPACE.isOn)
infoNotice
.registerProcess(
InfoNoticeStatusFragment.LOW_FREE_SPACE,
getString(R.string.error_reviewer_low_free_space),
null, new Runnable() {
@Override
public void run() {
AlertDialog.Builder alert = new AlertDialog.Builder(
OsmMapGpsTrailerReviewerMapActivity.this);
alert.setMessage(getText(R.string.error_reviewer_low_free_space_help));
alert.setPositiveButton(
R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int whichButton) {
exitFromApp();
}
});
alert.show();
}
});
else
infoNotice
.unregisterProcess(InfoNoticeStatusFragment.LOW_FREE_SPACE);
return;
}
});
return false;
}
if (event == null || event == GTGEvent.ERROR_SDCARD_NOT_MOUNTED) {
runOnUiThread(new Runnable() {
@Override
public void run() {
startInternalActivity(new Intent(OsmMapGpsTrailerReviewerMapActivity.this,
FatalErrorActivity.class).putExtra(
FatalErrorActivity.MESSAGE_RESOURCE_ID,
R.string.error_reviewer_sdcard_not_mounted));
}
});
return false;
}
if (event == null || event == GTGEvent.PROCESSING_GPS_POINTS) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (GTGEvent.PROCESSING_GPS_POINTS.isOn) {
timeAndDateSdf.setTimeZone(Util.getCurrTimeZone());
long[] currDateAndExpectedDate = (long[]) event.obj;
//this can sometimes be null because (I believe) that onGTGEvent
// and offGTGEvent are called in rapid succession, and offGTGEvent
// destroys the object associated to the event before we can
// process the onGTGEvent message
//TODO 2.5 fix the GTGEvent.obj race condition
if (currDateAndExpectedDate != null)
notifyProcessing(
OngoingProcessEnum.PROCESS_GPS_POINTS,
String.format(
getString(R.string.process_gps_points_fmt),
timeAndDateSdf
.format(new Date(
currDateAndExpectedDate[0]))));
}
else
notifyDoneProcessing(OngoingProcessEnum.PROCESS_GPS_POINTS);
}
});
}
if (event == null || event == GTGEvent.LOADING_MEDIA) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (GTGEvent.LOADING_MEDIA.isOn)
notifyProcessing(OngoingProcessEnum.LOADING_MEDIA);
else
notifyDoneProcessing(OngoingProcessEnum.LOADING_MEDIA);
}
});
}
// if (event == GTGEvent.ERROR_UNLICENSED) {
// runOnUiThread(new Runnable() {
//
// @Override
// public void run() {
// if (GTGEvent.ERROR_UNLICENSED.isOn)
// updateInfoNoticeForTrialInfo();
// }
// });
// }
//co: we ignore it when the ttt server is down, it's kind of a non event
// if(event == GTGEvent.TTT_SERVER_DOWN)
// {
// return false;
// }
return false;
}
@Override
public void offGTGEvent(GTGEvent event) {
updateStatusFromGTGEvent(event);
}
public void notifyPathsChanged() {
if(gpsTrailerOverlay != null)
gpsTrailerOverlay.notifyPathsChanged();
}
public void notifyDistUpdated(final double distance) {
runOnUiThread(new Runnable() {
@Override
public void run() {
updateDistanceView(distance);
}
});
}
protected void updateDistanceView(double distance) {
distanceView.setText(String.format(getText(R.string.distance_traveled).toString(),
distance == -1 ? "--" :
MapScaleWidget.calcLabelForLength(distance, GTG.prefs.useMetric)));
}
public void registerMediaGalleryFragment(
MediaGalleryFragment mediaGalleryFragment) {
this.mediaGalleryFragment = mediaGalleryFragment;
}
public void unregisterMediaGalleryFragment(
MediaGalleryFragment mediaGalleryFragment2) {
this.mediaGalleryFragment = null;
}
@Override
public int getRequirements() {
return GTG.REQUIREMENTS_FULL_PASSWORD_PROTECTED_UI;
}
}