From 759f9d80169bf8d6de7d973366e4bbc5cc890900 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 4 Jan 2016 13:02:22 -0800 Subject: [PATCH] Support for location drafts Fixes #4979 // FREEBIE --- res/values/strings.xml | 1 + .../securesms/ConversationActivity.java | 35 +++++++----- .../components/location/SignalPlace.java | 56 ++++++++++++++++--- .../securesms/crypto/MasterCipher.java | 4 +- .../securesms/database/DraftDatabase.java | 20 ++++--- .../securesms/mms/AttachmentManager.java | 10 ++-- .../securesms/mms/LocationSlide.java | 20 +++++-- src/org/thoughtcrime/securesms/mms/Slide.java | 4 ++ 8 files changed, 108 insertions(+), 42 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index e76234b5a8..73c7736889 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -51,6 +51,7 @@ (image) (audio) (video) + (location) Can\'t find an app to select media. diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 8cddd77e63..60eb729299 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -56,7 +56,6 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; -import com.google.android.gms.location.places.Place; import com.google.android.gms.location.places.ui.PlacePicker; import com.google.protobuf.ByteString; @@ -78,6 +77,7 @@ import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer; import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer.AttachmentDrawerListener; import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer.DrawerState; import org.thoughtcrime.securesms.components.emoji.EmojiDrawer; +import org.thoughtcrime.securesms.components.location.SignalPlace; import org.thoughtcrime.securesms.components.reminder.InviteReminder; import org.thoughtcrime.securesms.components.reminder.ReminderView; import org.thoughtcrime.securesms.contacts.ContactAccessor; @@ -97,6 +97,7 @@ import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType; import org.thoughtcrime.securesms.mms.AttachmentTypeSelectorAdapter; import org.thoughtcrime.securesms.mms.AudioSlide; +import org.thoughtcrime.securesms.mms.LocationSlide; import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; @@ -353,7 +354,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity fragment.reloadList(); break; case PICK_LOCATION: - attachmentManager.setLocation(masterSecret, PlacePicker.getPlace(data, this), getCurrentMediaConstraints()); + SignalPlace place = new SignalPlace(PlacePicker.getPlace(data, this)); + attachmentManager.setLocation(masterSecret, place, getCurrentMediaConstraints()); break; } } @@ -748,14 +750,20 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected void onPostExecute(List drafts) { for (Draft draft : drafts) { - if (draft.getType().equals(Draft.TEXT)) { - composeText.setText(draft.getValue()); - } else if (draft.getType().equals(Draft.IMAGE)) { - setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE); - } else if (draft.getType().equals(Draft.AUDIO)) { - setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO); - } else if (draft.getType().equals(Draft.VIDEO)) { - setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO); + try { + if (draft.getType().equals(Draft.TEXT)) { + composeText.setText(draft.getValue()); + } else if (draft.getType().equals(Draft.LOCATION)) { + attachmentManager.setLocation(masterSecret, SignalPlace.deserialize(draft.getValue()), getCurrentMediaConstraints()); + } else if (draft.getType().equals(Draft.IMAGE)) { + setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE); + } else if (draft.getType().equals(Draft.AUDIO)) { + setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO); + } else if (draft.getType().equals(Draft.VIDEO)) { + setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO); + } + } catch (IOException e) { + Log.w(TAG, e); } } @@ -1053,9 +1061,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } for (Slide slide : attachmentManager.buildSlideDeck().getSlides()) { - if (slide.hasAudio()) drafts.add(new Draft(Draft.AUDIO, slide.getUri().toString())); - else if (slide.hasVideo()) drafts.add(new Draft(Draft.VIDEO, slide.getUri().toString())); - else if (slide.hasImage()) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString())); + if (slide.hasAudio()) drafts.add(new Draft(Draft.AUDIO, slide.getUri().toString())); + else if (slide.hasVideo()) drafts.add(new Draft(Draft.VIDEO, slide.getUri().toString())); + else if (slide.hasLocation()) drafts.add(new Draft(Draft.LOCATION, ((LocationSlide)slide).getPlace().serialize())); + else if (slide.hasImage()) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString())); } return drafts; diff --git a/src/org/thoughtcrime/securesms/components/location/SignalPlace.java b/src/org/thoughtcrime/securesms/components/location/SignalPlace.java index c83a3c8bd7..64dcc48702 100644 --- a/src/org/thoughtcrime/securesms/components/location/SignalPlace.java +++ b/src/org/thoughtcrime/securesms/components/location/SignalPlace.java @@ -1,37 +1,77 @@ package org.thoughtcrime.securesms.components.location; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextUtils; +import android.util.Log; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import com.google.android.gms.location.places.Place; import com.google.android.gms.maps.model.LatLng; +import org.thoughtcrime.securesms.util.JsonUtils; + +import java.io.IOException; + public class SignalPlace { private static final String URL = "https://maps.google.com/maps?q=%s,%s"; + private static final String TAG = SignalPlace.class.getSimpleName(); - private final Place place; + @JsonProperty + private String name; + + @JsonProperty + private String address; + + @JsonProperty + private double latitude; + + @JsonProperty + private double longitude; public SignalPlace(Place place) { - this.place = place; + this.name = place.getName().toString(); + this.address = place.getAddress().toString(); + this.latitude = place.getLatLng().latitude; + this.longitude = place.getLatLng().longitude; } + public SignalPlace() {} + + @JsonIgnore public LatLng getLatLong() { - return place.getLatLng(); + return new LatLng(latitude, longitude); } + @JsonIgnore public String getDescription() { String description = ""; - if (!TextUtils.isEmpty(place.getName())) { - description += (place.getName() + "\n"); + if (!TextUtils.isEmpty(name)) { + description += (name + "\n"); } - if (!TextUtils.isEmpty(place.getAddress())) { - description += (place.getAddress() + "\n"); + if (!TextUtils.isEmpty(address)) { + description += (address + "\n"); } - description += String.format(URL, place.getLatLng().latitude, place.getLatLng().longitude); + description += String.format(URL, latitude, longitude); return description; } + + public @Nullable String serialize() { + try { + return JsonUtils.toJson(this); + } catch (IOException e) { + Log.w(TAG, e); + return null; + } + } + + public static SignalPlace deserialize(@NonNull String serialized) throws IOException { + return JsonUtils.fromJson(serialized, SignalPlace.class); + } } diff --git a/src/org/thoughtcrime/securesms/crypto/MasterCipher.java b/src/org/thoughtcrime/securesms/crypto/MasterCipher.java index cdc6263c8a..969320391c 100644 --- a/src/org/thoughtcrime/securesms/crypto/MasterCipher.java +++ b/src/org/thoughtcrime/securesms/crypto/MasterCipher.java @@ -75,7 +75,7 @@ public class MasterCipher { return encryptBytes(privateKey.serialize()); } - public String encryptBody(String body) { + public String encryptBody(@NonNull String body) { return encryptAndEncodeBytes(body.getBytes()); } @@ -149,7 +149,7 @@ public class MasterCipher { } } - private String encryptAndEncodeBytes(byte[] bytes) { + private String encryptAndEncodeBytes(@NonNull byte[] bytes) { byte[] encryptedAndMacBody = encryptBytes(bytes); return Base64.encodeBytes(encryptedAndMacBody); } diff --git a/src/org/thoughtcrime/securesms/database/DraftDatabase.java b/src/org/thoughtcrime/securesms/database/DraftDatabase.java index b9a5ceab8d..30470de9ba 100644 --- a/src/org/thoughtcrime/securesms/database/DraftDatabase.java +++ b/src/org/thoughtcrime/securesms/database/DraftDatabase.java @@ -103,10 +103,11 @@ public class DraftDatabase extends Database { } public static class Draft { - public static final String TEXT = "text"; - public static final String IMAGE = "image"; - public static final String VIDEO = "video"; - public static final String AUDIO = "audio"; + public static final String TEXT = "text"; + public static final String IMAGE = "image"; + public static final String VIDEO = "video"; + public static final String AUDIO = "audio"; + public static final String LOCATION = "location"; private final String type; private final String value; @@ -126,11 +127,12 @@ public class DraftDatabase extends Database { public String getSnippet(Context context) { switch (type) { - case TEXT: return value; - case IMAGE: return context.getString(R.string.DraftDatabase_Draft_image_snippet); - case VIDEO: return context.getString(R.string.DraftDatabase_Draft_video_snippet); - case AUDIO: return context.getString(R.string.DraftDatabase_Draft_audio_snippet); - default: return null; + case TEXT: return value; + case IMAGE: return context.getString(R.string.DraftDatabase_Draft_image_snippet); + case VIDEO: return context.getString(R.string.DraftDatabase_Draft_video_snippet); + case AUDIO: return context.getString(R.string.DraftDatabase_Draft_audio_snippet); + case LOCATION: return context.getString(R.string.DraftDatabase_Draft_location_snippet); + default: return null; } } } diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index 55a89a3a34..86f3df9e10 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -35,15 +35,14 @@ import android.widget.Toast; import com.google.android.gms.common.GooglePlayServicesNotAvailableException; import com.google.android.gms.common.GooglePlayServicesRepairableException; -import com.google.android.gms.location.places.Place; import com.google.android.gms.location.places.ui.PlacePicker; import org.thoughtcrime.securesms.MediaPreviewActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.AudioView; import org.thoughtcrime.securesms.components.RemovableMediaView; -import org.thoughtcrime.securesms.components.location.SignalMapView; import org.thoughtcrime.securesms.components.ThumbnailView; +import org.thoughtcrime.securesms.components.location.SignalMapView; import org.thoughtcrime.securesms.components.location.SignalPlace; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; @@ -148,11 +147,10 @@ public class AttachmentManager { } public void setLocation(@NonNull final MasterSecret masterSecret, - @NonNull final Place place, + @NonNull final SignalPlace place, @NonNull final MediaConstraints constraints) { - final SignalPlace signalPlace = new SignalPlace(place); - ListenableFuture future = mapView.display(signalPlace); + ListenableFuture future = mapView.display(place); attachmentView.setVisibility(View.VISIBLE); removableMediaView.display(mapView); @@ -162,7 +160,7 @@ public class AttachmentManager { public void onSuccess(@NonNull Bitmap result) { byte[] blob = BitmapUtil.toByteArray(result); Uri uri = PersistentBlobProvider.getInstance(context).create(masterSecret, blob); - LocationSlide locationSlide = new LocationSlide(context, uri, blob.length, signalPlace.getDescription()); + LocationSlide locationSlide = new LocationSlide(context, uri, blob.length, place); setSlide(locationSlide); attachmentListener.onAttachmentChanged(); diff --git a/src/org/thoughtcrime/securesms/mms/LocationSlide.java b/src/org/thoughtcrime/securesms/mms/LocationSlide.java index fc6b113f02..e9e6b9a14e 100644 --- a/src/org/thoughtcrime/securesms/mms/LocationSlide.java +++ b/src/org/thoughtcrime/securesms/mms/LocationSlide.java @@ -4,22 +4,34 @@ import android.content.Context; import android.net.Uri; import android.support.annotation.NonNull; +import org.thoughtcrime.securesms.components.location.SignalPlace; import org.whispersystems.libaxolotl.util.guava.Optional; public class LocationSlide extends ImageSlide { @NonNull - private final String description; + private final SignalPlace place; - public LocationSlide(@NonNull Context context, @NonNull Uri uri, long size, @NonNull String description) + public LocationSlide(@NonNull Context context, @NonNull Uri uri, long size, @NonNull SignalPlace place) { super(context, uri, size); - this.description = description; + this.place = place; } @Override @NonNull public Optional getBody() { - return Optional.of(description); + return Optional.of(place.getDescription()); } + + @NonNull + public SignalPlace getPlace() { + return place; + } + + @Override + public boolean hasLocation() { + return true; + } + } diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java index c0bf65105d..f676ac3a6f 100644 --- a/src/org/thoughtcrime/securesms/mms/Slide.java +++ b/src/org/thoughtcrime/securesms/mms/Slide.java @@ -72,6 +72,10 @@ public abstract class Slide { return false; } + public boolean hasLocation() { + return false; + } + public @NonNull String getContentDescription() { return ""; } public Attachment asAttachment() {