display geo uris as location. show 'send loction' in share menu if request location intent can be resolved

iNPUTmice created

Change summary

src/main/java/eu/siacs/conversations/entities/Message.java               |   4 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |  18 
src/main/java/eu/siacs/conversations/ui/ConversationActivity.java        | 138 
src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java      |  32 
src/main/java/eu/siacs/conversations/utils/GeoHelper.java                |  71 
src/main/res/menu/attachment_choices.xml                                 |  17 
src/main/res/values/strings.xml                                          |   3 
7 files changed, 230 insertions(+), 53 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Message.java 🔗

@@ -8,6 +8,7 @@ import java.net.URL;
 import java.util.Arrays;
 
 import eu.siacs.conversations.Config;
+import eu.siacs.conversations.utils.GeoHelper;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 import eu.siacs.conversations.xmpp.jid.Jid;
 
@@ -49,6 +50,7 @@ public class Message extends AbstractEntity {
 	public static final String RELATIVE_FILE_PATH = "relativeFilePath";
 	public static final String ME_COMMAND = "/me ";
 
+
 	public boolean markable = false;
 	protected String conversationUuid;
 	protected Jid counterpart;
@@ -368,6 +370,8 @@ public class Message extends AbstractEntity {
 			 this.getCounterpart() != null &&
 			 this.getCounterpart().equals(message.getCounterpart()) &&
 			 (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) &&
+			 !GeoHelper.isGeoUri(message.getBody()) &&
+			 !GeoHelper.isGeoUri(this.body) &&
 			 !message.bodyContainsDownloadable() &&
 			 !this.bodyContainsDownloadable() &&
 			 !message.getBody().startsWith(ME_COMMAND) &&

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -306,6 +306,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		return this.mAvatarService;
 	}
 
+	public void attachLocationToConversation(final Conversation conversation,
+											 final Uri uri,
+											 final UiCallback<Message> callback) {
+		int encryption = conversation.getNextEncryption(forceEncryption());
+		if (encryption == Message.ENCRYPTION_PGP) {
+			encryption = Message.ENCRYPTION_DECRYPTED;
+		}
+		Message message = new Message(conversation,uri.toString(),encryption);
+		if (conversation.getNextCounterpart() != null) {
+			message.setCounterpart(conversation.getNextCounterpart());
+		}
+		if (encryption == Message.ENCRYPTION_DECRYPTED) {
+			getPgpEngine().encrypt(message,callback);
+		} else {
+			callback.success(message);
+		}
+	}
+
 	public void attachFileToConversation(final Conversation conversation,
 			final Uri uri,
 			final UiCallback<Message> callback) {

src/main/java/eu/siacs/conversations/ui/ConversationActivity.java 🔗

@@ -33,7 +33,6 @@ import java.util.ArrayList;
 import java.util.List;
 
 import eu.siacs.conversations.R;
-import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Blockable;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
@@ -63,6 +62,7 @@ public class ConversationActivity extends XmppActivity
 	private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
 	private static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303;
 	private static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304;
+	private static final int ATTACHMENT_CHOICE_LOCATION = 0x0305;
 	private static final String STATE_OPEN_CONVERSATION = "state_open_conversation";
 	private static final String STATE_PANEL_OPEN = "state_panel_open";
 	private static final String STATE_PENDING_URI = "state_pending_uri";
@@ -71,6 +71,7 @@ public class ConversationActivity extends XmppActivity
 	private boolean mPanelOpen = true;
 	private Uri mPendingImageUri = null;
 	private Uri mPendingFileUri = null;
+	private Uri mPendingGeoUri = null;
 
 	private View mContentView;
 
@@ -313,7 +314,6 @@ public class ConversationActivity extends XmppActivity
 					menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite());
 				} else {
 					menuMucDetails.setVisible(false);
-					final Account account = this.getSelectedConversation().getAccount();
 				}
 				if (this.getSelectedConversation().isMuted()) {
 					menuMute.setVisible(false);
@@ -325,50 +325,60 @@ public class ConversationActivity extends XmppActivity
 		return true;
 	}
 
-	private void selectPresenceToAttachFile(final int attachmentChoice) {
-		selectPresence(getSelectedConversation(), new OnPresenceSelected() {
+	private void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) {
+		if (attachmentChoice == ATTACHMENT_CHOICE_LOCATION && encryption != Message.ENCRYPTION_OTR) {
+			getSelectedConversation().setNextCounterpart(null);
+			Intent intent = new Intent("eu.siacs.conversations.location.request");
+			startActivityForResult(intent,attachmentChoice);
+		} else {
+			selectPresence(getSelectedConversation(), new OnPresenceSelected() {
 
-			@Override
-			public void onPresenceSelected() {
-				Intent intent = new Intent();
-				boolean chooser = false;
-				switch (attachmentChoice) {
-					case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
-						intent.setAction(Intent.ACTION_GET_CONTENT);
-						intent.setType("image/*");
-						chooser = true;
-						break;
-					case ATTACHMENT_CHOICE_TAKE_PHOTO:
-						mPendingImageUri = xmppConnectionService.getFileBackend().getTakePhotoUri();
-						intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
-						intent.putExtra(MediaStore.EXTRA_OUTPUT,mPendingImageUri);
-						break;
-					case ATTACHMENT_CHOICE_CHOOSE_FILE:
-						chooser = true;
-						intent.setType("*/*");
-						intent.addCategory(Intent.CATEGORY_OPENABLE);
-						intent.setAction(Intent.ACTION_GET_CONTENT);
-						break;
-					case ATTACHMENT_CHOICE_RECORD_VOICE:
-						intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
-						break;
-				}
-				if (intent.resolveActivity(getPackageManager()) != null) {
-					if (chooser) {
-						startActivityForResult(
-								Intent.createChooser(intent,getString(R.string.perform_action_with)),
-								attachmentChoice);
-					} else {
-						startActivityForResult(intent, attachmentChoice);
+				@Override
+				public void onPresenceSelected() {
+					Intent intent = new Intent();
+					boolean chooser = false;
+					switch (attachmentChoice) {
+						case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
+							intent.setAction(Intent.ACTION_GET_CONTENT);
+							intent.setType("image/*");
+							chooser = true;
+							break;
+						case ATTACHMENT_CHOICE_TAKE_PHOTO:
+							mPendingImageUri = xmppConnectionService.getFileBackend().getTakePhotoUri();
+							intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
+							intent.putExtra(MediaStore.EXTRA_OUTPUT, mPendingImageUri);
+							break;
+						case ATTACHMENT_CHOICE_CHOOSE_FILE:
+							chooser = true;
+							intent.setType("*/*");
+							intent.addCategory(Intent.CATEGORY_OPENABLE);
+							intent.setAction(Intent.ACTION_GET_CONTENT);
+							break;
+						case ATTACHMENT_CHOICE_RECORD_VOICE:
+							intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
+							break;
+						case ATTACHMENT_CHOICE_LOCATION:
+							intent.setAction("eu.siacs.conversations.location.request");
+							break;
+					}
+					if (intent.resolveActivity(getPackageManager()) != null) {
+						if (chooser) {
+							startActivityForResult(
+									Intent.createChooser(intent, getString(R.string.perform_action_with)),
+									attachmentChoice);
+						} else {
+							startActivityForResult(intent, attachmentChoice);
+						}
 					}
 				}
-			}
-		});
+			});
+		}
 	}
 
 	private void attachFile(final int attachmentChoice) {
 		final Conversation conversation = getSelectedConversation();
-		if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
+		final int encryption = conversation.getNextEncryption(forceEncryption());
+		if (encryption == Message.ENCRYPTION_PGP) {
 			if (hasPgp()) {
 				if (conversation.getContact().getPgpKeyId() != 0) {
 					xmppConnectionService.getPgpEngine().hasKey(
@@ -378,13 +388,12 @@ public class ConversationActivity extends XmppActivity
 								@Override
 								public void userInputRequried(PendingIntent pi,
 										Contact contact) {
-									ConversationActivity.this.runIntent(pi,
-											attachmentChoice);
+									ConversationActivity.this.runIntent(pi,attachmentChoice);
 								}
 
 								@Override
 								public void success(Contact contact) {
-									selectPresenceToAttachFile(attachmentChoice);
+									selectPresenceToAttachFile(attachmentChoice,encryption);
 								}
 
 								@Override
@@ -406,7 +415,7 @@ public class ConversationActivity extends XmppActivity
 											.setNextEncryption(Message.ENCRYPTION_NONE);
 										xmppConnectionService.databaseBackend
 											.updateConversation(conversation);
-										selectPresenceToAttachFile(attachmentChoice);
+										selectPresenceToAttachFile(attachmentChoice,Message.ENCRYPTION_NONE);
 									}
 								});
 					}
@@ -414,11 +423,8 @@ public class ConversationActivity extends XmppActivity
 			} else {
 				showInstallPgpDialog();
 			}
-		} else if (getSelectedConversation().getNextEncryption(
-					forceEncryption()) == Message.ENCRYPTION_NONE) {
-			selectPresenceToAttachFile(attachmentChoice);
 		} else {
-			selectPresenceToAttachFile(attachmentChoice);
+			selectPresenceToAttachFile(attachmentChoice,encryption);
 		}
 	}
 
@@ -526,6 +532,9 @@ public class ConversationActivity extends XmppActivity
 		if (new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(getPackageManager()) == null) {
 			attachFilePopup.getMenu().findItem(R.id.attach_record_voice).setVisible(false);
 		}
+		if (new Intent("eu.siacs.conversations.location.request").resolveActivity(getPackageManager()) == null) {
+			attachFilePopup.getMenu().findItem(R.id.attach_location).setVisible(false);
+		}
 		attachFilePopup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
 
 			@Override
@@ -543,6 +552,9 @@ public class ConversationActivity extends XmppActivity
 					case R.id.attach_record_voice:
 						attachFile(ATTACHMENT_CHOICE_RECORD_VOICE);
 						break;
+					case R.id.attach_location:
+						attachFile(ATTACHMENT_CHOICE_LOCATION);
+						break;
 				}
 				return false;
 			}
@@ -809,6 +821,7 @@ public class ConversationActivity extends XmppActivity
 			showConversationsOverview();
 			mPendingImageUri = null;
 			mPendingFileUri = null;
+			mPendingGeoUri = null;
 			setSelectedConversation(conversationList.get(0));
 			this.mConversationFragment.reInit(getSelectedConversation());
 		}
@@ -819,6 +832,9 @@ public class ConversationActivity extends XmppActivity
 		} else if (mPendingFileUri != null) {
 			attachFileToConversation(getSelectedConversation(),mPendingFileUri);
 			mPendingFileUri = null;
+		} else if (mPendingGeoUri != null) {
+			attachLocationToConversation(getSelectedConversation(),mPendingGeoUri);
+			mPendingGeoUri = null;
 		}
 		ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
 		setIntent(new Intent());
@@ -897,6 +913,14 @@ public class ConversationActivity extends XmppActivity
 				Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
 				intent.setData(mPendingImageUri);
 				sendBroadcast(intent);
+			} else if (requestCode == ATTACHMENT_CHOICE_LOCATION) {
+				double latitude = data.getDoubleExtra("latitude",0);
+				double longitude = data.getDoubleExtra("longitude",0);
+				this.mPendingGeoUri = Uri.parse("geo:"+String.valueOf(latitude)+","+String.valueOf(longitude));
+				if (xmppConnectionServiceBound) {
+					attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
+					this.mPendingGeoUri = null;
+				}
 			}
 		} else {
 			if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
@@ -905,6 +929,26 @@ public class ConversationActivity extends XmppActivity
 		}
 	}
 
+	private void attachLocationToConversation(Conversation conversation, Uri uri) {
+		xmppConnectionService.attachLocationToConversation(conversation,uri, new UiCallback<Message>() {
+
+			@Override
+			public void success(Message message) {
+				xmppConnectionService.sendMessage(message);
+			}
+
+			@Override
+			public void error(int errorCode, Message object) {
+
+			}
+
+			@Override
+			public void userInputRequried(PendingIntent pi, Message object) {
+
+			}
+		});
+	}
+
 	private void attachFileToConversation(Conversation conversation, Uri uri) {
 		prepareFileToast = Toast.makeText(getApplicationContext(),
 				getText(R.string.preparing_file), Toast.LENGTH_LONG);

src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java 🔗

@@ -34,6 +34,7 @@ import eu.siacs.conversations.entities.DownloadableFile;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.Message.ImageParams;
 import eu.siacs.conversations.ui.ConversationActivity;
+import eu.siacs.conversations.utils.GeoHelper;
 import eu.siacs.conversations.utils.UIHelper;
 
 public class MessageAdapter extends ArrayAdapter<Message> {
@@ -299,6 +300,21 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 		viewHolder.download_button.setOnLongClickListener(openContextMenu);
 	}
 
+	private void displayLocationMessage(ViewHolder viewHolder, final Message message) {
+		viewHolder.image.setVisibility(View.GONE);
+		viewHolder.messageBody.setVisibility(View.GONE);
+		viewHolder.download_button.setVisibility(View.VISIBLE);
+		viewHolder.download_button.setText(R.string.show_location);
+		viewHolder.download_button.setOnClickListener(new OnClickListener() {
+
+			@Override
+			public void onClick(View v) {
+				showLocation(message);
+			}
+		});
+		viewHolder.download_button.setOnLongClickListener(openContextMenu);
+	}
+
 	private void displayImageMessage(ViewHolder viewHolder,
 			final Message message) {
 		if (viewHolder.download_button != null) {
@@ -509,7 +525,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 		} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
 			displayDecryptionFailed(viewHolder);
 		} else {
-			displayTextMessage(viewHolder, message);
+			if (GeoHelper.isGeoUri(message.getBody())) {
+				displayLocationMessage(viewHolder,message);
+			} else {
+				displayTextMessage(viewHolder, message);
+			}
 		}
 
 		displayStatus(viewHolder, message);
@@ -544,6 +564,16 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 		}
 	}
 
+	public void showLocation(Message message) {
+		for(Intent intent : GeoHelper.createGeoIntentsFromMessage(message)) {
+			if (intent.resolveActivity(getContext().getPackageManager()) != null) {
+				getContext().startActivity(intent);
+				return;
+			}
+		}
+		Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show();
+	}
+
 	public interface OnContactPictureClicked {
 		public void onContactPictureClicked(Message message);
 	}

src/main/java/eu/siacs/conversations/utils/GeoHelper.java 🔗

@@ -0,0 +1,71 @@
+package eu.siacs.conversations.utils;
+
+import android.content.Intent;
+import android.net.Uri;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+
+public class GeoHelper {
+	private static Pattern GEO_URI = Pattern.compile("geo:([\\-0-9.]+),([\\-0-9.]+)(?:,([\\-0-9.]+))?(?:\\?(.*))?", Pattern.CASE_INSENSITIVE);
+
+	public static boolean isGeoUri(String body) {
+		return body != null && GEO_URI.matcher(body).matches();
+	}
+
+	public static ArrayList<Intent> createGeoIntentsFromMessage(Message message) {
+		final ArrayList<Intent> intents = new ArrayList();
+		Matcher matcher = GEO_URI.matcher(message.getBody());
+		if (!matcher.matches()) {
+			return intents;
+		}
+		double latitude;
+		double longitude;
+		try {
+			latitude = Double.parseDouble(matcher.group(1));
+			if (latitude > 90.0 || latitude < -90.0) {
+				return intents;
+			}
+			longitude = Double.parseDouble(matcher.group(2));
+			if (longitude > 180.0 || longitude < -180.0) {
+				return intents;
+			}
+		} catch (NumberFormatException nfe) {
+			return intents;
+		}
+		final Conversation conversation = message.getConversation();
+		String label;
+		if (conversation.getMode() == Conversation.MODE_SINGLE && message.getStatus() == Message.STATUS_RECEIVED) {
+			try {
+				label = "(" + URLEncoder.encode(message.getConversation().getName(), "UTF-8") + ")";
+			} catch (UnsupportedEncodingException e) {
+				label = "";
+			}
+		} else {
+			label = "";
+		}
+
+		Intent locationPluginIntent = new Intent("eu.siacs.conversations.location.show");
+		locationPluginIntent.putExtra("latitude",latitude);
+		locationPluginIntent.putExtra("longitude",longitude);
+		if (conversation.getMode() == Conversation.MODE_SINGLE && message.getStatus() == Message.STATUS_RECEIVED) {
+			locationPluginIntent.putExtra("name",conversation.getName());
+		}
+		intents.add(locationPluginIntent);
+
+		Intent geoIntent = new Intent(Intent.ACTION_VIEW);
+		geoIntent.setData(Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude) + "?q=" + String.valueOf(latitude) + "," + String.valueOf(longitude) + label));
+		intents.add(geoIntent);
+
+		Intent httpIntent = new Intent(Intent.ACTION_VIEW);
+		httpIntent.setData(Uri.parse("https://maps.google.com/maps?q=loc:"+String.valueOf(latitude) + "," + String.valueOf(longitude) +label));
+		intents.add(httpIntent);
+		return intents;
+	}
+}

src/main/res/menu/attachment_choices.xml 🔗

@@ -2,14 +2,21 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android" >
 
     <item
-        android:id="@+id/attach_choose_picture"
-        android:title="@string/attach_choose_picture"/>
+        android:id="@+id/attach_location"
+        android:title="@string/send_location"/>
+
+    <item
+        android:id="@+id/attach_record_voice"
+        android:title="@string/attach_record_voice"/>
+
     <item
         android:id="@+id/attach_take_picture"
         android:title="@string/attach_take_picture"/>
-	<item
-		android:id="@+id/attach_record_voice"
-		android:title="@string/attach_record_voice"/>
+
+    <item
+        android:id="@+id/attach_choose_picture"
+        android:title="@string/attach_choose_picture"/>
+
     <item
         android:id="@+id/attach_choose_file"
         android:title="@string/choose_file"/>

src/main/res/values/strings.xml 🔗

@@ -449,4 +449,7 @@
     <string name="contact_has_stopped_typing">%s has stopped typing</string>
     <string name="pref_chat_states">Typing notifications</string>
     <string name="pref_chat_states_summary">Let your contact know when you are writing a new message</string>
+    <string name="send_location">Send location</string>
+    <string name="show_location">Show location</string>
+    <string name="no_application_found_to_display_location">No application found to display location</string>
 </resources>