Java tutorial
/*------------------------------------------------------------------------------ ** Ident: Innovation en Inspiration > Google Android ** Author: rene ** Copyright: (c) Jan 22, 2009 Sogeti Nederland B.V. All Rights Reserved. **------------------------------------------------------------------------------ ** Sogeti Nederland B.V. | No part of this file may be reproduced ** Distributed Software Engineering | or transmitted in any form or by any ** Lange Dreef 17 | means, electronic or mechanical, for the ** 4131 NJ Vianen | purpose, without the express written ** The Netherlands | permission of the copyright holder. *------------------------------------------------------------------------------ * * This file is part of OpenGPSTracker. * * OpenGPSTracker 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. * * OpenGPSTracker 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 OpenGPSTracker. If not, see <http://www.gnu.org/licenses/>. * */ package nl.sogeti.android.gpstracker.actions; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import java.net.URI; import java.net.URISyntaxException; import nl.sogeti.android.gpstracker.R; import nl.sogeti.android.gpstracker.actions.utils.GpxCreator; import nl.sogeti.android.gpstracker.actions.utils.KmzCreator; import nl.sogeti.android.gpstracker.actions.utils.StatisticsCalulator; import nl.sogeti.android.gpstracker.db.GPStracking; import nl.sogeti.android.gpstracker.db.GPStracking.Media; import nl.sogeti.android.gpstracker.db.GPStracking.MetaData; import nl.sogeti.android.gpstracker.db.GPStracking.Tracks; import nl.sogeti.android.gpstracker.util.Constants; import nl.sogeti.android.gpstracker.util.UnitsI18n; import nl.sogeti.android.gpstracker.viewer.LoggerMap; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthenticationException; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.DefaultHttpClient; import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.Dialog; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.SharedPreferences.Editor; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.RemoteViews; import android.widget.Spinner; import android.widget.Toast; public class ShareTrack extends Activity { private static final int EXPORT_TYPE_KMZ = 0; private static final int EXPORT_TYPE_GPX = 1; private static final int EXPORT_TYPE_TEXTLINE = 2; private static final int EXPORT_TARGET_SAVE = 0; private static final int EXPORT_TARGET_SEND = 1; private static final int EXPORT_TARGET_JOGRUN = 2; private static final int EXPORT_TARGET_OSM = 3; private static final int EXPORT_TYPE_TWITDRIOD = 0; private static final int EXPORT_TYPE_SMS = 1; private static final int EXPORT_TYPE_TEXT = 2; protected static final int DIALOG_FILENAME = 11; protected static final int PROGRESS_STEPS = 10; private static final int DIALOG_INSTALL_TWIDROID = 34; private static final String TAG = "OGT.ShareTrack"; private RemoteViews mContentView; private int barProgress = 0; private Notification mNotification; private NotificationManager mNotificationManager; private EditText mFileNameView; private EditText mTweetView; private Spinner mShareTypeSpinner; private Spinner mShareTargetSpinner; private Uri mTrackUri; private StatisticsCalulator calculator; private OnClickListener mTwidroidDialogListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { Uri twidroidUri = Uri.parse("market://details?id=com.twidroid"); Intent getTwidroid = new Intent(Intent.ACTION_VIEW, twidroidUri); try { startActivity(getTwidroid); } catch (ActivityNotFoundException e) { twidroidUri = Uri.parse("http://twidroid.com/download/"); getTwidroid = new Intent(Intent.ACTION_VIEW, twidroidUri); startActivity(getTwidroid); } } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.sharedialog); mTrackUri = getIntent().getData(); calculator = new StatisticsCalulator(this, new UnitsI18n(this, null)); mFileNameView = (EditText) findViewById(R.id.fileNameField); mTweetView = (EditText) findViewById(R.id.tweetField); mShareTypeSpinner = (Spinner) findViewById(R.id.shareTypeSpinner); ArrayAdapter<CharSequence> shareTypeAdapter = ArrayAdapter.createFromResource(this, R.array.sharetype_choices, android.R.layout.simple_spinner_item); shareTypeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mShareTypeSpinner.setAdapter(shareTypeAdapter); mShareTargetSpinner = (Spinner) findViewById(R.id.shareTargetSpinner); mShareTypeSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { public void onItemSelected(AdapterView<?> arg0, View arg1, int position, long arg3) { adjustTargetToType(position); } public void onNothingSelected(AdapterView<?> arg0) { /* NOOP */ } }); int lastType = PreferenceManager.getDefaultSharedPreferences(this).getInt(Constants.EXPORT_TYPE, EXPORT_TYPE_KMZ); mShareTypeSpinner.setSelection(lastType); adjustTargetToType(lastType); mFileNameView.setText(queryForTrackName()); Button okay = (Button) findViewById(R.id.okayshare_button); okay.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { share(); } }); Button cancel = (Button) findViewById(R.id.cancelshare_button); cancel.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { ShareTrack.this.finish(); } }); } /* * (non-Javadoc) * @see android.app.Activity#onCreateDialog(int) */ @Override protected Dialog onCreateDialog(int id) { Dialog dialog = null; Builder builder = null; switch (id) { case DIALOG_INSTALL_TWIDROID: builder = new AlertDialog.Builder(this); builder.setTitle(R.string.dialog_notwidroid).setMessage(R.string.dialog_notwidroid_message) .setIcon(android.R.drawable.ic_dialog_alert) .setPositiveButton(R.string.btn_install, mTwidroidDialogListener) .setNegativeButton(R.string.btn_cancel, null); dialog = builder.create(); return dialog; default: return super.onCreateDialog(id); } } private void setGpxExportTargets() { ArrayAdapter<CharSequence> shareTargetAdapter = ArrayAdapter.createFromResource(this, R.array.sharegpxtarget_choices, android.R.layout.simple_spinner_item); shareTargetAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mShareTargetSpinner.setAdapter(shareTargetAdapter); int lastTarget = PreferenceManager.getDefaultSharedPreferences(this).getInt(Constants.EXPORT_GPXTARGET, EXPORT_TARGET_SEND); mShareTargetSpinner.setSelection(lastTarget); } private void setKmzExportTargets() { ArrayAdapter<CharSequence> shareTargetAdapter = ArrayAdapter.createFromResource(this, R.array.sharekmztarget_choices, android.R.layout.simple_spinner_item); shareTargetAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mShareTargetSpinner.setAdapter(shareTargetAdapter); int lastTarget = PreferenceManager.getDefaultSharedPreferences(this).getInt(Constants.EXPORT_KMZTARGET, EXPORT_TARGET_SEND); mShareTargetSpinner.setSelection(lastTarget); } private void setTextLineExportTargets() { ArrayAdapter<CharSequence> shareTargetAdapter = ArrayAdapter.createFromResource(this, R.array.sharetexttarget_choices, android.R.layout.simple_spinner_item); shareTargetAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mShareTargetSpinner.setAdapter(shareTargetAdapter); int lastTarget = PreferenceManager.getDefaultSharedPreferences(this).getInt(Constants.EXPORT_TXTTARGET, EXPORT_TYPE_TWITDRIOD); mShareTargetSpinner.setSelection(lastTarget); } private void share() { String chosenFileName = mFileNameView.getText().toString(); String textLine = mTweetView.getText().toString(); int type = (int) mShareTypeSpinner.getSelectedItemId(); int target = (int) mShareTargetSpinner.getSelectedItemId(); Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); editor.putInt(Constants.EXPORT_TYPE, type); switch (type) { case EXPORT_TYPE_KMZ: editor.putInt(Constants.EXPORT_KMZTARGET, target); editor.commit(); exportKmz(chosenFileName, target); break; case EXPORT_TYPE_GPX: editor.putInt(Constants.EXPORT_GPXTARGET, target); editor.commit(); exportGpx(chosenFileName, target); break; case EXPORT_TYPE_TEXTLINE: editor.putInt(Constants.EXPORT_TXTTARGET, target); editor.commit(); exportTextLine(textLine, target); default: Log.e(TAG, "Failed to determine sharing type" + type); break; } } protected void exportKmz(String chosenFileName, int target) { EndJob endJob = null; switch (target) { case EXPORT_TARGET_SEND: endJob = new EndJob() { public void shareFile(Uri fileUri, String contentType) { sendFile(fileUri, getString(R.string.email_kmzbody), contentType); } }; break; case EXPORT_TARGET_SAVE: endJob = new EndJob() { public void shareFile(Uri fileUri, String contentType) { } }; break; default: Log.e(TAG, "Unable to determine target for sharing KMZ " + target); break; } if (endJob != null) { KmzCreator kmzCreator = new KmzCreator(this, mTrackUri, chosenFileName, new ProgressMonitor(chosenFileName, endJob)); kmzCreator.start(); ShareTrack.this.finish(); } } protected void exportGpx(String chosenFileName, int target) { boolean attachments = true; EndJob endJob = null; switch (target) { case EXPORT_TARGET_SEND: attachments = true; endJob = new EndJob() { public void shareFile(Uri fileUri, String contentType) { sendFile(fileUri, getString(R.string.email_gpxbody), contentType); } }; break; case EXPORT_TARGET_SAVE: attachments = true; endJob = new EndJob() { public void shareFile(Uri fileUri, String contentType) { } }; break; case EXPORT_TARGET_JOGRUN: attachments = false; endJob = new EndJob() { public void shareFile(Uri fileUri, String contentType) { sendToJogmap(fileUri, contentType); } }; break; case EXPORT_TARGET_OSM: attachments = false; endJob = new EndJob() { public void shareFile(Uri fileUri, String contentType) { sendToOsm(fileUri, mTrackUri, contentType); } }; break; default: Log.e(TAG, "Unable to determine target for sharing GPX " + target); break; } if (endJob != null) { GpxCreator gpxCreator = new GpxCreator(this, mTrackUri, chosenFileName, attachments, new ProgressMonitor(chosenFileName, endJob)); gpxCreator.start(); ShareTrack.this.finish(); } } protected void exportTextLine(String message, int target) { String subject = "Open GPS Tracker"; switch (target) { case EXPORT_TYPE_TWITDRIOD: sendTwidroidTweet(message); break; case EXPORT_TYPE_SMS: sendSMS(message); ShareTrack.this.finish(); break; case EXPORT_TYPE_TEXT: sentGenericText(subject, message); ShareTrack.this.finish(); break; } } private void sendTwidroidTweet(String tweet) { final Intent intent = new Intent("com.twidroid.SendTweet"); intent.putExtra("com.twidroid.extra.MESSAGE", tweet); intent.setType("application/twitter"); try { startActivity(intent); ShareTrack.this.finish(); } catch (ActivityNotFoundException e) { showDialog(DIALOG_INSTALL_TWIDROID); } } private void sendFile(Uri fileUri, String body, String contentType) { Intent sendActionIntent = new Intent(Intent.ACTION_SEND); sendActionIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.email_subject)); sendActionIntent.putExtra(Intent.EXTRA_TEXT, body); sendActionIntent.putExtra(Intent.EXTRA_STREAM, fileUri); sendActionIntent.setType(contentType); startActivity(Intent.createChooser(sendActionIntent, getString(R.string.sender_chooser))); } private void sendToJogmap(Uri fileUri, String contentType) { String authCode = PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.JOGRUNNER_AUTH, ""); File gpxFile = new File(fileUri.getEncodedPath()); HttpClient httpclient = new DefaultHttpClient(); HttpResponse response = null; URI jogmap = null; String jogmapResponseText = ""; int statusCode = 0; try { jogmap = new URI(getString(R.string.jogmap_post_url)); HttpPost method = new HttpPost(jogmap); MultipartEntity entity = new MultipartEntity(); entity.addPart("id", new StringBody(authCode)); entity.addPart("mFile", new FileBody(gpxFile)); method.setEntity(entity); response = httpclient.execute(method); statusCode = response.getStatusLine().getStatusCode(); InputStream stream = response.getEntity().getContent(); jogmapResponseText = convertStreamToString(stream); } catch (IOException e) { Log.e(TAG, "Failed to upload to " + jogmap.toString(), e); CharSequence text = getString(R.string.jogmap_failed) + e.getLocalizedMessage(); Toast toast = Toast.makeText(this, text, Toast.LENGTH_LONG); toast.show(); } catch (URISyntaxException e) { //Log.e(TAG, "Failed to use configured URI " + jogmap.toString(), e); CharSequence text = getString(R.string.jogmap_failed) + e.getLocalizedMessage(); Toast toast = Toast.makeText(this, text, Toast.LENGTH_LONG); toast.show(); } if (statusCode == 200) { CharSequence text = getString(R.string.jogmap_success) + jogmapResponseText; Toast toast = Toast.makeText(this, text, Toast.LENGTH_LONG); toast.show(); } else { Log.e(TAG, "Wrong status code " + statusCode); CharSequence text = getString(R.string.jogmap_failed) + jogmapResponseText; Toast toast = Toast.makeText(this, text, Toast.LENGTH_LONG); toast.show(); } } /** * POST a (GPX) file to the 0.6 API of the OpenStreetMap.org website publishing * this track to the public. * * @param fileUri * @param contentType */ private void sendToOsm(Uri fileUri, Uri trackUri, String contentType) { String username = PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.OSM_USERNAME, ""); String password = PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.OSM_PASSWORD, ""); String visibility = PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.OSM_VISIBILITY, "trackable"); File gpxFile = new File(fileUri.getEncodedPath()); String hostname = getString(R.string.osm_post_host); Integer port = new Integer(getString(R.string.osm_post_port)); HttpHost targetHost = new HttpHost(hostname, port, "http"); DefaultHttpClient httpclient = new DefaultHttpClient(); HttpResponse response = null; String responseText = ""; int statusCode = 0; Cursor metaData = null; String sources = null; try { metaData = this.getContentResolver().query(Uri.withAppendedPath(trackUri, "metadata"), new String[] { MetaData.VALUE }, MetaData.KEY + " = ? ", new String[] { Constants.DATASOURCES_KEY }, null); if (metaData.moveToFirst()) { sources = metaData.getString(0); } if (sources != null && sources.contains(LoggerMap.GOOGLE_PROVIDER)) { throw new IOException("Unable to upload track with materials derived from Google Maps."); } // The POST to the create node HttpPost method = new HttpPost(getString(R.string.osm_post_context)); // Preemptive basic auth on the first request method.addHeader( new BasicScheme().authenticate(new UsernamePasswordCredentials(username, password), method)); // Build the multipart body with the upload data MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("file", new FileBody(gpxFile)); entity.addPart("description", new StringBody(queryForTrackName())); entity.addPart("tags", new StringBody(queryForNotes())); entity.addPart("visibility", new StringBody(visibility)); method.setEntity(entity); // Execute the POST to OpenStreetMap response = httpclient.execute(targetHost, method); // Read the response statusCode = response.getStatusLine().getStatusCode(); InputStream stream = response.getEntity().getContent(); responseText = convertStreamToString(stream); } catch (IOException e) { Log.e(TAG, "Failed to upload to " + targetHost.getHostName() + "Response: " + responseText, e); responseText = getString(R.string.osm_failed) + e.getLocalizedMessage(); Toast toast = Toast.makeText(this, responseText, Toast.LENGTH_LONG); toast.show(); } catch (AuthenticationException e) { Log.e(TAG, "Failed to upload to " + targetHost.getHostName() + "Response: " + responseText, e); responseText = getString(R.string.osm_failed) + e.getLocalizedMessage(); Toast toast = Toast.makeText(this, responseText, Toast.LENGTH_LONG); toast.show(); } finally { if (metaData != null) { metaData.close(); } } if (statusCode == 200) { Log.i(TAG, responseText); CharSequence text = getString(R.string.osm_success) + responseText; Toast toast = Toast.makeText(this, text, Toast.LENGTH_LONG); toast.show(); } else { Log.e(TAG, "Failed to upload to error code " + statusCode + " " + responseText); CharSequence text = getString(R.string.osm_failed) + responseText; Toast toast = Toast.makeText(this, text, Toast.LENGTH_LONG); toast.show(); } } public String convertStreamToString(InputStream is) throws IOException { /* * To convert the InputStream to String we use the Reader.read(char[] buffer) method. We iterate until the Reader return -1 which means there's no more data to read. We use the StringWriter * class to produce the string. */ if (is != null) { Writer writer = new StringWriter(); char[] buffer = new char[1024]; try { Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); int n; while ((n = reader.read(buffer)) != -1) { writer.write(buffer, 0, n); } } finally { is.close(); } return writer.toString(); } else { return ""; } } private void sendSMS(String msg) { final Intent intent = new Intent(Intent.ACTION_VIEW); intent.setType("vnd.android-dir/mms-sms"); intent.putExtra("sms_body", msg); startActivity(intent); } private void sentGenericText(String subject, String msg) { final Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_SUBJECT, subject); intent.putExtra(Intent.EXTRA_TEXT, msg); startActivity(intent); } private String createTweetText() { calculator.updateCalculations(mTrackUri); String name = queryForTrackName(); String distString = calculator.getDistanceText(); String avgSpeed = calculator.getAvgSpeedText(); String duration = calculator.getDurationText(); return String.format(getString(R.string.tweettext, name, distString, avgSpeed, duration)); } private String queryForTrackName() { ContentResolver resolver = getContentResolver(); Cursor trackCursor = null; String name = null; try { trackCursor = resolver.query(mTrackUri, new String[] { Tracks.NAME }, null, null, null); if (trackCursor.moveToFirst()) { name = trackCursor.getString(0); } } finally { if (trackCursor != null) { trackCursor.close(); } } return name; } private String queryForNotes() { StringBuilder tags = new StringBuilder(); ContentResolver resolver = getContentResolver(); Cursor mediaCursor = null; Uri mediaUri = Uri.withAppendedPath(mTrackUri, "media"); try { mediaCursor = resolver.query(mediaUri, new String[] { Media.URI }, null, null, null); if (mediaCursor.moveToFirst()) { do { Uri noteUri = Uri.parse(mediaCursor.getString(0)); if (noteUri.getScheme().equals("content") && noteUri.getAuthority().equals(GPStracking.AUTHORITY + ".string")) { String tag = noteUri.getLastPathSegment().trim(); if (!tag.contains(" ")) { if (tags.length() > 0) { tags.append(" "); } tags.append(tag); } } } while (mediaCursor.moveToNext()); } } finally { if (mediaCursor != null) { mediaCursor.close(); } } return tags.toString(); } private void adjustTargetToType(int position) { switch (position) { case EXPORT_TYPE_KMZ: setKmzExportTargets(); mFileNameView.setVisibility(View.VISIBLE); mTweetView.setVisibility(View.GONE); break; case EXPORT_TYPE_GPX: setGpxExportTargets(); mFileNameView.setVisibility(View.VISIBLE); mTweetView.setVisibility(View.GONE); break; case EXPORT_TYPE_TEXTLINE: setTextLineExportTargets(); mFileNameView.setVisibility(View.GONE); mTweetView.setVisibility(View.VISIBLE); if (mTweetView.getText().toString().equals("")) { mTweetView.setText(createTweetText()); } default: break; } } public class ProgressMonitor { private String mFileName; private EndJob mEndJob; private int mGoal; private int mProgress; public ProgressMonitor(String sharename, EndJob endJob) { mFileName = sharename; mEndJob = endJob; } public void setGoal(int goal) { this.mGoal = goal; } public int getGoal() { return mGoal; } public void increaseProgress(int step) { mProgress += step; updateNotification(); } public void startNotification() { String ns = Context.NOTIFICATION_SERVICE; mNotificationManager = (NotificationManager) ShareTrack.this.getSystemService(ns); int icon = android.R.drawable.ic_menu_save; CharSequence tickerText = getString(R.string.ticker_saving) + "\"" + mFileName + "\""; mNotification = new Notification(); PendingIntent contentIntent = PendingIntent.getActivity(ShareTrack.this, 0, new Intent(ShareTrack.this, LoggerMap.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_UPDATE_CURRENT); mNotification.contentIntent = contentIntent; mNotification.tickerText = tickerText; mNotification.icon = icon; mNotification.flags |= Notification.FLAG_ONGOING_EVENT; mContentView = new RemoteViews(getPackageName(), R.layout.savenotificationprogress); mContentView.setImageViewResource(R.id.icon, icon); mContentView.setTextViewText(R.id.progresstext, tickerText); mNotification.contentView = mContentView; } private void updateNotification() { // Log.d( "TAG", "Progress " + progress + " of " + goal ); if (mProgress > 0 && mProgress < mGoal) { if ((mProgress * PROGRESS_STEPS) / mGoal != barProgress) { barProgress = (mProgress * PROGRESS_STEPS) / mGoal; mContentView.setProgressBar(R.id.progress, mGoal, mProgress, false); mNotificationManager.notify(R.layout.savenotificationprogress, mNotification); } } else if (mProgress == 0) { mContentView.setProgressBar(R.id.progress, mGoal, mProgress, true); mNotificationManager.notify(R.layout.savenotificationprogress, mNotification); } else if (mProgress >= mGoal) { mContentView.setProgressBar(R.id.progress, mGoal, mProgress, false); mNotificationManager.notify(R.layout.savenotificationprogress, mNotification); } } public void endNotification(String filepath, String contentType) { mNotificationManager.cancel(R.layout.savenotificationprogress); if (mEndJob != null && filepath != null) { Uri file = Uri.fromFile(new File(filepath)); mEndJob.shareFile(file, contentType); } } } interface EndJob { void shareFile(Uri fileUri, String contentType); } }