package net.osmand.plus.monitoring;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.support.v7.app.AlertDialog;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import net.osmand.AndroidUtils;
import net.osmand.Location;
import net.osmand.ValueHolder;
import net.osmand.plus.ApplicationMode;
import net.osmand.plus.NavigationService;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmAndTaskManager.OsmAndTaskRunnable;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.SavingTrackHelper;
import net.osmand.plus.dashboard.tools.DashFragmentData;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.TextInfoWidget;
import java.util.List;
import gnu.trove.list.array.TIntArrayList;
public class OsmandMonitoringPlugin extends OsmandPlugin {
public static final String ID = "osmand.monitoring";
public final static String OSMAND_SAVE_SERVICE_ACTION = "OSMAND_SAVE_SERVICE_ACTION";
private OsmandSettings settings;
private OsmandApplication app;
private TextInfoWidget monitoringControl;
private LiveMonitoringHelper liveMonitoringHelper;
private boolean isSaving;
public OsmandMonitoringPlugin(OsmandApplication app) {
this.app = app;
liveMonitoringHelper = new LiveMonitoringHelper(app);
final List<ApplicationMode> am = ApplicationMode.allPossibleValues();
ApplicationMode.regWidgetVisibility("monitoring", am.toArray(new ApplicationMode[am.size()]));
settings = app.getSettings();
}
@Override
public void disable(OsmandApplication app) {
super.disable(app);
app.getNotificationHelper().refreshNotifications();
}
@Override
public void updateLocation(Location location) {
liveMonitoringHelper.updateLocation(location);
}
@Override
public int getLogoResourceId() {
return R.drawable.ic_action_gps_info;
}
@Override
public int getAssetResourceName() {
return R.drawable.trip_recording;
}
@Override
public String getId() {
return ID;
}
@Override
public String getDescription() {
return app.getString(R.string.record_plugin_description);
}
@Override
public String getName() {
return app.getString(R.string.record_plugin_name);
}
@Override
public String getHelpFileName() {
return "feature_articles/trip-recording-plugin.html";
}
@Override
public void registerLayers(MapActivity activity) {
registerWidget(activity);
}
private void registerWidget(MapActivity activity) {
MapInfoLayer layer = activity.getMapLayers().getMapInfoLayer();
monitoringControl = createMonitoringControl(activity);
layer.registerSideWidget(monitoringControl,
R.drawable.ic_action_play_dark, R.string.map_widget_monitoring, "monitoring", false, 30);
layer.recreateControls();
}
@Override
public void updateLayers(OsmandMapTileView mapView, MapActivity activity) {
if (isActive()) {
if (monitoringControl == null) {
registerWidget(activity);
}
} else {
if (monitoringControl != null) {
MapInfoLayer layer = activity.getMapLayers().getMapInfoLayer();
layer.removeSideWidget(monitoringControl);
layer.recreateControls();
monitoringControl = null;
}
}
}
public static final int[] SECONDS = new int[] {0, 1, 2, 3, 5, 10, 15, 30, 60, 90};
public static final int[] MINUTES = new int[] {2, 3, 5};
@Override
public Class<? extends Activity> getSettingsActivity() {
return SettingsMonitoringActivity.class;
}
/**
* creates (if it wasn't created previously) the control to be added on a MapInfoLayer that shows a monitoring state (recorded/stopped)
*/
private TextInfoWidget createMonitoringControl(final MapActivity map) {
monitoringControl = new TextInfoWidget(map) {
long lastUpdateTime;
@Override
public boolean updateInfo(DrawSettings drawSettings) {
if(isSaving){
setText(map.getString(R.string.shared_string_save), "");
setIcons(R.drawable.widget_monitoring_rec_big_day, R.drawable.widget_monitoring_rec_big_night);
return true;
}
String txt = map.getString(R.string.monitoring_control_start);
String subtxt = null;
int dn = R.drawable.widget_monitoring_rec_inactive_night;
int d = R.drawable.widget_monitoring_rec_inactive_day;
long last = lastUpdateTime;
final boolean globalRecord = settings.SAVE_GLOBAL_TRACK_TO_GPX.get();
final boolean isRecording = app.getSavingTrackHelper().getIsRecording();
float dist = app.getSavingTrackHelper().getDistance();
//make sure widget always shows recorded track distance if unsaved track exists
if (dist > 0) {
last = app.getSavingTrackHelper().getLastTimeUpdated();
String ds = OsmAndFormatter.getFormattedDistance(dist, map.getMyApplication());
int ls = ds.lastIndexOf(' ');
if (ls == -1) {
txt = ds;
} else {
txt = ds.substring(0, ls);
subtxt = ds.substring(ls + 1);
}
}
if(globalRecord) {
//indicates global recording (+background recording)
dn = R.drawable.widget_monitoring_rec_big_night;
d = R.drawable.widget_monitoring_rec_big_day;
} else if (isRecording) {
//indicates (profile-based, configured in settings) recording (looks like is only active during nav in follow mode)
dn = R.drawable.widget_monitoring_rec_small_night;
d = R.drawable.widget_monitoring_rec_small_day;
} else {
dn = R.drawable.widget_monitoring_rec_inactive_night;
d = R.drawable.widget_monitoring_rec_inactive_day;
}
setText(txt, subtxt);
setIcons(d, dn);
if ((last != lastUpdateTime) && (globalRecord || isRecording)) {
lastUpdateTime = last;
//blink implementation with 2 indicator states (global logging + profile/navigation logging)
if (globalRecord) {
setIcons(R.drawable.widget_monitoring_rec_small_day,
R.drawable.widget_monitoring_rec_small_night);
} else {
setIcons(R.drawable.widget_monitoring_rec_small_day,
R.drawable.widget_monitoring_rec_small_night);
}
map.getMyApplication().runInUIThread(new Runnable() {
@Override
public void run() {
if (globalRecord) {
setIcons(R.drawable.widget_monitoring_rec_big_day,
R.drawable.widget_monitoring_rec_big_night);
} else {
setIcons(R.drawable.widget_monitoring_rec_small_day,
R.drawable.widget_monitoring_rec_small_night);
}
}
}, 500);
}
return true;
}
};
monitoringControl.updateInfo(null);
// monitoringControl.addView(child);
monitoringControl.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
controlDialog(map, true);
}
});
return monitoringControl;
}
public void controlDialog(final Activity map, final boolean showTrackSelection) {
final boolean wasTrackMonitored = settings.SAVE_GLOBAL_TRACK_TO_GPX.get();
AlertDialog.Builder bld = new AlertDialog.Builder(map);
final TIntArrayList items = new TIntArrayList();
if (wasTrackMonitored) {
items.add(R.string.gpx_monitoring_stop);
items.add(R.string.gpx_start_new_segment);
if(settings.LIVE_MONITORING.get()) {
items.add(R.string.live_monitoring_stop);
} else if(!settings.LIVE_MONITORING_URL.getProfileDefaultValue(settings.APPLICATION_MODE.get()).
equals(settings.LIVE_MONITORING_URL.get())){
items.add(R.string.live_monitoring_start);
}
} else {
items.add(R.string.gpx_monitoring_start);
}
if (app.getSavingTrackHelper().hasDataToSave()) {
items.add(R.string.save_current_track);
}
String[] strings = new String[items.size()];
for (int i = 0; i < strings.length; i++) {
strings[i] = app.getString(items.get(i));
}
final int[] holder = new int[] {0};
final Runnable run = new Runnable() {
public void run() {
int which = holder[0];
int item = items.get(which);
if(item == R.string.save_current_track){
saveCurrentTrack();
} else if(item == R.string.gpx_monitoring_start) {
if (app.getLocationProvider().checkGPSEnabled(map)) {
startGPXMonitoring(map, showTrackSelection);
}
} else if(item == R.string.gpx_monitoring_stop) {
stopRecording();
} else if(item == R.string.gpx_start_new_segment) {
app.getSavingTrackHelper().startNewSegment();
} else if(item == R.string.live_monitoring_stop) {
settings.LIVE_MONITORING.set(false);
} else if(item == R.string.live_monitoring_start) {
final ValueHolder<Integer> vs = new ValueHolder<Integer>();
vs.value = settings.LIVE_MONITORING_INTERVAL.get();
showIntervalChooseDialog(map, app.getString(R.string.live_monitoring_interval) + " : %s",
app.getString(R.string.save_track_to_gpx_globally), SECONDS, MINUTES,
null, vs, showTrackSelection, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
settings.LIVE_MONITORING_INTERVAL.set(vs.value);
settings.LIVE_MONITORING.set(true);
}
});
}
monitoringControl.updateInfo(null);
}
};
if(strings.length == 1) {
run.run();
} else {
bld.setItems(strings, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
holder[0] = which;
run.run();
}
});
bld.show();
}
}
public void saveCurrentTrack() {
app.getTaskManager().runInBackground(new OsmAndTaskRunnable<Void, Void, Void>() {
@Override
protected void onPreExecute() {
isSaving = true;
updateControl();
}
@Override
protected Void doInBackground(Void... params) {
try {
SavingTrackHelper helper = app.getSavingTrackHelper();
helper.saveDataToGpx(app.getAppCustomization().getTracksDir());
helper.close();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
isSaving = false;
app.getNotificationHelper().refreshNotifications();
updateControl();
}
}, (Void) null);
}
public void updateControl() {
if (monitoringControl != null) {
monitoringControl.updateInfo(null);
}
}
public void stopRecording(){
settings.SAVE_GLOBAL_TRACK_TO_GPX.set(false);
if (app.getNavigationService() != null) {
app.getNavigationService().stopIfNeeded(app, NavigationService.USED_BY_GPX);
}
}
public void startGPXMonitoring(final Activity map) {
startGPXMonitoring(map, true);
}
public void startGPXMonitoring(final Activity map, final boolean showTrackSelection) {
final ValueHolder<Integer> vs = new ValueHolder<Integer>();
final ValueHolder<Boolean> choice = new ValueHolder<Boolean>();
vs.value = settings.SAVE_GLOBAL_TRACK_INTERVAL.get();
choice.value = settings.SAVE_GLOBAL_TRACK_REMEMBER.get();
final Runnable runnable = new Runnable() {
public void run() {
app.getSavingTrackHelper().startNewSegment();
settings.SAVE_GLOBAL_TRACK_INTERVAL.set(vs.value);
settings.SAVE_GLOBAL_TRACK_TO_GPX.set(true);
settings.SAVE_GLOBAL_TRACK_REMEMBER.set(choice.value);
int interval = settings.SAVE_GLOBAL_TRACK_INTERVAL.get();
app.startNavigationService(NavigationService.USED_BY_GPX, interval < 30000? 0 : interval);
}
};
if (choice.value || map == null) {
runnable.run();
} else {
showIntervalChooseDialog(map, app.getString(R.string.save_track_interval_globally) + " : %s",
app.getString(R.string.save_track_to_gpx_globally), SECONDS, MINUTES, choice, vs, showTrackSelection,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
runnable.run();
}
});
}
}
public static void showIntervalChooseDialog(final Context uiCtx, final String patternMsg,
String title, final int[] seconds, final int[] minutes,
final ValueHolder<Boolean> choice, final ValueHolder<Integer> v,
final boolean showTrackSelection, OnClickListener onclick){
AlertDialog.Builder dlg = new AlertDialog.Builder(uiCtx);
dlg.setTitle(title);
WindowManager mgr = (WindowManager) uiCtx.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
mgr.getDefaultDisplay().getMetrics(dm);
LinearLayout ll = createIntervalChooseLayout(uiCtx, patternMsg, seconds, minutes,
choice, v, showTrackSelection, dm);
dlg.setView(ll);
dlg.setPositiveButton(R.string.shared_string_ok, onclick);
dlg.setNegativeButton(R.string.shared_string_cancel, null);
dlg.show();
}
public static LinearLayout createIntervalChooseLayout(final Context uiCtx,
final String patternMsg, final int[] seconds,
final int[] minutes, final ValueHolder<Boolean> choice,
final ValueHolder<Integer> v,
final boolean showTrackSelection, DisplayMetrics dm) {
LinearLayout ll = new LinearLayout(uiCtx);
final int dp24 = AndroidUtils.dpToPx(uiCtx, 24f);
final int dp8 = AndroidUtils.dpToPx(uiCtx, 8f);
final TextView tv = new TextView(uiCtx);
tv.setPadding(dp24, dp8 * 2, dp24, dp8);
tv.setText(String.format(patternMsg, uiCtx.getString(R.string.int_continuosly)));
SeekBar sp = new SeekBar(uiCtx);
sp.setPadding(dp24 + dp8, dp8, dp24 + dp8, dp8);
final int secondsLength = seconds.length;
final int minutesLength = minutes.length;
sp.setMax(secondsLength + minutesLength - 1);
sp.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
String s;
if(progress == 0) {
s = uiCtx.getString(R.string.int_continuosly);
v.value = 0;
} else {
if(progress < secondsLength) {
s = seconds[progress] + " " + uiCtx.getString(R.string.int_seconds);
v.value = seconds[progress] * 1000;
} else {
s = minutes[progress - secondsLength] + " " + uiCtx.getString(R.string.int_min);
v.value = minutes[progress - secondsLength] * 60 * 1000;
}
}
tv.setText(String.format(patternMsg, s));
}
});
for (int i = 0; i < secondsLength + minutesLength - 1; i++) {
if (i < secondsLength) {
if (v.value <= seconds[i] * 1000) {
sp.setProgress(i);
break;
}
} else {
if (v.value <= minutes[i - secondsLength] * 1000 * 60) {
sp.setProgress(i);
break;
}
}
}
ll.setOrientation(LinearLayout.VERTICAL);
ll.addView(tv);
ll.addView(sp);
if (choice != null) {
final CheckBox cb = new CheckBox(uiCtx);
cb.setText(R.string.shared_string_remember_my_choice);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT);
lp.setMargins(dp24, dp8, dp24, 0);
cb.setLayoutParams(lp);
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
choice.value = isChecked;
}
});
ll.addView(cb);
}
if (showTrackSelection) {
final OsmandApplication app = (OsmandApplication) uiCtx.getApplicationContext();
boolean light = app.getSettings().isLightContent();
View divider = new View(uiCtx);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
AndroidUtils.dpToPx(uiCtx, 1f));
lp.setMargins(0, dp8 * 2, 0, 0);
divider.setLayoutParams(lp);
divider.setBackgroundColor(uiCtx.getResources().getColor(
light ? R.color.dashboard_divider_light : R.color.dashboard_divider_dark));
ll.addView(divider);
final CheckBox cb = new CheckBox(uiCtx);
cb.setText(R.string.shared_string_show_on_map);
lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT);
lp.setMargins(dp24, dp8 * 2, dp24, 0);
cb.setLayoutParams(lp);
cb.setChecked(app.getSelectedGpxHelper().getSelectedCurrentRecordingTrack() != null);
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
app.getSelectedGpxHelper().selectGpxFile(app.getSavingTrackHelper().getCurrentGpx(), isChecked, false);
}
});
ll.addView(cb);
}
return ll;
}
@Override
public DashFragmentData getCardFragment() {
return DashTrackFragment.FRAGMENT_DATA;
}
}