diff --git a/res/layout/attachment_type_selector.xml b/res/layout/attachment_type_selector.xml
index 7bbf603fb..0a72b1665 100644
--- a/res/layout/attachment_type_selector.xml
+++ b/res/layout/attachment_type_selector.xml
@@ -162,6 +162,32 @@
+
+
+
+
+
+
+
+
+
+
display(final SignalPlace place) {
+ final SettableFuture future = new SettableFuture<>();
+
+ this.mapView.onCreate(null);
+ this.mapView.onResume();
+
+ this.mapView.setVisibility(View.VISIBLE);
+ this.imageView.setVisibility(View.GONE);
+
+ this.mapView.getMapAsync(new OnMapReadyCallback() {
+ @Override
+ public void onMapReady(final GoogleMap googleMap) {
+ googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(place.getLatLong(), 13));
+ googleMap.addMarker(new MarkerOptions().position(place.getLatLong()));
+ googleMap.setBuildingsEnabled(true);
+ googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
+ googleMap.getUiSettings().setAllGesturesEnabled(false);
+ googleMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
+ @Override
+ public void onMapLoaded() {
+ googleMap.snapshot(new GoogleMap.SnapshotReadyCallback() {
+ @Override
+ public void onSnapshotReady(Bitmap bitmap) {
+ future.set(bitmap);
+ imageView.setImageBitmap(bitmap);
+ imageView.setVisibility(View.VISIBLE);
+ mapView.setVisibility(View.GONE);
+ mapView.onPause();
+ mapView.onDestroy();
+ }
+ });
+ }
+ });
+ }
+ });
+
+ this.textView.setText(place.getDescription());
+
+ return future;
+ }
+
+}
diff --git a/src/org/thoughtcrime/securesms/components/location/SignalPlace.java b/src/org/thoughtcrime/securesms/components/location/SignalPlace.java
new file mode 100644
index 000000000..3b8566cdf
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/location/SignalPlace.java
@@ -0,0 +1,81 @@
+package org.thoughtcrime.securesms.components.location;
+
+import android.net.Uri;
+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";
+ private static final String TAG = SignalPlace.class.getSimpleName();
+
+ @JsonProperty
+ private CharSequence name;
+
+ @JsonProperty
+ private CharSequence address;
+
+ @JsonProperty
+ private double latitude;
+
+ @JsonProperty
+ private double longitude;
+
+ public SignalPlace(Place place) {
+ this.name = place.getName();
+ this.address = place.getAddress();
+ this.latitude = place.getLatLng().latitude;
+ this.longitude = place.getLatLng().longitude;
+ }
+
+ public SignalPlace() {}
+
+ @JsonIgnore
+ public LatLng getLatLong() {
+ return new LatLng(latitude, longitude);
+ }
+
+ @JsonIgnore
+ public String getDescription() {
+ String description = "";
+
+ if (!TextUtils.isEmpty(name)) {
+ description += (name + "\n");
+ }
+
+ if (!TextUtils.isEmpty(address)) {
+ description += (address + "\n");
+ }
+
+ description += Uri.parse(URL)
+ .buildUpon()
+ .appendQueryParameter("q", String.format("%s,%s", latitude, longitude))
+ .build().toString();
+
+ 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/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
index 29560817c..b4b33e297 100644
--- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
+++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
@@ -49,6 +49,8 @@ import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.DocumentView;
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
import org.thoughtcrime.securesms.components.ThumbnailView;
+import org.thoughtcrime.securesms.components.location.SignalMapView;
+import org.thoughtcrime.securesms.components.location.SignalPlace;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
import org.thoughtcrime.securesms.scribbles.ScribbleActivity;
@@ -82,6 +84,7 @@ public class AttachmentManager {
private ThumbnailView thumbnail;
private AudioView audioView;
private DocumentView documentView;
+ private SignalMapView mapView;
private @NonNull List garbage = new LinkedList<>();
private @NonNull Optional slide = Optional.absent();
@@ -100,6 +103,7 @@ public class AttachmentManager {
this.thumbnail = ViewUtil.findById(root, R.id.attachment_thumbnail);
this.audioView = ViewUtil.findById(root, R.id.attachment_audio);
this.documentView = ViewUtil.findById(root, R.id.attachment_document);
+ this.mapView = ViewUtil.findById(root, R.id.attachment_location);
this.removableMediaView = ViewUtil.findById(root, R.id.removable_media_view);
removableMediaView.setRemoveClickListener(new RemoveButtonListener());
@@ -176,6 +180,34 @@ public class AttachmentManager {
this.slide = Optional.of(slide);
}
+ public ListenableFuture setLocation(@NonNull final SignalPlace place,
+ @NonNull final MediaConstraints constraints)
+ {
+ inflateStub();
+
+ SettableFuture returnResult = new SettableFuture<>();
+ ListenableFuture future = mapView.display(place);
+
+ attachmentViewStub.get().setVisibility(View.VISIBLE);
+ removableMediaView.display(mapView, false);
+
+ future.addListener(new AssertedSuccessListener() {
+ @Override
+ public void onSuccess(@NonNull Bitmap result) {
+ byte[] blob = BitmapUtil.toByteArray(result);
+ Uri uri = PersistentBlobProvider.getInstance(context)
+ .create(context, blob, MediaUtil.IMAGE_PNG, null);
+ LocationSlide locationSlide = new LocationSlide(context, uri, blob.length, place);
+
+ setSlide(locationSlide);
+ attachmentListener.onAttachmentChanged();
+ returnResult.set(true);
+ }
+ });
+
+ return returnResult;
+ }
+
@SuppressLint("StaticFieldLeak")
public ListenableFuture setMedia(@NonNull final GlideRequests glideRequests,
@NonNull final Uri uri,
@@ -341,6 +373,21 @@ public class AttachmentManager {
.execute();
}
+ public static void selectLocation(Activity activity, int requestCode) {
+ Permissions.with(activity)
+ .request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
+ .ifNecessary()
+ .withPermanentDenialDialog(activity.getString(R.string.perm_explain_access_to_location_denied))
+ .onAllGranted(() -> {
+ try {
+ activity.startActivityForResult(new PlacePicker.IntentBuilder().build(activity), requestCode);
+ } catch (GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException e) {
+ Log.w(TAG, e);
+ }
+ })
+ .execute();
+ }
+
private @Nullable Uri getSlideUri() {
return slide.isPresent() ? slide.get().getUri() : null;
}
diff --git a/src/org/thoughtcrime/securesms/mms/LocationSlide.java b/src/org/thoughtcrime/securesms/mms/LocationSlide.java
new file mode 100644
index 000000000..c07c4b3da
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mms/LocationSlide.java
@@ -0,0 +1,37 @@
+package org.thoughtcrime.securesms.mms;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+
+import org.thoughtcrime.securesms.components.location.SignalPlace;
+import org.thoughtcrime.securesms.util.guava.Optional;
+
+public class LocationSlide extends ImageSlide {
+
+ @NonNull
+ private final SignalPlace place;
+
+ public LocationSlide(@NonNull Context context, @NonNull Uri uri, long size, @NonNull SignalPlace place)
+ {
+ super(context, uri, size, 0, 0);
+ this.place = place;
+ }
+
+ @Override
+ @NonNull
+ public Optional getBody() {
+ return Optional.of(place.getDescription());
+ }
+
+ @NonNull
+ public SignalPlace getPlace() {
+ return place;
+ }
+
+ @Override
+ public boolean hasLocation() {
+ return true;
+ }
+
+}