Support for location drafts

Fixes #4979
// FREEBIE
master
Moxie Marlinspike 2016-01-04 13:02:22 -08:00
parent 83ec4e0627
commit 759f9d8016
8 changed files with 108 additions and 42 deletions

View File

@ -51,6 +51,7 @@
<string name="DraftDatabase_Draft_image_snippet">(image)</string>
<string name="DraftDatabase_Draft_audio_snippet">(audio)</string>
<string name="DraftDatabase_Draft_video_snippet">(video)</string>
<string name="DraftDatabase_Draft_location_snippet">(location)</string>
<!-- AttchmentManager -->
<string name="AttachmentManager_cant_open_media_selection">Can\'t find an app to select media.</string>

View File

@ -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<Draft> 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;

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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<Bitmap> future = mapView.display(signalPlace);
ListenableFuture<Bitmap> 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();

View File

@ -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<String> getBody() {
return Optional.of(description);
return Optional.of(place.getDescription());
}
@NonNull
public SignalPlace getPlace() {
return place;
}
@Override
public boolean hasLocation() {
return true;
}
}

View File

@ -72,6 +72,10 @@ public abstract class Slide {
return false;
}
public boolean hasLocation() {
return false;
}
public @NonNull String getContentDescription() { return ""; }
public Attachment asAttachment() {