/******************************************************************************* * Gaggle is Copyright 2010 by Geeksville Industries LLC, a California limited liability corporation. * * Gaggle is distributed under a dual license. We've chosen this approach because within Gaggle we've used a number * of components that Geeksville Industries LLC might reuse for commercial products. Gaggle can be distributed under * either of the two licenses listed below. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Commercial Distribution License * If you would like to distribute Gaggle (or portions thereof) under a license other than * the "GNU General Public License, version 2", contact Geeksville Industries. Geeksville Industries reserves * the right to release Gaggle source code under a commercial license of its choice. * * GNU Public License, version 2 * All other distribution of Gaggle must conform to the terms of the GNU Public License, version 2. The full * text of this license is included in the Gaggle source, see assets/manual/gpl-2.0.txt. ******************************************************************************/ package com.geeksville.gaggle; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.util.Observable; import java.util.Observer; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.database.Cursor; import android.location.Location; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.text.format.DateFormat; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.webkit.WebView; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.TextView; import android.widget.Toast; import com.flurry.android.FlurryAgent; import com.geeksville.android.AndroidUtil; import com.geeksville.android.DBListActivity; import com.geeksville.info.Units; import com.geeksville.io.LineEndingStream; import com.geeksville.location.ExtendedWaypoint; import com.geeksville.location.GPSClientStub; import com.geeksville.location.LocationLogDbAdapter; import com.geeksville.location.WPTImporter; import com.geeksville.location.Waypoint; import com.geeksville.location.WaypointCursor; import com.geeksville.location.WaypointDB; import com.geeksville.view.AsyncProgressDialog; public class ListWaypointsActivity extends DBListActivity implements Observer { private WaypointDB db; private GPSClientStub gps; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.waypoint_main); isConfirmDeletes = false; // Deleting a waypoint isn't such a big deal - // don't require confirm db = ((GaggleApplication) getApplication()).getWaypoints(); super.onCreate(savedInstanceState); WebView view = (WebView) findViewById(android.R.id.empty); // view.getSettings().setJavaScriptEnabled(true); view.loadUrl(getResources().getString(R.string.no_waypoints_url)); perhapsAddFromUri(); } /** * @see android.app.Activity#onPause() */ @Override protected void onPause() { if (db != null) db.deleteObserver(this); gps.close(); super.onPause(); } /** * @see android.app.Activity#onResume() */ @Override protected void onResume() { super.onResume(); Units.instance.setFromPrefs(this); gps = new GPSClientStub(this); // FIXME, close the backing DB when the waypoint cache is done with it db.addObserver(this); } /** * Create our options menu * * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) */ @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.waypoint_optionmenu, menu); return true; } /** * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.add_menu: handleAddWaypoint(); return true; case R.id.delete_menu: handleDeleteMenu(); return true; } return super.onOptionsItemSelected(item); } /** * * @see android.app.Activity#onCreateContextMenu(android.view.ContextMenu, * android.view.View, android.view.ContextMenu.ContextMenuInfo) */ @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); getMenuInflater().inflate(R.menu.waypoint_context, menu); } /** * Handle our context menu * * @see android.app.Activity#onContextItemSelected(android.view.MenuItem) */ @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.goto_menu: handleGotoWaypoint(item); return true; default: break; } return super.onContextItemSelected(item); } /** * Handle clicks on an individual waypoint */ @Override protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); handleViewItem(position); } /** * See if the user wants us to open an IGC file FIXME - confirm with user */ private void perhapsAddFromUri() { Intent intent = getIntent(); String typ = intent.getType(); final Uri uri = intent.getData(); String action = intent.getAction(); if (uri != null && action != null && action.equals(Intent.ACTION_VIEW)) { Log.d("ListWaypointsActivity", "Considering " + typ); AsyncProgressDialog progress = new AsyncProgressDialog(this, getString(R.string.importing_waypoints), getString(R.string.please_wait)) { @Override protected void doInBackground() { FlurryAgent.onEvent("WPT import start"); // See if we can read the file try { InputStream s = AndroidUtil.getFromURI( ListWaypointsActivity.this, uri); WPTImporter imp = new WPTImporter(db); int numadded = imp.addFromStream(s); if (numadded > 0) {// Save file locally. waypointsToFile(imp.fileContents, uri.getLastPathSegment()); } String msg = String.format( getString(R.string.imported_d_waypoints), numadded); showCompletionToast(msg); FlurryAgent.onEvent("WPT import success"); } catch (Exception ex) { FlurryAgent.onEvent("WPT import failed"); showCompletionDialog(getString(R.string.import_failed), ex.getLocalizedMessage()); } } /* * (non-Javadoc) * * @see * com.geeksville.view.AsyncProgressDialog#onPostExecute(java * .lang.Void) */ @Override protected void onPostExecute(Void unused) { super.onPostExecute(unused); if (isShowingDialog()) finish(); // Exit our activity - go back to the // webserver because we failed else myCursor.requery(); finish(); } }; progress.execute(); } } private boolean waypointsToFile(String waypoints, String filename) throws IOException { PrintStream out = null; try { if (!Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) return false; File sdcard = Environment.getExternalStorageDirectory(); if (!sdcard.exists()) return false; String path = getString(R.string.file_folder); File directory = new File(sdcard, path); if (!directory.exists()) directory.mkdir(); path += '/' + getString(R.string.waypoints); directory = new File(sdcard, path); if (!directory.exists()) directory.mkdir(); String basename = filename; File fullname = new File(directory, basename); FileOutputStream s = new FileOutputStream(fullname); out = new PrintStream(new LineEndingStream(s)); out.print(waypoints); } finally { out.close(); } return true; } /** * @see com.geeksville.android.DBListActivity#createCursor() */ @Override protected Cursor createCursor() { Cursor c = db.fetchWaypointsByDistance(); if (c.getCount() > 0) // If we have any points, encourage the user to turn on the GPS so // we can show distance ((GaggleApplication) getApplication()).enableGPS(this); return c; } /* * (non-Javadoc) * * @see com.geeksville.android.DBListActivity#createListAdapter() */ @Override protected BaseAdapter createListAdapter() { // Create an array to specify the fields we want to display in the // list String[] from = new String[] { LocationLogDbAdapter.KEY_NAME, LocationLogDbAdapter.KEY_DESCRIPTION, WaypointCursor.KEY_DIST_PILOTX, LocationLogDbAdapter.KEY_WAYPOINT_TYPE, WaypointCursor.KEY_DIST_PILOTY }; // and an array of the fields we want to bind those fields to int[] to = new int[] { R.id.name, R.id.description, R.id.distance, R.id.image, R.id.units }; // Now create a simple cursor adapter and set it to display SimpleCursorAdapter a = new SimpleCursorAdapter(this, R.layout.waypoint_row, myCursor, from, to); final int distcol = myCursor .getColumnIndex(WaypointCursor.KEY_DIST_PILOTX); final int distycol = myCursor .getColumnIndex(WaypointCursor.KEY_DIST_PILOTY); final int typecol = myCursor .getColumnIndex(LocationLogDbAdapter.KEY_WAYPOINT_TYPE); a.setViewBinder(new SimpleCursorAdapter.ViewBinder() { public boolean setViewValue(View _view, Cursor cursor, int columnIndex) { try { // Show the distance with correct units if (columnIndex == distcol) { TextView view = (TextView) _view; int dist = cursor.getInt(distcol); // view.setVisibility(dist == -1 ? View.INVISIBLE : // View.VISIBLE); String distStr = (dist == -1) ? "---" : Units.instance .metersToDistance(dist); view.setText(distStr); return true; } // We don't care about pilot y, but this is a skanky hack to // set the units view string if (columnIndex == distycol) { TextView view = (TextView) _view; view.setText(Units.instance.getDistanceUnits()); return true; } // Show the distance with correct units if (columnIndex == typecol) { ImageView view = (ImageView) _view; ExtendedWaypoint w = ((WaypointCursor) cursor) .getWaypoint(); view.setImageDrawable(w.getIcon()); return true; } } catch (RuntimeException ex) { Log.d("ListWaypoint", "Caught exception building waypoint list: " + ex.getMessage()); } return false; } }); return a; } /* * (non-Javadoc) * * @see * com.geeksville.android.DBListActivity#handleDeleteItem(android.view.MenuItem * ) */ @Override protected boolean handleDeleteItem(MenuItem item) { db.deleteWaypoint(itemToRowId(item)); return true; } /** * Delete all waypoints */ private void handleDeleteMenu() { // Is the user sure? AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.delete_waypoints); builder.setMessage(R.string.are_you_sure); builder.setNegativeButton(R.string.cancel, null); builder.setPositiveButton(R.string.delete, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { db.deleteAllWaypoints(); myCursor.requery(); } }); AlertDialog alert = builder.create(); alert.show(); } private void handleAddWaypoint() { GaggleApplication app = ((GaggleApplication) getApplication()); Location myloc = null; if (gps != null && gps.get() != null) myloc = gps.get().getLastKnownLocation(); if (myloc == null) Toast.makeText(ListWaypointsActivity.this, R.string.can_not_add_waypoint_still_waiting_for_gps_fix, Toast.LENGTH_LONG).show(); else { java.util.Date now = new java.util.Date(); String name = DateFormat.format("yy/MM/dd kk:mm:ss", now) .toString(); ExtendedWaypoint w = new ExtendedWaypoint(name, myloc.getLatitude(), myloc.getLongitude(), (int) myloc.getAltitude(), 0, Waypoint.Type.Unknown.ordinal()); app.getWaypoints().add(w); myCursor.requery(); // FIXME - then select it in the cursor/GUI Toast.makeText(ListWaypointsActivity.this, R.string.waypoint_created, Toast.LENGTH_SHORT).show(); } } private void handleGotoWaypoint(MenuItem item) { gotoWaypoint(itemToRowNum(item)); } private void gotoWaypoint(int rownum) { myCursor.moveToPosition(rownum); final ExtendedWaypoint w = ((WaypointCursor) myCursor).getWaypoint(); ((GaggleApplication) getApplication()).currentDestination = w; Toast.makeText(ListWaypointsActivity.this, getString(R.string.new_destination) + w.name, Toast.LENGTH_SHORT).show(); } /* * (non-Javadoc) * * @see * com.geeksville.android.DBListActivity#handleViewItem(android.view.MenuItem * ) */ @Override protected void handleViewItem(MenuItem item) { handleViewItem(itemToRowNum(item)); } private void handleViewItem(final int rownum) { myCursor.moveToPosition(rownum); final ExtendedWaypoint w = ((WaypointCursor) myCursor).getWaypoint(); Runnable onOkay = new Runnable() { @Override public void run() { w.commit(); myCursor.requery(); Toast.makeText(ListWaypointsActivity.this, R.string.updated_waypoint, Toast.LENGTH_SHORT).show(); } }; Runnable onGoto = new Runnable() { @Override public void run() { // Just in case the user changed the waypoint w.commit(); myCursor.requery(); gotoWaypoint(rownum); } }; WaypointDialog d = new WaypointDialog(this, w, onOkay, onGoto); d.show(); } @Override public void update(Observable observable, Object data) { // myCursor.requery(); // FIXME - don't do this if the user is moving // around? } }