package org.droidplanner.android.activities; import android.app.ProgressDialog; import android.content.Intent; import android.location.Location; import android.location.LocationListener; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.FragmentManager; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; import com.MAVLink.common.msg_global_position_int; import com.o3dr.android.client.data.tlog.TLogPicker; import com.o3dr.services.android.lib.coordinate.LatLong; import com.o3dr.services.android.lib.data.ServiceDataContract; import com.o3dr.services.android.lib.util.MathUtils; import org.beyene.sius.unit.length.LengthUnit; import org.droidplanner.android.R; import org.droidplanner.android.fragments.LocatorListFragment; import org.droidplanner.android.fragments.LocatorMapFragment; import org.droidplanner.android.utils.file.IO.TLogReader; import org.droidplanner.android.utils.file.IO.TLogReader.Event; import org.droidplanner.android.utils.prefs.AutoPanMode; import org.droidplanner.android.utils.unit.providers.length.LengthUnitProvider; import java.lang.ref.WeakReference; import java.util.LinkedList; import java.util.List; /** * This implements the map locator activity. The map locator activity allows the user to find * a lost drone using last known GPS positions from the tlogs. */ public class LocatorActivity extends DrawerNavigationUI implements LocatorListFragment.OnLocatorListListener, LocationListener { private static final String TAG = LocatorActivity.class.getSimpleName(); private static final String STATE_LAST_SELECTED_POSITION = "STATE_LAST_SELECTED_POSITION"; private static final int TLOG_PICKER_REQUEST_CODE = 101; private final static List<TLogReader.Event> lastPositions = new LinkedList<>(); private OpenTLogFileAsyncTask tlogOpener; /* View widgets. */ private LocatorMapFragment locatorMapFragment; private LocatorListFragment locatorListFragment; private LinearLayout statusView; private TextView latView, lonView, distanceView, azimuthView, altitudeView; private msg_global_position_int selectedMsg; private LatLong lastGCSPosition; private float lastGCSBearingTo = Float.MAX_VALUE; private double lastGCSAzimuth = Double.MAX_VALUE; public List<Event> getLastPositions() { return lastPositions; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_locator); FragmentManager fragmentManager = getSupportFragmentManager(); locatorMapFragment = ((LocatorMapFragment) fragmentManager.findFragmentById(R.id.locator_map_fragment)); if(locatorMapFragment == null){ locatorMapFragment = new LocatorMapFragment(); fragmentManager.beginTransaction().add(R.id.locator_map_fragment, locatorMapFragment).commit(); } locatorListFragment = (LocatorListFragment) fragmentManager.findFragmentById(R.id.locatorListFragment); statusView = (LinearLayout) findViewById(R.id.statusView); latView = (TextView) findViewById(R.id.latView); lonView = (TextView) findViewById(R.id.lonView); distanceView = (TextView) findViewById(R.id.distanceView); azimuthView = (TextView) findViewById(R.id.azimuthView); altitudeView = (TextView) findViewById(R.id.altitudeView); final ImageButton zoomToFit = (ImageButton) findViewById(R.id.zoom_to_fit_button); zoomToFit.setVisibility(View.VISIBLE); zoomToFit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (locatorMapFragment != null) { locatorMapFragment.zoomToFit(); } } }); ImageButton mGoToMyLocation = (ImageButton) findViewById(R.id.my_location_button); mGoToMyLocation.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { locatorMapFragment.goToMyLocation(); } }); mGoToMyLocation.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { locatorMapFragment.setAutoPanMode(AutoPanMode.USER); return true; } }); ImageButton mGoToDroneLocation = (ImageButton) findViewById(R.id.drone_location_button); mGoToDroneLocation.setVisibility(View.GONE); // clear prev state if this is a fresh start if (savedInstanceState == null) { // fresh start lastPositions.clear(); } } @Override public void onResume() { super.onResume(); locatorMapFragment.setLocationReceiver(this); } @Override protected int getToolbarId() { return R.id.actionbar_container; } @Override public void onPause() { super.onPause(); locatorMapFragment.setLocationReceiver(null); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); final int lastSelectedPosition = lastPositions.indexOf(selectedMsg); outState.putInt(STATE_LAST_SELECTED_POSITION, lastSelectedPosition); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); final int lastSelectedPosition = savedInstanceState.getInt(STATE_LAST_SELECTED_POSITION, -1); if (lastSelectedPosition != -1 && lastSelectedPosition < lastPositions.size()) setSelectedMsg((msg_global_position_int) lastPositions.get(lastSelectedPosition).getMavLinkMessage()); } @Override protected int getNavigationDrawerEntryId() { return R.id.navigation_locator; } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.menu_locator, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_open_tlog_file: TLogPicker.startTLogPicker(this, TLOG_PICKER_REQUEST_CODE); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent returnIntent) { if (requestCode != TLOG_PICKER_REQUEST_CODE || resultCode != RESULT_OK) { super.onActivityResult(requestCode, resultCode, returnIntent); return; } //Get the file's absolute path from the incoming intent final String tlogAbsolutePath = returnIntent.getStringExtra(ServiceDataContract.EXTRA_TLOG_ABSOLUTE_PATH); if(tlogOpener != null) tlogOpener.cancel(true); tlogOpener = new OpenTLogFileAsyncTask(this); tlogOpener.execute(tlogAbsolutePath); } /* Copy all messages with non-zero coords -> lastPositions */ private void loadLastPositions(List<TLogReader.Event> logEvents) { lastPositions.clear(); for (TLogReader.Event event : logEvents) { final msg_global_position_int message = (msg_global_position_int) event.getMavLinkMessage(); if (message.lat != 0 || message.lon != 0) lastPositions.add(event); } setSelectedMsg(null); locatorListFragment.notifyDataSetChanged(); updateInfo(); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); updateMapPadding(); } private void updateMapPadding() { int bottomPadding = 0; if (lastPositions.size() > 0) { bottomPadding = locatorListFragment.getView().getHeight(); } locatorMapFragment.setMapPadding(0, 0, 0, bottomPadding); } @Override public void onBackPressed() { super.onBackPressed(); locatorMapFragment.saveCameraPosition(); } @Override public void onItemClick(msg_global_position_int msg) { setSelectedMsg(msg); locatorMapFragment.zoomToFit(); updateInfo(); } public void setSelectedMsg(msg_global_position_int msg) { selectedMsg = msg; final LatLong msgCoord; if (msg != null) msgCoord = coordFromMsgGlobalPositionInt(selectedMsg); else msgCoord = new LatLong(0, 0); locatorMapFragment.updateLastPosition(msgCoord); } private void updateInfo() { if (selectedMsg != null) { statusView.setVisibility(View.VISIBLE); final LengthUnitProvider lengthUnitProvider = unitSystem.getLengthUnitProvider(); final double altitude = selectedMsg.alt / 1000; //meters LengthUnit convertedAltitude = lengthUnitProvider.boxBaseValueToTarget(altitude); altitudeView.setText("Altitude: " + convertedAltitude.toString()); // coords final LatLong msgCoord = coordFromMsgGlobalPositionInt(selectedMsg); // distance if (lastGCSPosition == null || lastGCSPosition.getLatitude() == 0 || lastGCSPosition.getLongitude() == 0) { // unknown distanceView.setText(R.string.status_waiting_for_gps, TextView.BufferType.NORMAL); azimuthView.setText(""); } else { final double distance = MathUtils.getDistance(lastGCSPosition, msgCoord); final LengthUnit convertedDistance = lengthUnitProvider.boxBaseValueToTarget(distance); String distanceText = getString(R.string.editor_info_window_distance, convertedDistance.toString()); if (lastGCSBearingTo != Float.MAX_VALUE) { final String bearing = String.format(" @ %.0f°", lastGCSBearingTo); distanceText += bearing; } distanceView.setText(distanceText); if (lastGCSAzimuth != Double.MAX_VALUE) { final String azimuth = getString(R.string.editor_info_window_heading, lastGCSAzimuth); azimuthView.setText(azimuth); } } latView.setText(getString(R.string.waypoint_latitude, msgCoord.getLatitude())); lonView.setText(getString(R.string.waypoint_longitude, msgCoord.getLongitude())); } else { statusView.setVisibility(View.INVISIBLE); latView.setText(""); lonView.setText(""); distanceView.setText(""); azimuthView.setText(""); altitudeView.setText(""); } } private static LatLong coordFromMsgGlobalPositionInt(msg_global_position_int msg) { double lat = msg.lat; lat /= 1E7; double lon = msg.lon; lon /= 1E7; return new LatLong(lat, lon); } @Override public void onLocationChanged(Location location) { lastGCSPosition = new LatLong(location.getLatitude(), location.getLongitude()); lastGCSAzimuth = location.getBearing(); if (selectedMsg != null) { final LatLong msgCoord = coordFromMsgGlobalPositionInt(selectedMsg); final Location target = new Location(location); target.setLatitude(msgCoord.getLatitude()); target.setLongitude(msgCoord.getLongitude()); lastGCSBearingTo = Math.round(location.bearingTo(target)); lastGCSBearingTo = (lastGCSBearingTo + 360) % 360; } else { lastGCSBearingTo = Float.MAX_VALUE; } updateInfo(); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } private static class OpenTLogFileAsyncTask extends AsyncTask<String, Void, List<TLogReader.Event>> { private final WeakReference<LocatorActivity> activityRef; private final ProgressDialog progressDialog; public OpenTLogFileAsyncTask(LocatorActivity activity) { activityRef = new WeakReference<>(activity); progressDialog = new ProgressDialog(activity); progressDialog.setTitle("Loading data..."); progressDialog.setMessage("Please wait."); progressDialog.setIndeterminate(true); } @Override protected void onPreExecute() { progressDialog.show(); } @Override protected List<TLogReader.Event> doInBackground(String... params) { final String filename = params[0]; TLogReader tlogReader = new TLogReader(msg_global_position_int.MAVLINK_MSG_ID_GLOBAL_POSITION_INT); tlogReader.openTLog(filename); return tlogReader.getLogEvents(); } @Override protected void onCancelled() { progressDialog.dismiss(); } @Override protected void onPostExecute(List<TLogReader.Event> events) { progressDialog.dismiss(); final LocatorActivity activity = activityRef.get(); if (activity == null) return; activity.loadLastPositions(events); activity.locatorMapFragment.zoomToFit(); } } }