mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-25 23:38:56 +00:00
Support for location messages
Start with encoding as a simple image thumbnail for compatibility with MMS and iOS // FREEBIE
This commit is contained in:
@@ -56,6 +56,8 @@ 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;
|
||||
|
||||
import org.thoughtcrime.redphone.RedPhone;
|
||||
@@ -174,6 +176,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private static final int GROUP_EDIT = 5;
|
||||
private static final int TAKE_PHOTO = 6;
|
||||
private static final int ADD_CONTACT = 7;
|
||||
private static final int PICK_LOCATION = 8;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
protected ComposeText composeText;
|
||||
@@ -312,7 +315,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int reqCode, int resultCode, Intent data) {
|
||||
public void onActivityResult(final int reqCode, int resultCode, Intent data) {
|
||||
Log.w(TAG, "onActivityResult called: " + reqCode + ", " + resultCode + " , " + data);
|
||||
super.onActivityResult(reqCode, resultCode, data);
|
||||
|
||||
@@ -349,6 +352,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
recipients.addListener(this);
|
||||
fragment.reloadList();
|
||||
break;
|
||||
case PICK_LOCATION:
|
||||
attachmentManager.setLocation(masterSecret, PlacePicker.getPlace(data, this), getCurrentMediaConstraints());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,6 +1004,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
AttachmentManager.selectAudio(this, PICK_AUDIO); break;
|
||||
case AttachmentTypeSelectorAdapter.ADD_CONTACT_INFO:
|
||||
AttachmentManager.selectContactInfo(this, PICK_CONTACT_INFO); break;
|
||||
case AttachmentTypeSelector.ADD_LOCATION:
|
||||
AttachmentManager.selectLocation(this, PICK_LOCATION); break;
|
||||
case AttachmentTypeSelectorAdapter.TAKE_PHOTO:
|
||||
attachmentManager.capturePhoto(this, TAKE_PHOTO); break;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ public class AttachmentTypeSelector extends PopupWindow {
|
||||
public static final int ADD_SOUND = 3;
|
||||
public static final int ADD_CONTACT_INFO = 4;
|
||||
public static final int TAKE_PHOTO = 5;
|
||||
public static final int ADD_LOCATION = 6;
|
||||
|
||||
private static final int ANIMATION_DURATION = 300;
|
||||
|
||||
@@ -43,6 +44,7 @@ public class AttachmentTypeSelector extends PopupWindow {
|
||||
private final @NonNull ImageView videoButton;
|
||||
private final @NonNull ImageView contactButton;
|
||||
private final @NonNull ImageView cameraButton;
|
||||
private final @NonNull ImageView locationButton;
|
||||
private final @NonNull ImageView closeButton;
|
||||
|
||||
private @Nullable View currentAnchor;
|
||||
@@ -54,21 +56,27 @@ public class AttachmentTypeSelector extends PopupWindow {
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.attachment_type_selector, null, true);
|
||||
|
||||
this.listener = listener;
|
||||
this.imageButton = ViewUtil.findById(layout, R.id.gallery_button);
|
||||
this.audioButton = ViewUtil.findById(layout, R.id.audio_button);
|
||||
this.videoButton = ViewUtil.findById(layout, R.id.video_button);
|
||||
this.contactButton = ViewUtil.findById(layout, R.id.contact_button);
|
||||
this.cameraButton = ViewUtil.findById(layout, R.id.camera_button);
|
||||
this.closeButton = ViewUtil.findById(layout, R.id.close_button);
|
||||
this.listener = listener;
|
||||
this.imageButton = ViewUtil.findById(layout, R.id.gallery_button);
|
||||
this.audioButton = ViewUtil.findById(layout, R.id.audio_button);
|
||||
this.videoButton = ViewUtil.findById(layout, R.id.video_button);
|
||||
this.contactButton = ViewUtil.findById(layout, R.id.contact_button);
|
||||
this.cameraButton = ViewUtil.findById(layout, R.id.camera_button);
|
||||
this.closeButton = ViewUtil.findById(layout, R.id.close_button);
|
||||
this.locationButton = ViewUtil.findById(layout, R.id.location_button);
|
||||
|
||||
this.imageButton.setOnClickListener(new PropagatingClickListener(ADD_IMAGE));
|
||||
this.audioButton.setOnClickListener(new PropagatingClickListener(ADD_SOUND));
|
||||
this.videoButton.setOnClickListener(new PropagatingClickListener(ADD_VIDEO));
|
||||
this.contactButton.setOnClickListener(new PropagatingClickListener(ADD_CONTACT_INFO));
|
||||
this.cameraButton.setOnClickListener(new PropagatingClickListener(TAKE_PHOTO));
|
||||
this.locationButton.setOnClickListener(new PropagatingClickListener(ADD_LOCATION));
|
||||
this.closeButton.setOnClickListener(new CloseClickListener());
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
||||
this.locationButton.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
setContentView(layout);
|
||||
setWidth(LinearLayout.LayoutParams.MATCH_PARENT);
|
||||
setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package org.thoughtcrime.securesms.components.location;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.gms.location.places.Place;
|
||||
import com.google.android.gms.maps.CameraUpdateFactory;
|
||||
import com.google.android.gms.maps.GoogleMap;
|
||||
import com.google.android.gms.maps.MapView;
|
||||
import com.google.android.gms.maps.OnMapReadyCallback;
|
||||
import com.google.android.gms.maps.model.MarkerOptions;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
|
||||
public class SignalMapView extends LinearLayout {
|
||||
|
||||
private MapView mapView;
|
||||
private ImageView imageView;
|
||||
private TextView textView;
|
||||
|
||||
public SignalMapView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SignalMapView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public SignalMapView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initialize(context);
|
||||
}
|
||||
|
||||
private void initialize(Context context) {
|
||||
setOrientation(LinearLayout.VERTICAL);
|
||||
LayoutInflater.from(context).inflate(R.layout.signal_map_view, this, true);
|
||||
|
||||
this.mapView = ViewUtil.findById(this, R.id.map_view);
|
||||
this.imageView = ViewUtil.findById(this, R.id.image_view);
|
||||
this.textView = ViewUtil.findById(this, R.id.address_view);
|
||||
}
|
||||
|
||||
public ListenableFuture<Bitmap> display(final SignalPlace place) {
|
||||
final SettableFuture<Bitmap> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.thoughtcrime.securesms.components.location;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.android.gms.location.places.Place;
|
||||
import com.google.android.gms.maps.model.LatLng;
|
||||
|
||||
public class SignalPlace {
|
||||
|
||||
private static final String URL = "https://maps.google.com/maps?q=%s,%s";
|
||||
|
||||
private final Place place;
|
||||
|
||||
public SignalPlace(Place place) {
|
||||
this.place = place;
|
||||
}
|
||||
|
||||
public LatLng getLatLong() {
|
||||
return place.getLatLng();
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
String description = "";
|
||||
|
||||
if (!TextUtils.isEmpty(place.getName())) {
|
||||
description += (place.getName() + "\n");
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(place.getAddress())) {
|
||||
description += (place.getAddress() + "\n");
|
||||
}
|
||||
|
||||
description += String.format(URL, place.getLatLng().latitude, place.getLatLng().longitude);
|
||||
|
||||
return description;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
@@ -32,15 +33,25 @@ import android.util.Log;
|
||||
import android.view.View;
|
||||
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.SignalPlace;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
|
||||
@@ -61,6 +72,7 @@ public class AttachmentManager {
|
||||
private final @NonNull RemovableMediaView removableMediaView;
|
||||
private final @NonNull ThumbnailView thumbnail;
|
||||
private final @NonNull AudioView audioView;
|
||||
private final @NonNull SignalMapView mapView;
|
||||
private final @NonNull AttachmentListener attachmentListener;
|
||||
|
||||
private @NonNull List<Uri> garbage = new LinkedList<>();
|
||||
@@ -71,6 +83,7 @@ public class AttachmentManager {
|
||||
this.attachmentView = ViewUtil.findById(activity, R.id.attachment_editor);
|
||||
this.thumbnail = ViewUtil.findById(activity, R.id.attachment_thumbnail);
|
||||
this.audioView = ViewUtil.findById(activity, R.id.attachment_audio);
|
||||
this.mapView = ViewUtil.findById(activity, R.id.attachment_location);
|
||||
this.removableMediaView = ViewUtil.findById(activity, R.id.removable_media_view);
|
||||
this.context = activity;
|
||||
this.attachmentListener = listener;
|
||||
@@ -134,6 +147,29 @@ public class AttachmentManager {
|
||||
this.slide = Optional.of(slide);
|
||||
}
|
||||
|
||||
public void setLocation(@NonNull final MasterSecret masterSecret,
|
||||
@NonNull final Place place,
|
||||
@NonNull final MediaConstraints constraints)
|
||||
{
|
||||
final SignalPlace signalPlace = new SignalPlace(place);
|
||||
ListenableFuture<Bitmap> future = mapView.display(signalPlace);
|
||||
|
||||
attachmentView.setVisibility(View.VISIBLE);
|
||||
removableMediaView.display(mapView);
|
||||
|
||||
future.addListener(new AssertedSuccessListener<Bitmap>() {
|
||||
@Override
|
||||
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());
|
||||
|
||||
setSlide(locationSlide);
|
||||
attachmentListener.onAttachmentChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setMedia(@NonNull final MasterSecret masterSecret,
|
||||
@NonNull final Uri uri,
|
||||
@NonNull final MediaType mediaType,
|
||||
@@ -218,6 +254,14 @@ public class AttachmentManager {
|
||||
activity.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
public static void selectLocation(Activity activity, int requestCode) {
|
||||
try {
|
||||
activity.startActivityForResult(new PlacePicker.IntentBuilder().build(activity), requestCode);
|
||||
} catch (GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Uri getSlideUri() {
|
||||
return slide.isPresent() ? slide.get().getUri() : null;
|
||||
}
|
||||
@@ -310,7 +354,6 @@ public class AttachmentManager {
|
||||
public @NonNull Slide createSlide(@NonNull Context context,
|
||||
@NonNull Uri uri,
|
||||
long dataSize)
|
||||
throws IOException
|
||||
{
|
||||
switch (this) {
|
||||
case IMAGE: return new ImageSlide(context, uri, dataSize);
|
||||
|
||||
@@ -36,7 +36,7 @@ import ws.com.google.android.mms.pdu.PduPart;
|
||||
|
||||
public class AudioSlide extends Slide {
|
||||
|
||||
public AudioSlide(Context context, Uri uri, long dataSize) throws IOException {
|
||||
public AudioSlide(Context context, Uri uri, long dataSize) {
|
||||
super(context, constructAttachmentFromUri(context, uri, ContentType.AUDIO_UNSPECIFIED, dataSize));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ public class GifSlide extends ImageSlide {
|
||||
super(context, attachment);
|
||||
}
|
||||
|
||||
public GifSlide(Context context, Uri uri, long size) throws IOException {
|
||||
public GifSlide(Context context, Uri uri, long size) {
|
||||
super(context, constructAttachmentFromUri(context, uri, ContentType.IMAGE_GIF, size));
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public class ImageSlide extends Slide {
|
||||
super(context, attachment);
|
||||
}
|
||||
|
||||
public ImageSlide(Context context, Uri uri, long size) throws IOException {
|
||||
public ImageSlide(Context context, Uri uri, long size) {
|
||||
super(context, constructAttachmentFromUri(context, uri, ContentType.IMAGE_JPEG, size));
|
||||
}
|
||||
|
||||
|
||||
25
src/org/thoughtcrime/securesms/mms/LocationSlide.java
Normal file
25
src/org/thoughtcrime/securesms/mms/LocationSlide.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
|
||||
public class LocationSlide extends ImageSlide {
|
||||
|
||||
@NonNull
|
||||
private final String description;
|
||||
|
||||
public LocationSlide(@NonNull Context context, @NonNull Uri uri, long size, @NonNull String description)
|
||||
{
|
||||
super(context, uri, size);
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Optional<String> getBody() {
|
||||
return Optional.of(description);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
@@ -26,7 +28,11 @@ public class OutgoingMediaMessage {
|
||||
|
||||
public OutgoingMediaMessage(Recipients recipients, SlideDeck slideDeck, String message, long sentTimeMillis, int distributionType)
|
||||
{
|
||||
this(recipients, message, slideDeck.asAttachments(), sentTimeMillis, distributionType);
|
||||
this(recipients,
|
||||
TextUtils.isEmpty(message) ? slideDeck.getBody() : slideDeck.getBody() + "\n\n" + message,
|
||||
slideDeck.asAttachments(),
|
||||
sentTimeMillis,
|
||||
distributionType);
|
||||
}
|
||||
|
||||
public OutgoingMediaMessage(OutgoingMediaMessage that) {
|
||||
|
||||
@@ -30,8 +30,6 @@ import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public abstract class Slide {
|
||||
|
||||
protected final Attachment attachment;
|
||||
@@ -57,6 +55,11 @@ public abstract class Slide {
|
||||
return attachment.getThumbnailUri();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Optional<String> getBody() {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
public boolean hasImage() {
|
||||
return false;
|
||||
}
|
||||
@@ -100,7 +103,6 @@ public abstract class Slide {
|
||||
@NonNull Uri uri,
|
||||
@NonNull String defaultMime,
|
||||
long size)
|
||||
throws IOException
|
||||
{
|
||||
Optional<String> resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri));
|
||||
return new UriAttachment(uri, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size);
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -48,6 +50,22 @@ public class SlideDeck {
|
||||
slides.clear();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getBody() {
|
||||
String body = "";
|
||||
|
||||
for (Slide slide : slides) {
|
||||
Optional<String> slideBody = slide.getBody();
|
||||
|
||||
if (slideBody.isPresent()) {
|
||||
body = slideBody.get();
|
||||
}
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<Attachment> asAttachments() {
|
||||
List<Attachment> attachments = new LinkedList<>();
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import ws.com.google.android.mms.pdu.PduPart;
|
||||
|
||||
public class VideoSlide extends Slide {
|
||||
|
||||
public VideoSlide(Context context, Uri uri, long dataSize) throws IOException {
|
||||
public VideoSlide(Context context, Uri uri, long dataSize) {
|
||||
super(context, constructAttachmentFromUri(context, uri, ContentType.VIDEO_UNSPECIFIED, dataSize));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user