package mil.nga.giat.asam.map;
import java.lang.ref.WeakReference;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import mil.nga.giat.asam.Asam;
import mil.nga.giat.asam.AsamListReportTabletActivity;
import mil.nga.giat.asam.DisclaimerDialogFragment;
import mil.nga.giat.asam.DisclaimerDialogFragment.OnDisclaimerDialogDismissedListener;
import mil.nga.giat.asam.InfoDialogFragment;
import mil.nga.giat.asam.LegalTabletActivity;
import mil.nga.giat.asam.PreferencesDialogFragment;
import mil.nga.giat.asam.PreferencesDialogFragment.OnPreferencesDialogDismissedListener;
import mil.nga.giat.asam.R;
import mil.nga.giat.asam.TextQueryDialogFragment;
import mil.nga.giat.asam.TextQueryDialogFragment.OnTextQueryListener;
import mil.nga.giat.asam.connectivity.OfflineBannerFragment;
import mil.nga.giat.asam.db.AsamDbHelper;
import mil.nga.giat.asam.model.AsamBean;
import mil.nga.giat.asam.model.AsamJsonParser;
import mil.nga.giat.asam.model.AsamMapClusterBean;
import mil.nga.giat.asam.model.TextQueryParametersBean;
import mil.nga.giat.asam.net.AsamWebService;
import mil.nga.giat.asam.util.AsamConstants;
import mil.nga.giat.asam.util.AsamListContainer;
import mil.nga.giat.asam.util.AsamLog;
import mil.nga.giat.asam.util.AsamUtils;
import mil.nga.giat.asam.util.SyncTime;
import mil.nga.giat.poffencluster.PoffenCluster;
import mil.nga.giat.poffencluster.PoffenClusterCalculator;
import mil.nga.giat.poffencluster.PoffenPoint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.ActionBarActivity;
import android.text.Html;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.CancelableCallback;
import com.google.android.gms.maps.GoogleMap.OnCameraChangeListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.vividsolutions.jts.geom.Geometry;
public class AllAsamsMapTabletActivity extends ActionBarActivity implements OnCameraChangeListener, OnMarkerClickListener, CancelableCallback, OnTextQueryListener, OnDisclaimerDialogDismissedListener, OnPreferencesDialogDismissedListener, Asam.OnOfflineFeaturesListener, OfflineBannerFragment.OnOfflineBannerClick {
private static class QueryHandler extends Handler {
WeakReference<AllAsamsMapTabletActivity> mAllAsamsMapTabletActivity;
QueryHandler(AllAsamsMapTabletActivity allAsamsMapTabletActivity) {
mAllAsamsMapTabletActivity = new WeakReference<AllAsamsMapTabletActivity>(allAsamsMapTabletActivity);
}
@Override
public void handleMessage(Message message) {
AllAsamsMapTabletActivity allAsamsMapTabletActivity = mAllAsamsMapTabletActivity.get();
allAsamsMapTabletActivity.mDateRangeTextViewUI.setText(allAsamsMapTabletActivity.mDateRangeText);
allAsamsMapTabletActivity.mTotalAsamsTextViewUI.setText(allAsamsMapTabletActivity.mTotalAsamsText);
allAsamsMapTabletActivity.mQueryProgressDialog.dismiss();
if (allAsamsMapTabletActivity.mQueryError) {
allAsamsMapTabletActivity.mQueryError = false;
Toast.makeText(allAsamsMapTabletActivity, allAsamsMapTabletActivity.getString(R.string.all_asams_map_tablet_query_error_text), Toast.LENGTH_LONG).show();
}
allAsamsMapTabletActivity.clearAsamMarkers();
if (allAsamsMapTabletActivity.mAsams.size() == 1) {
// Camera position changing so redraw will be triggered in onCameraChange.
if (allAsamsMapTabletActivity.mPerformBoundsAdjustmentWithQuery) {
allAsamsMapTabletActivity.mPerformMapClustering = true;
AsamBean asam = allAsamsMapTabletActivity.mAsams.get(0);
allAsamsMapTabletActivity.mMapUI.animateCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition.Builder().target(new LatLng(asam.getLatitude(), asam.getLongitude())).zoom(AsamConstants.SINGLE_ASAM_ZOOM_LEVEL).build()));
}
}
else if (allAsamsMapTabletActivity.mAsams.size() > 1) {
// Camera position changing so redraw will be triggered in onCameraChange.
if (allAsamsMapTabletActivity.mPerformBoundsAdjustmentWithQuery) {
allAsamsMapTabletActivity.mPerformMapClustering = true;
LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder();
for (AsamBean asam : allAsamsMapTabletActivity.mAsams) {
boundsBuilder = boundsBuilder.include(new LatLng(asam.getLatitude(), asam.getLongitude()));
}
allAsamsMapTabletActivity.mMapUI.animateCamera(CameraUpdateFactory.newLatLngBounds(boundsBuilder.build(), 0), allAsamsMapTabletActivity);
}
}
else {
Toast.makeText(allAsamsMapTabletActivity, allAsamsMapTabletActivity.getString(R.string.all_asams_map_no_asams_text), Toast.LENGTH_LONG).show();
}
// Camera position not changing so redraw won't be triggered in onCameraChange.
if (!allAsamsMapTabletActivity.mPerformBoundsAdjustmentWithQuery && allAsamsMapTabletActivity.mAsams.size() > 0) {
// Use the PoffenCluster library to calculate the clusters.
int zoomLevel = Math.round(allAsamsMapTabletActivity.mMapUI.getCameraPosition().zoom);
LatLngBounds bounds = allAsamsMapTabletActivity.mMapUI.getProjection().getVisibleRegion().latLngBounds;
int numLatitudeCells = (int)(Math.round(Math.pow(2, zoomLevel)));
int numLongitudeCells = (int)(Math.round(Math.pow(2, zoomLevel)));
PoffenClusterCalculator<AsamBean> calculator = new PoffenClusterCalculator.Builder<AsamBean>(numLatitudeCells, numLongitudeCells).mergeLargeClusters(false).build();
for (AsamBean asam : allAsamsMapTabletActivity.mAsams) {
calculator.add(asam, new PoffenPoint(asam.getLatitude(), asam.getLongitude()));
}
allAsamsMapTabletActivity.mMapClusters = Collections.synchronizedList(new ArrayList<AsamMapClusterBean>());
synchronized (allAsamsMapTabletActivity.Mutex) {
allAsamsMapTabletActivity.mVisibleClusters = new ArrayList<AsamMapClusterBean>();
}
List<PoffenCluster<AsamBean>> poffenClusters = calculator.getPoffenClusters();
synchronized (allAsamsMapTabletActivity.Mutex) {
for (PoffenCluster<AsamBean> poffenCluster : poffenClusters) {
PoffenPoint poffenPoint = poffenCluster.getClusterCoordinateClosestToMean();
AsamMapClusterBean cluster = new AsamMapClusterBean(poffenCluster.getClusterItems(), new LatLng(poffenPoint.getLatitude(), poffenPoint.getLongitude()));
allAsamsMapTabletActivity.mMapClusters.add(cluster);
if (bounds.contains(cluster.getClusteredMapPosition()) || zoomLevel <= AsamConstants.ZOOM_LEVEL_TO_DRAW_ALL_CLUSTERS) {
allAsamsMapTabletActivity.mVisibleClusters.add(cluster);
// Now draw it on the map.
Marker marker;
if (poffenCluster.getClusterItems().size() == 1) {
marker = allAsamsMapTabletActivity.mMapUI.addMarker(new MarkerOptions().position(cluster.getClusteredMapPosition()).icon(AsamConstants.PIRATE_MARKER).anchor(0.5f, 0.5f));
}
else {
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(AsamUtils.drawNumberOnClusterMarker(allAsamsMapTabletActivity, poffenCluster.getClusterItems().size()));
marker = allAsamsMapTabletActivity.mMapUI.addMarker(new MarkerOptions().position(cluster.getClusteredMapPosition()).icon(bitmapDescriptor).anchor(0.5f, 0.5f));
}
cluster.setMapMarker(marker);
}
}
}
}
}
}
private static final int ALL_QUERY_MODE = 0;
private static final int SUBREGION_QUERY_MODE = 1;
private static final int TEXT_QUERY_MODE = 2;
private static final int SUBREGION_MAP_TABLET_ACTIVITY_REQUEST_CODE = 0;
private static final int TOTAL_TIME_SLIDER_TICKS = 1000;
private static final SimpleDateFormat DATE_RANGE_FORMAT = new SimpleDateFormat("MM/dd/yyyy", Locale.US);
private static final String DATE_RANGE_PATTERN = "%s to %s";
private static final String TOTAL_ASAMS_PATTERN = "%5d of %d ASAMs";
private String mDateRangeText;
private String mTotalAsamsText;
private final Object Mutex = new Object();
private volatile boolean mQueryError;
private volatile boolean mPerformMapClustering;
private volatile boolean mPerformBoundsAdjustmentWithQuery;
private List<AsamBean> mAsams;
private List<AsamMapClusterBean> mMapClusters;
private GoogleMap mMapUI;
private int mMapType;
private Collection<Geometry> offlineGeometries = null;
private TextView mDateRangeTextViewUI;
private TextView mTotalAsamsTextViewUI;
private TextView mQueryModeMessageTextViewUI;
private LinearLayout mQueryModeMessageContainerUI;
private SeekBar mTimeSliderUI;
private MenuItem mListViewMenuItemUI;
private MenuItem mSubregionsMenuItemUI;
private MenuItem mSettingsMenuItemUI;
private MenuItem mTextQueryMenuItemUI;
private MenuItem mInfoMenuItemUI;
private Date mEarliestAsamDate;
private ProgressDialog mQueryProgressDialog;
private QueryHandler mQueryHandler;
private List<Integer> mSelectedSubregionIds;
private TextQueryParametersBean mTextQueryParametersBean;
private Date mTextQueryDateEarliest;
private Date mTextQueryDateLatest;
private int mQueryMode;
private int mPreviousZoomLevel;
private List<AsamMapClusterBean> mVisibleClusters;
private boolean mDisclaimerPopupShowing;
private SharedPreferences mSharedPreferences;
private OfflineMap offlineMap;
private MenuItem offlineMap110mMenuItem;
private OfflineBannerFragment offlineAlertFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AsamLog.i(AllAsamsMapTabletActivity.class.getName() + ":onCreate");
setContentView(R.layout.all_asams_map_tablet);
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
mQueryError = false;
mPerformMapClustering = false;
mAsams = new ArrayList<AsamBean>();
mMapClusters = Collections.synchronizedList(new ArrayList<AsamMapClusterBean>());
mVisibleClusters = new ArrayList<AsamMapClusterBean>();
mMapUI = ((SupportMapFragment)getSupportFragmentManager().findFragmentById(R.id.all_asams_map_tablet_map_view_ui)).getMap();
mMapUI.setOnCameraChangeListener(this);
mMapUI.setOnMarkerClickListener(this);
offlineAlertFragment = new OfflineBannerFragment();
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content, offlineAlertFragment)
.commit();
mPreviousZoomLevel = -1;
mDateRangeTextViewUI = (TextView)findViewById(R.id.all_asams_map_tablet_date_range_text_view_ui);
mTimeSliderUI = (SeekBar)findViewById(R.id.all_asams_map_tablet_time_slider_ui);
mTotalAsamsTextViewUI = (TextView)findViewById(R.id.all_asams_map_tablet_total_asams_text_view_ui);
mQueryModeMessageTextViewUI = (TextView)findViewById(R.id.all_asams_map_tablet_query_mode_message_text_view_ui);
mQueryModeMessageContainerUI = (LinearLayout)findViewById(R.id.all_asams_map_tablet_query_mode_message_container_ui);
mTimeSliderUI.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (mQueryMode == TEXT_QUERY_MODE) {
if (progress == TOTAL_TIME_SLIDER_TICKS - 1) {
mDateRangeTextViewUI.setText(String.format(DATE_RANGE_PATTERN, DATE_RANGE_FORMAT.format(mTextQueryDateLatest), DATE_RANGE_FORMAT.format(mTextQueryDateEarliest)));
}
else if (progress == 0) {
mDateRangeTextViewUI.setText(String.format(DATE_RANGE_PATTERN, DATE_RANGE_FORMAT.format(mTextQueryDateLatest), DATE_RANGE_FORMAT.format(mTextQueryDateLatest)));
}
else {
mDateRangeTextViewUI.setText(String.format(DATE_RANGE_PATTERN, DATE_RANGE_FORMAT.format(mTextQueryDateLatest), DATE_RANGE_FORMAT.format(calculateTextQueryDateFromTimeSlider(progress))));
}
}
else {
if (progress == TOTAL_TIME_SLIDER_TICKS - 1) {
mDateRangeTextViewUI.setText(String.format(DATE_RANGE_PATTERN, DATE_RANGE_FORMAT.format(new Date()), DATE_RANGE_FORMAT.format(initAndGetEarliestAsamDate())));
}
else if (progress == 0) {
mDateRangeTextViewUI.setText(String.format(DATE_RANGE_PATTERN, DATE_RANGE_FORMAT.format(new Date()), DATE_RANGE_FORMAT.format(new Date())));
}
else {
mDateRangeTextViewUI.setText(String.format(DATE_RANGE_PATTERN, DATE_RANGE_FORMAT.format(new Date()), DATE_RANGE_FORMAT.format(calculateQueryDateFromTimeSlider(progress))));
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mQueryProgressDialog = ProgressDialog.show(AllAsamsMapTabletActivity.this, getString(R.string.all_asams_map_tablet_query_progress_dialog_title_text), getString(R.string.all_asams_map_tablet_query_progress_dialog_content_text), true);
new QueryThread(seekBar.getProgress()).start();
}
});
mQueryMode = ALL_QUERY_MODE;
// Called to handle the UI after the query has run.
mQueryHandler = new QueryHandler(this);
// Show the disclaimer popup if necessary.
boolean hideDisclaimer = mSharedPreferences.getBoolean(AsamConstants.HIDE_DISCLAIMER_KEY, false);
boolean isTabletLaunching = getIntent().getBooleanExtra(AsamConstants.TABLET_IS_LAUNCHING_KEY, false);
if (isTabletLaunching && !hideDisclaimer) {
mDisclaimerPopupShowing = true;
mTimeSliderUI.setEnabled(false);
DialogFragment dialogFragment = DisclaimerDialogFragment.newInstance();
dialogFragment.show(getSupportFragmentManager(), AsamConstants.DISCLAIMER_DIALOG_TAG);
}
else {
new QueryThread(getInitialTimePeriodQuery()).start();
mQueryProgressDialog = ProgressDialog.show(this, getString(R.string.all_asams_map_tablet_query_progress_dialog_title_text), getString(R.string.all_asams_map_tablet_query_progress_dialog_content_text), true);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.all_asams_map_tablet_menu, menu);
mListViewMenuItemUI = menu.findItem(R.id.all_asams_map_tablet_menu_list_view_ui);
mSubregionsMenuItemUI = menu.findItem(R.id.all_asams_map_tablet_menu_subregions_ui);
mSettingsMenuItemUI = menu.findItem(R.id.all_asams_map_tablet_menu_settings_ui);
mTextQueryMenuItemUI = menu.findItem(R.id.all_asams_map_tablet_menu_text_query_ui);
mInfoMenuItemUI = menu.findItem(R.id.all_asams_map_tablet_menu_info_ui);
setMenuItemsState(!mDisclaimerPopupShowing);
int mapType = mSharedPreferences.getInt(AsamConstants.MAP_TYPE_KEY, 1);
switch (mapType) {
case GoogleMap.MAP_TYPE_SATELLITE:
menu.findItem(R.id.map_type_satellite).setChecked(true);
break;
case GoogleMap.MAP_TYPE_HYBRID:
menu.findItem(R.id.map_type_hybrid).setChecked(true);
break;
case AsamConstants.MAP_TYPE_OFFLINE_110M:
menu.findItem(R.id.map_type_offline_110m).setChecked(true);
break;
default:
menu.findItem(R.id.map_type_normal).setChecked(true);
}
return super.onCreateOptionsMenu(menu);
}
@Override
public void onResume() {
super.onResume();
((Asam) getApplication()).registerOfflineMapListener(this);
supportInvalidateOptionsMenu();
int mapType = mSharedPreferences.getInt(AsamConstants.MAP_TYPE_KEY, GoogleMap.MAP_TYPE_NORMAL);
if (mapType != mMapType) onMapTypeChanged(mapType);
}
@Override
public void onPause() {
super.onPause();
((Asam) getApplication()).unregisterOfflineMapListener(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.map_type_normal:
item.setChecked(!item.isChecked());
onMapTypeChanged(GoogleMap.MAP_TYPE_NORMAL);
return true;
case R.id.map_type_satellite:
item.setChecked(!item.isChecked());
onMapTypeChanged(GoogleMap.MAP_TYPE_SATELLITE);
return true;
case R.id.map_type_hybrid:
item.setChecked(!item.isChecked());
onMapTypeChanged(GoogleMap.MAP_TYPE_HYBRID);
return true;
case R.id.map_type_offline_110m:
item.setChecked(!item.isChecked());
onMapTypeChanged(AsamConstants.MAP_TYPE_OFFLINE_110M);
return true;
case R.id.all_asams_map_tablet_menu_list_view_ui: {
AsamListContainer.mAsams = mAsams;
Intent intent = new Intent(this, AsamListReportTabletActivity.class);
startActivity(intent);
return true;
}
case R.id.all_asams_map_tablet_menu_subregions_ui: {
// Jump to the subregions map.
Intent intent = new Intent(this, SubregionMapActivity.class);
intent.putExtra(AsamConstants.SUBREGION_MAP_EXPECTING_RESULT_CODE_KEY, true);
startActivityForResult(intent, SUBREGION_MAP_TABLET_ACTIVITY_REQUEST_CODE);
return true;
}
case R.id.all_asams_map_tablet_menu_text_query_ui: {
DialogFragment dialogFragment = TextQueryDialogFragment.newInstance();
dialogFragment.show(getSupportFragmentManager(), AsamConstants.TEXT_QUERY_DIALOG_TAG);
return true;
}
case R.id.all_asams_map_tablet_menu_settings_ui: {
DialogFragment dialogFragment = PreferencesDialogFragment.newInstance();
dialogFragment.show(getSupportFragmentManager(), AsamConstants.PREFERENCES_DIALOG_TAG);
return true;
}
case R.id.all_asams_map_tablet_menu_info_ui: {
DialogFragment dialogFragment = InfoDialogFragment.newInstance();
dialogFragment.show(getSupportFragmentManager(), AsamConstants.INFO_DIALOG_TAG);
return true;
}
case R.id.all_asams_map_tablet_menu_reset_ui:
mQueryModeMessageContainerUI.setVisibility(View.INVISIBLE);
mQueryMode = ALL_QUERY_MODE;
mTextQueryDateEarliest = null;
mTextQueryDateLatest = null;
mQueryProgressDialog = ProgressDialog.show(this, getString(R.string.all_asams_map_tablet_query_progress_dialog_title_text), getString(R.string.all_asams_map_tablet_query_progress_dialog_content_text), true);
new QueryThread(getInitialTimePeriodQuery()).start();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onMarkerClick(Marker marker) {
synchronized (Mutex) {
for (AsamMapClusterBean mapCluster : mVisibleClusters) {
if (marker.equals(mapCluster.getMapMarker())) {
AsamListContainer.mAsams = mapCluster.getAsams();
Intent intent = new Intent(this, AsamListReportTabletActivity.class);
startActivity(intent);
break;
}
}
}
return true;
}
@Override
public void onCameraChange(CameraPosition position) {
AsamLog.i(AllAsamsMapTabletActivity.class.getName() + ":onCameraChange");
if (mDisclaimerPopupShowing) {
return;
}
if (mPerformMapClustering) {
mPerformMapClustering = false;
mPreviousZoomLevel = Math.round(mMapUI.getCameraPosition().zoom);
// Use the PoffenCluster library to calculate the clusters.
int zoomLevel = mPreviousZoomLevel;
int numLatitudeCells = (int)(Math.round(Math.pow(2, zoomLevel)));
int numLongitudeCells = (int)(Math.round(Math.pow(2, zoomLevel)));
LatLngBounds bounds = mMapUI.getProjection().getVisibleRegion().latLngBounds;
PoffenClusterCalculator<AsamBean> calculator = new PoffenClusterCalculator.Builder<AsamBean>(numLatitudeCells, numLongitudeCells).mergeLargeClusters(false).build();
for (AsamBean asam : mAsams) {
calculator.add(asam, new PoffenPoint(asam.getLatitude(), asam.getLongitude()));
}
mMapClusters = Collections.synchronizedList(new ArrayList<AsamMapClusterBean>());
synchronized (Mutex) {
mVisibleClusters = new ArrayList<AsamMapClusterBean>();
}
List<PoffenCluster<AsamBean>> poffenClusters = calculator.getPoffenClusters();
synchronized (Mutex) {
for (PoffenCluster<AsamBean> poffenCluster : poffenClusters) {
PoffenPoint poffenPoint = poffenCluster.getClusterCoordinateClosestToMean();
AsamMapClusterBean cluster = new AsamMapClusterBean(poffenCluster.getClusterItems(), new LatLng(poffenPoint.getLatitude(), poffenPoint.getLongitude()));
mMapClusters.add(cluster);
if (bounds.contains(cluster.getClusteredMapPosition()) || zoomLevel <= AsamConstants.ZOOM_LEVEL_TO_DRAW_ALL_CLUSTERS) {
mVisibleClusters.add(cluster);
// Now draw it on the map.
Marker marker;
if (poffenCluster.getClusterItems().size() == 1) {
marker = mMapUI.addMarker(new MarkerOptions().position(cluster.getClusteredMapPosition()).icon(AsamConstants.PIRATE_MARKER).anchor(0.5f, 0.5f));
}
else {
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(AsamUtils.drawNumberOnClusterMarker(this, poffenCluster.getClusterItems().size()));
marker = mMapUI.addMarker(new MarkerOptions().position(cluster.getClusteredMapPosition()).icon(bitmapDescriptor).anchor(0.5f, 0.5f));
}
cluster.setMapMarker(marker);
}
}
}
}
else {
if (mPreviousZoomLevel == -1) {
mPreviousZoomLevel = Math.round(position.zoom);
}
else if (mPreviousZoomLevel != Math.round(position.zoom)) {
mPreviousZoomLevel = Math.round(position.zoom);
new RecalculateAndRedrawClustersBasedOnZoomLevelAsyncTask().execute(Math.round(mMapUI.getCameraPosition().zoom));
}
else {
redrawMarkersOnMapBasedOnVisibleRegion();
}
}
}
@Override
public void onConfigurationChanged(Configuration config) {
super.onConfigurationChanged(config);
Thread redrawThread = new Thread() {
@Override
public void run() {
try {
Thread.sleep(250);
}
catch (Exception ignore) {}
runOnUiThread(new Thread() {
@Override
public void run() {
redrawMarkersOnMapBasedOnVisibleRegion();
}
});
}
};
redrawThread.start();
}
@Override
public void onCancel() {
}
@Override
public void onFinish() {
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case SUBREGION_MAP_TABLET_ACTIVITY_REQUEST_CODE:
if (resultCode == Activity.RESULT_OK) {
mQueryMode = SUBREGION_QUERY_MODE;
mSelectedSubregionIds = data.getIntegerArrayListExtra(AsamConstants.SUBREGION_QUERY_SUBREGIONS_LIST_KEY);
Calendar timePeriod = new GregorianCalendar();
int timeSpan = data.getIntExtra(AsamConstants.SUBREGION_QUERY_TIME_SPAN_KEY, AsamConstants.TIME_SPAN_60_DAYS);
if (timeSpan == AsamConstants.TIME_SPAN_1_YEAR) {
timePeriod.add(Calendar.YEAR, -1);
}
else if (timeSpan == AsamConstants.TIME_SPAN_5_YEARS) {
timePeriod.add(Calendar.YEAR, -5);
}
else if (timeSpan == AsamConstants.TIME_SPAN_ALL) {
timePeriod.setTime(initAndGetEarliestAsamDate());
}
else {
timePeriod.add(Calendar.DAY_OF_YEAR, -timeSpan);
}
mQueryModeMessageContainerUI.setVisibility(View.VISIBLE);
mQueryModeMessageTextViewUI.setText(Html.fromHtml(String.format(getResources().getString(R.string.all_asams_map_tablet_subregions_query_mode_message_text), AsamUtils.getCommaSeparatedStringFromIntegerList(mSelectedSubregionIds))));
mTimeSliderUI.setProgress(calculateTimeSliderTicksFromDate(timePeriod.getTime()));
mQueryProgressDialog = ProgressDialog.show(this, getString(R.string.all_asams_map_tablet_query_progress_dialog_title_text), getString(R.string.all_asams_map_tablet_query_progress_dialog_content_text), true);
new QueryThread(timePeriod).start();
}
break;
}
}
@Override
public void onTextQuery(TextQueryParametersBean textQueryParameters) {
DialogFragment dialogFragment = (DialogFragment)getSupportFragmentManager().findFragmentByTag(AsamConstants.TEXT_QUERY_DIALOG_TAG);
if (dialogFragment != null) {
dialogFragment.dismiss();
}
mTextQueryParametersBean = textQueryParameters;
mQueryMode = TEXT_QUERY_MODE;
// Populate the from and to dates for the text query.
if (!AsamUtils.isEmpty(textQueryParameters.mDateFrom)) {
try {
mTextQueryDateEarliest = AsamDbHelper.TEXT_QUERY_DATE_FORMAT.parse(textQueryParameters.mDateFrom);
}
catch (ParseException caught) {
mTextQueryDateEarliest = initAndGetEarliestAsamDate();
}
}
else {
mTextQueryDateEarliest = initAndGetEarliestAsamDate();
}
if (!AsamUtils.isEmpty(textQueryParameters.mDateTo)) {
try {
mTextQueryDateLatest = AsamDbHelper.TEXT_QUERY_DATE_FORMAT.parse(textQueryParameters.mDateTo);
}
catch (ParseException caught) {
mTextQueryDateLatest = new Date();
}
}
else {
mTextQueryDateLatest = new Date();
}
mQueryModeMessageContainerUI.setVisibility(View.VISIBLE);
mQueryModeMessageTextViewUI.setText(Html.fromHtml(String.format(getResources().getString(R.string.all_asams_map_tablet_text_query_mode_message_text), textQueryParameters.getParametersAsFormattedHtml())));
mTimeSliderUI.setProgress(TOTAL_TIME_SLIDER_TICKS - 1); // All of the time will be shown.
mQueryProgressDialog = ProgressDialog.show(this, getString(R.string.all_asams_map_tablet_query_progress_dialog_title_text), getString(R.string.all_asams_map_tablet_query_progress_dialog_content_text), true);
new QueryThread().start();
}
public void onDisclaimerDialogDismissed(boolean exitApplication) {
if (exitApplication) {
finish();
}
else {
mDisclaimerPopupShowing = false;
setMenuItemsState(true);
mTimeSliderUI.setEnabled(true);
new QueryThread(getInitialTimePeriodQuery()).start();
mQueryProgressDialog = ProgressDialog.show(this, getString(R.string.all_asams_map_tablet_query_progress_dialog_title_text), getString(R.string.all_asams_map_tablet_query_progress_dialog_content_text), true);
}
}
public void onPreferencesDialogDismissed(boolean hideDisclaimer) {
DialogFragment dialogFragment = (DialogFragment)getSupportFragmentManager().findFragmentByTag(AsamConstants.PREFERENCES_DIALOG_TAG);
if (dialogFragment != null) {
dialogFragment.dismiss();
}
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean(AsamConstants.HIDE_DISCLAIMER_KEY, hideDisclaimer);
editor.commit();
}
public void disclaimerButtonClicked(View view) {
// no-op. Taken care of by onDisclaimerDialogDismissed and onPreferencesDialogDismissed.
}
public void syncButtonClicked(View view) {
PreferencesDialogFragment dialogFragment = (PreferencesDialogFragment)getSupportFragmentManager().findFragmentByTag(AsamConstants.PREFERENCES_DIALOG_TAG);
if (dialogFragment != null) {
if (SyncTime.isSynched(this)) {
Toast.makeText(this, getString(R.string.preferences_no_sync_description_text), Toast.LENGTH_LONG).show();
}
else {
mQueryProgressDialog = ProgressDialog.show(this, getString(R.string.all_asams_map_tablet_query_progress_dialog_title_text), getString(R.string.all_asams_map_tablet_query_progress_dialog_content_text), true);
new QueryThread(true).start();
}
}
}
public void legalRowClicked(View view) {
DialogFragment dialogFragment = (DialogFragment)getSupportFragmentManager().findFragmentByTag(AsamConstants.INFO_DIALOG_TAG);
if (dialogFragment != null) {
dialogFragment.dismiss();
}
Intent intent = new Intent(this, LegalTabletActivity.class);
startActivity(intent);
}
public void emailLinkClicked(View view) {
DialogFragment dialogFragment = (DialogFragment)getSupportFragmentManager().findFragmentByTag(AsamConstants.INFO_DIALOG_TAG);
if (dialogFragment != null) {
dialogFragment.dismiss();
}
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
String[] recipients = { getString(R.string.info_fragment_email_address) };
intent.putExtra(android.content.Intent.EXTRA_EMAIL, recipients);
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(R.string.info_fragment_email_subject_text));
intent.setType("plain/text");
startActivity(intent);
}
private void setMenuItemsState(boolean enabled) {
mListViewMenuItemUI.setEnabled(enabled);
mSubregionsMenuItemUI.setEnabled(enabled);
mSettingsMenuItemUI.setEnabled(enabled);
mTextQueryMenuItemUI.setEnabled(enabled);
mInfoMenuItemUI.setEnabled(enabled);
}
private void redrawMarkersOnMapBasedOnVisibleRegion() {
LatLngBounds bounds = mMapUI.getProjection().getVisibleRegion().latLngBounds;
int zoomLevel = Math.round(mMapUI.getCameraPosition().zoom);
final List<AsamMapClusterBean> clustersToAddToMap = new ArrayList<AsamMapClusterBean>();
final List<AsamMapClusterBean> clustersToRemoveFromMap = new ArrayList<AsamMapClusterBean>();
for (AsamMapClusterBean mapCluster : mMapClusters) {
if (bounds.contains(mapCluster.getClusteredMapPosition()) || zoomLevel <= AsamConstants.ZOOM_LEVEL_TO_DRAW_ALL_CLUSTERS) {
clustersToAddToMap.add(mapCluster);
}
}
synchronized (Mutex) {
for (AsamMapClusterBean mapCluster : mVisibleClusters) {
if (!bounds.contains(mapCluster.getClusteredMapPosition()) && zoomLevel > AsamConstants.ZOOM_LEVEL_TO_DRAW_ALL_CLUSTERS) {
clustersToRemoveFromMap.add(mapCluster);
}
}
for (AsamMapClusterBean mapCluster : clustersToRemoveFromMap) {
mapCluster.getMapMarker().remove(); // Remove from map.
mVisibleClusters.remove(mapCluster); // Remove from visible marker list.
}
for (AsamMapClusterBean mapCluster : clustersToAddToMap) {
// Only add it if not already visible.
if (!mVisibleClusters.contains(mapCluster)) {
Marker marker;
if (mapCluster.getAsams().size() == 1) {
marker = mMapUI.addMarker(new MarkerOptions().position(mapCluster.getClusteredMapPosition()).icon(AsamConstants.PIRATE_MARKER).anchor(0.5f, 0.5f));
}
else {
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(AsamUtils.drawNumberOnClusterMarker(AllAsamsMapTabletActivity.this, mapCluster.getAsams().size()));
marker = mMapUI.addMarker(new MarkerOptions().position(mapCluster.getClusteredMapPosition()).icon(bitmapDescriptor).anchor(0.5f, 0.5f));
}
mapCluster.setMapMarker(marker);
mVisibleClusters.add(mapCluster);
}
}
}
}
private Date initAndGetEarliestAsamDate() {
if (mEarliestAsamDate == null) {
SQLiteDatabase db = null;
try {
AsamDbHelper dbHelper = new AsamDbHelper(this);
db = dbHelper.getReadableDatabase();
if (mEarliestAsamDate == null) {
mEarliestAsamDate = dbHelper.getMinOccurrenceDate(db);
}
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.MONTH, Calendar.MAY);
calendar.set(Calendar.DAY_OF_MONTH, 1);
calendar.set(Calendar.YEAR, 1978);
Date earliestDate = calendar.getTime();
if (mEarliestAsamDate.before(earliestDate)) {
mEarliestAsamDate = earliestDate;
}
}
finally {
if (db != null) {
db.close();
}
}
}
return mEarliestAsamDate;
}
private Calendar getInitialTimePeriodQuery() {
Calendar timePeriod = new GregorianCalendar();
timePeriod.add(Calendar.YEAR, -1);
mTimeSliderUI.setProgress(calculateTimeSliderTicksFromDate(timePeriod.getTime()));
return timePeriod;
}
private Date calculateQueryDateFromTimeSlider(int timeSliderTick) {
Date currentDate = new Date();
long totalDateRangeInMilliseconds = currentDate.getTime() - initAndGetEarliestAsamDate().getTime();
long millisecondsFromLatestAsamDate = Math.round(((double)totalDateRangeInMilliseconds / TOTAL_TIME_SLIDER_TICKS) * timeSliderTick);
return new Date(currentDate.getTime() - millisecondsFromLatestAsamDate);
}
private Date calculateTextQueryDateFromTimeSlider(int timeSliderTick) {
long totalDateRangeInMilliseconds = mTextQueryDateLatest.getTime() - mTextQueryDateEarliest.getTime();
long millisecondsFromTextQueryDateFrom = Math.round(((double)totalDateRangeInMilliseconds / TOTAL_TIME_SLIDER_TICKS) * timeSliderTick);
return new Date(mTextQueryDateLatest.getTime() - millisecondsFromTextQueryDateFrom);
}
private int calculateTimeSliderTicksFromDate(Date date) {
Date currentDate = new Date();
long totalDateRangeInMilliseconds = currentDate.getTime() - initAndGetEarliestAsamDate().getTime();
double percentage = (currentDate.getTime() - date.getTime()) / (double)totalDateRangeInMilliseconds;
return (int)Math.round(TOTAL_TIME_SLIDER_TICKS * percentage);
}
private class RecalculateAndRedrawClustersBasedOnZoomLevelAsyncTask extends AsyncTask<Integer, Void, Void> {
@Override
protected void onPreExecute() {
clearAsamMarkers();
}
@Override
protected Void doInBackground(Integer... zoomLevel) {
mMapClusters.clear();
if (mAsams.size() < AsamConstants.MAX_NUM_ASAMS_FOR_NO_CLUSTERING_WITH_ZOOM_LEVEL && zoomLevel[0] > AsamConstants.MAX_ZOOM_LEVEL_FOR_CLUSTERING) {
// Turn off clustering. Zoomed in enough.
for (AsamBean asam : mAsams) {
List<AsamBean> asams = new ArrayList<AsamBean>();
asams.add(asam);
AsamMapClusterBean cluster = new AsamMapClusterBean(asams, new LatLng(asam.getLatitude(), asam.getLongitude()));
mMapClusters.add(cluster);
}
}
else {
// Use the PoffenCluster library to calculate the clusters.
int numLatitudeCells = (int)(Math.round(Math.pow(2, zoomLevel[0])));
int numLongitudeCells = (int)(Math.round(Math.pow(2, zoomLevel[0])));
PoffenClusterCalculator<AsamBean> calculator = new PoffenClusterCalculator.Builder<AsamBean>(numLatitudeCells, numLongitudeCells).mergeLargeClusters(false).build();
for (AsamBean asam : mAsams) {
calculator.add(asam, new PoffenPoint(asam.getLatitude(), asam.getLongitude()));
}
List<PoffenCluster<AsamBean>> poffenClusters = calculator.getPoffenClusters();
for (PoffenCluster<AsamBean> poffenCluster : poffenClusters) {
PoffenPoint poffenPoint = poffenCluster.getClusterCoordinateClosestToMean();
AsamMapClusterBean cluster = new AsamMapClusterBean(poffenCluster.getClusterItems(), new LatLng(poffenPoint.getLatitude(), poffenPoint.getLongitude()));
mMapClusters.add(cluster);
}
}
synchronized (Mutex) {
mVisibleClusters.clear();
}
return null;
}
@Override
protected void onPostExecute(Void empty) {
redrawMarkersOnMapBasedOnVisibleRegion();
}
}
private class QueryThread extends Thread {
private static final int TIME_SLIDER_QUERY = 0;
private static final int TIME_PERIOD_QUERY = 1;
private static final int STANDARD_QUERY = 2;
private int mQueryType;
private int mTimeSliderTick;
private Calendar mTimePeriod;
private boolean mSynchronizationQuery;
QueryThread() {
mQueryType = STANDARD_QUERY;
mPerformBoundsAdjustmentWithQuery = true;
mSynchronizationQuery = false;
}
QueryThread(boolean synchronizationQuery) {
mSynchronizationQuery = synchronizationQuery;
}
QueryThread(int timeSliderTick) {
mQueryType = TIME_SLIDER_QUERY;
mTimeSliderTick = timeSliderTick;
mPerformBoundsAdjustmentWithQuery = false;
mSynchronizationQuery = false;
}
QueryThread(Calendar timePeriod) {
mQueryType = TIME_PERIOD_QUERY;
mTimePeriod = timePeriod;
mPerformBoundsAdjustmentWithQuery = true;
mSynchronizationQuery = false;
}
@Override
public void run() {
Context context = AllAsamsMapTabletActivity.this;
String json = null;
SQLiteDatabase db = null;
if (!SyncTime.isSynched(context)) {
try {
AsamWebService webService = new AsamWebService(context);
json = webService.query();
if (!AsamUtils.isEmpty(json)) {
AsamJsonParser parser = new AsamJsonParser();
List<AsamBean> asams = parser.parseJson(json);
if (asams.size() > 0) {
// Do a diff of what the web service returned and what's currently in the db.
AsamDbHelper dbHelper = new AsamDbHelper(context);
db = dbHelper.getWritableDatabase();
asams = dbHelper.removeDuplicates(db, asams);
dbHelper.insertAsams(db, asams);
}
}
SyncTime.finishedSync(context);
}
catch (Exception caught) {
AsamLog.e(AllAsamsMapTabletActivity.class.getName() + ":There was an error parsing ASAM feed", caught);
mQueryError = true;
}
finally {
if (db != null) {
db.close();
db = null;
}
}
}
if (mSynchronizationQuery) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mQueryProgressDialog.dismiss();
if (!mQueryError) {
Toast.makeText(AllAsamsMapTabletActivity.this, getString(R.string.preferences_sync_complete_description_text), Toast.LENGTH_LONG).show();
}
else {
Toast.makeText(AllAsamsMapTabletActivity.this, getString(R.string.preferences_error_sync_description_text), Toast.LENGTH_LONG).show();
}
}
});
return;
}
try {
// Query for the time period based on the slider.
synchronized (Mutex) {
mAsams.clear();
AsamDbHelper dbHelper = new AsamDbHelper(context);
db = dbHelper.getReadableDatabase();
long totalNumberOfAsams = dbHelper.getTotalNumberOfAsams(db);
Calendar timePeriod = null;
if (mQueryType == TIME_SLIDER_QUERY) {
timePeriod = new GregorianCalendar();
if (mTimeSliderTick == TOTAL_TIME_SLIDER_TICKS - 1) {
timePeriod.setTime(initAndGetEarliestAsamDate());
}
else {
timePeriod.setTime(calculateQueryDateFromTimeSlider(mTimeSliderTick));
}
}
else if (mQueryType == TIME_PERIOD_QUERY) {
timePeriod = mTimePeriod;
}
if (mQueryMode == ALL_QUERY_MODE) {
mAsams.addAll(dbHelper.queryByTime(db, timePeriod));
}
else if (mQueryMode == SUBREGION_QUERY_MODE) {
mAsams.addAll(dbHelper.queryByTimeAndSubregions(db, timePeriod, mSelectedSubregionIds));
}
else if (mQueryMode == TEXT_QUERY_MODE) {
TextQueryParametersBean parameters = TextQueryParametersBean.newInstance(mTextQueryParametersBean);
parameters.mDateTo = AsamDbHelper.TEXT_QUERY_DATE_FORMAT.format(mTextQueryDateLatest);
if (mQueryType == STANDARD_QUERY) {
parameters.mDateFrom = AsamDbHelper.TEXT_QUERY_DATE_FORMAT.format(mTextQueryDateEarliest);
}
else if (mQueryType == TIME_SLIDER_QUERY) {
if (mTimeSliderTick == 0) {
parameters.mDateFrom = AsamDbHelper.TEXT_QUERY_DATE_FORMAT.format(mTextQueryDateLatest);
}
else if (mTimeSliderTick == TOTAL_TIME_SLIDER_TICKS - 1) {
parameters.mDateFrom = AsamDbHelper.TEXT_QUERY_DATE_FORMAT.format(mTextQueryDateEarliest);
}
else {
parameters.mDateFrom = AsamDbHelper.TEXT_QUERY_DATE_FORMAT.format(calculateTextQueryDateFromTimeSlider(mTimeSliderTick));
}
}
mAsams.addAll(dbHelper.queryByText(db, parameters));
}
if (mQueryMode == TEXT_QUERY_MODE) {
if (mQueryType == STANDARD_QUERY) {
mDateRangeText = String.format(DATE_RANGE_PATTERN, DATE_RANGE_FORMAT.format(mTextQueryDateLatest), DATE_RANGE_FORMAT.format(mTextQueryDateEarliest));
}
else if (mQueryType == TIME_SLIDER_QUERY) {
if (mTimeSliderTick == 0) {
mDateRangeText = String.format(DATE_RANGE_PATTERN, DATE_RANGE_FORMAT.format(mTextQueryDateLatest), DATE_RANGE_FORMAT.format(mTextQueryDateLatest));
}
else if (mTimeSliderTick == TOTAL_TIME_SLIDER_TICKS - 1) {
mDateRangeText = String.format(DATE_RANGE_PATTERN, DATE_RANGE_FORMAT.format(mTextQueryDateLatest), DATE_RANGE_FORMAT.format(mTextQueryDateEarliest));
}
else {
mDateRangeText = String.format(DATE_RANGE_PATTERN, DATE_RANGE_FORMAT.format(mTextQueryDateLatest), DATE_RANGE_FORMAT.format(calculateTextQueryDateFromTimeSlider(mTimeSliderTick)));
}
}
}
else {
mDateRangeText = String.format(DATE_RANGE_PATTERN, DATE_RANGE_FORMAT.format(new Date()), DATE_RANGE_FORMAT.format(timePeriod.getTime()));
}
mTotalAsamsText = String.format(TOTAL_ASAMS_PATTERN, mAsams.size(), totalNumberOfAsams);
}
}
finally {
if (db != null) {
db.close();
}
}
mQueryHandler.sendEmptyMessage(0);
}
}
private void clearAsamMarkers() {
for (AsamMapClusterBean mapCluster : mVisibleClusters) {
mapCluster.getMapMarker().remove(); // Remove from map.
}
}
public void onMapTypeChanged(int mapType) {
mMapType = mapType;
// Show/hide the offline alert fragment based on map type
if (mMapType == AsamConstants.MAP_TYPE_OFFLINE_110M) {
getSupportFragmentManager()
.beginTransaction()
.hide(offlineAlertFragment)
.commit();
} else {
getSupportFragmentManager()
.beginTransaction()
.show(offlineAlertFragment)
.commit();
}
// Change the map
if (mMapType == AsamConstants.MAP_TYPE_OFFLINE_110M) {
if (offlineMap != null) offlineMap.clear();
offlineMap = new OfflineMap(this, mMapUI, offlineGeometries);
} else {
if (offlineMap != null) {
offlineMap.clear();
offlineMap = null;
}
mMapUI.setMapType(mMapType);
}
// Update shared preferences
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putInt(AsamConstants.MAP_TYPE_KEY, mMapType);
editor.commit();
}
@Override
public void onOfflineFeaturesLoaded(Collection<Geometry> offlineGeometries) {
this.offlineGeometries = offlineGeometries;
if (offlineMap110mMenuItem != null) offlineMap110mMenuItem.setVisible(true);
if (offlineMap == null && mMapType == AsamConstants.MAP_TYPE_OFFLINE_110M) {
if (offlineMap != null) offlineMap.clear();
offlineMap = new OfflineMap(this, mMapUI, offlineGeometries);
}
}
@Override
public void onOfflineBannerClick() {
onMapTypeChanged(AsamConstants.MAP_TYPE_OFFLINE_110M);
supportInvalidateOptionsMenu();
}
}