From e950d431bc5a7c0bb1eecb5b6b85f1dfb710d42b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Apr 2024 10:13:04 +0200 Subject: [PATCH] use internal location viewer for in text geo uris --- .../conversations/ui/LocationActivity.java | 2 - .../ui/ShowLocationActivity.java | 179 ++++++++---------- .../ui/adapter/MediaPreviewAdapter.java | 17 +- .../conversations/ui/text/FixedURLSpan.java | 80 ++++---- .../conversations/ui/util/UriHelper.java | 15 +- .../siacs/conversations/utils/GeoHelper.java | 12 +- ...nt_copy_24dp.xml => ic_open_with_24dp.xml} | 2 +- src/main/res/menu/menu_show_location.xml | 8 +- 8 files changed, 161 insertions(+), 154 deletions(-) rename src/main/res/drawable/{ic_content_copy_24dp.xml => ic_open_with_24dp.xml} (60%) diff --git a/src/main/java/eu/siacs/conversations/ui/LocationActivity.java b/src/main/java/eu/siacs/conversations/ui/LocationActivity.java index 0e5ec7156..2de75cba7 100644 --- a/src/main/java/eu/siacs/conversations/ui/LocationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/LocationActivity.java @@ -236,13 +236,11 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca } } - @TargetApi(Build.VERSION_CODES.M) protected boolean hasLocationPermissions() { return (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED); } - @TargetApi(Build.VERSION_CODES.M) protected void requestPermissions(final int request_code) { if (!hasLocationPermissions()) { requestPermissions( diff --git a/src/main/java/eu/siacs/conversations/ui/ShowLocationActivity.java b/src/main/java/eu/siacs/conversations/ui/ShowLocationActivity.java index a902fe08c..8ed7e1da6 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShowLocationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShowLocationActivity.java @@ -9,6 +9,7 @@ import android.location.Location; import android.location.LocationListener; import android.net.Uri; import android.os.Bundle; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -17,11 +18,8 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; -import org.osmdroid.util.GeoPoint; - -import java.util.HashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import com.google.common.base.Strings; +import com.google.common.primitives.Doubles; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -30,8 +28,13 @@ import eu.siacs.conversations.ui.util.LocationHelper; import eu.siacs.conversations.ui.util.UriHelper; import eu.siacs.conversations.ui.widget.Marker; import eu.siacs.conversations.ui.widget.MyLocation; +import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.LocationProvider; +import org.osmdroid.util.GeoPoint; + +import java.util.Map; + public class ShowLocationActivity extends LocationActivity implements LocationListener { private GeoPoint loc = LocationProvider.FALLBACK; @@ -56,73 +59,41 @@ public class ShowLocationActivity extends LocationActivity implements LocationLi this.binding.fab.setOnClickListener(view -> startNavigation()); final Intent intent = getIntent(); - if (intent != null) { - final String action = intent.getAction(); - if (action == null) { - return; - } - switch (action) { - case "eu.siacs.conversations.location.show": - if (intent.hasExtra("longitude") && intent.hasExtra("latitude")) { - final double longitude = intent.getDoubleExtra("longitude", 0); - final double latitude = intent.getDoubleExtra("latitude", 0); - this.loc = new GeoPoint(latitude, longitude); - } - break; - case Intent.ACTION_VIEW: - final Uri geoUri = intent.getData(); - - // Attempt to set zoom level if the geo URI specifies it - if (geoUri != null) { - final HashMap query = - UriHelper.parseQueryString(geoUri.getQuery()); - - // Check for zoom level. - final String z = query.get("z"); - if (z != null) { - try { - mapController.setZoom(Double.valueOf(z)); - } catch (final Exception ignored) { - } - } - - // Check for the actual geo query. - boolean posInQuery = false; - final String q = query.get("q"); - if (q != null) { - final Pattern latlng = - Pattern.compile( - "/^([-+]?[0-9]+(\\.[0-9]+)?),([-+]?[0-9]+(\\.[0-9]+)?)(\\(.*\\))?/"); - final Matcher m = latlng.matcher(q); - if (m.matches()) { - try { - this.loc = - new GeoPoint( - Double.valueOf(m.group(1)), - Double.valueOf(m.group(3))); - posInQuery = true; - } catch (final Exception ignored) { - } - } - } - - final String schemeSpecificPart = geoUri.getSchemeSpecificPart(); - if (schemeSpecificPart != null && !schemeSpecificPart.isEmpty()) { - try { - final GeoPoint latlong = - LocationHelper.parseLatLong(schemeSpecificPart); - if (latlong != null && !posInQuery) { - this.loc = latlong; - } - } catch (final NumberFormatException ignored) { - } - } - } - - break; - } - updateLocationMarkers(); + if (intent == null) { + return; } + final String action = intent.getAction(); + switch (Strings.nullToEmpty(action)) { + case "eu.siacs.conversations.location.show": + if (intent.hasExtra("longitude") && intent.hasExtra("latitude")) { + final double longitude = intent.getDoubleExtra("longitude", 0); + final double latitude = intent.getDoubleExtra("latitude", 0); + this.loc = new GeoPoint(latitude, longitude); + } + break; + case Intent.ACTION_VIEW: + final Uri uri = intent.getData(); + if (uri == null) { + break; + } + final GeoPoint point; + try { + point = GeoHelper.parseGeoPoint(uri); + } catch (final Exception e) { + break; + } + this.loc = point; + final Map query = UriHelper.parseQueryString(uri.getQuery()); + final String z = query.get("z"); + final Double zoom = Strings.isNullOrEmpty(z) ? null : Doubles.tryParse(z); + if (zoom != null) { + Log.d(Config.LOGTAG, "inferring zoom level " + zoom + " from geo uri"); + mapController.setZoom(zoom); + gotoLoc(false); + } + break; + } + updateLocationMarkers(); } @Override @@ -173,37 +144,43 @@ public class ShowLocationActivity extends LocationActivity implements LocationLi @Override public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.action_copy_location: - final ClipboardManager clipboard = - (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - if (clipboard != null) { - final ClipData clip = - ClipData.newPlainText("location", createGeoUri().toString()); - clipboard.setPrimaryClip(clip); - Toast.makeText(this, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT) - .show(); - } - return true; - case R.id.action_share_location: - final Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_TEXT, createGeoUri().toString()); - shareIntent.setType("text/plain"); - try { - startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with))); - } catch (final ActivityNotFoundException e) { - // This should happen only on faulty androids because normally chooser is always - // available - Toast.makeText( - this, - R.string.no_application_found_to_open_file, - Toast.LENGTH_SHORT) - .show(); - } - return true; + final var itemId = item.getItemId(); + if (itemId == R.id.action_copy_location) { + final ClipboardManager clipboard = getSystemService(ClipboardManager.class); + final ClipData clip = ClipData.newPlainText("location", createGeoUri().toString()); + clipboard.setPrimaryClip(clip); + Toast.makeText(this, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show(); + return true; + } else if (itemId == R.id.action_share_location) { + final Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_TEXT, createGeoUri().toString()); + shareIntent.setType("text/plain"); + try { + startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with))); + } catch (final ActivityNotFoundException e) { + // This should happen only on faulty androids because normally chooser is always + // available + Toast.makeText(this, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT) + .show(); + } + return true; + } else if (itemId == R.id.action_open_with) { + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(createGeoUri()); + try { + startActivity(Intent.createChooser(intent, getText(R.string.open_with))); + } catch (final ActivityNotFoundException e) { + // This should happen only on faulty androids because normally chooser is always + // available + Toast.makeText(this, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT) + .show(); + } + return true; + + } else { + return super.onOptionsItemSelected(item); } - return super.onOptionsItemSelected(item); } private void startNavigation() { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java index a416b2b43..a579c07b2 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java @@ -9,6 +9,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; +import android.util.Log; import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.ImageView; @@ -20,16 +21,19 @@ import androidx.core.widget.ImageViewCompat; import androidx.databinding.DataBindingUtil; import androidx.recyclerview.widget.RecyclerView; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ItemMediaPreviewBinding; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.ui.ConversationFragment; +import eu.siacs.conversations.ui.ShowLocationActivity; import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.ui.util.Attachment; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.concurrent.RejectedExecutionException; public class MediaPreviewAdapter @@ -73,11 +77,16 @@ public class MediaPreviewAdapter holder.binding.mediaPreview.setOnClickListener(v -> view(context, attachment)); } - private static void view(final Context context, Attachment attachment) { + private static void view(final Context context, final Attachment attachment) { final Intent view = new Intent(Intent.ACTION_VIEW); - final Uri uri = FileBackend.getUriForUri(context, attachment.getUri()); - view.setDataAndType(uri, attachment.getMime()); - view.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + if (attachment.getType() == Attachment.Type.LOCATION) { + view.setClass(context, ShowLocationActivity.class); + view.setData(attachment.getUri()); + } else { + final Uri uri = FileBackend.getUriForUri(context, attachment.getUri()); + view.setDataAndType(uri, attachment.getMime()); + view.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } try { context.startActivity(view); } catch (final ActivityNotFoundException e) { diff --git a/src/main/java/eu/siacs/conversations/ui/text/FixedURLSpan.java b/src/main/java/eu/siacs/conversations/ui/text/FixedURLSpan.java index a5fba62a7..e84c04fbf 100644 --- a/src/main/java/eu/siacs/conversations/ui/text/FixedURLSpan.java +++ b/src/main/java/eu/siacs/conversations/ui/text/FixedURLSpan.java @@ -38,6 +38,7 @@ import android.os.Build; import android.text.Editable; import android.text.Spanned; import android.text.style.URLSpan; +import android.view.SoundEffectConstants; import android.view.View; import android.widget.Toast; @@ -45,43 +46,56 @@ import java.util.Arrays; import eu.siacs.conversations.R; import eu.siacs.conversations.ui.ConversationsActivity; - +import eu.siacs.conversations.ui.ShowLocationActivity; @SuppressLint("ParcelCreator") public class FixedURLSpan extends URLSpan { - private FixedURLSpan(String url) { - super(url); - } + private FixedURLSpan(String url) { + super(url); + } - public static void fix(final Editable editable) { - for (final URLSpan urlspan : editable.getSpans(0, editable.length() - 1, URLSpan.class)) { - final int start = editable.getSpanStart(urlspan); - final int end = editable.getSpanEnd(urlspan); - editable.removeSpan(urlspan); - editable.setSpan(new FixedURLSpan(urlspan.getURL()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } + public static void fix(final Editable editable) { + for (final URLSpan urlspan : editable.getSpans(0, editable.length() - 1, URLSpan.class)) { + final int start = editable.getSpanStart(urlspan); + final int end = editable.getSpanEnd(urlspan); + editable.removeSpan(urlspan); + editable.setSpan( + new FixedURLSpan(urlspan.getURL()), + start, + end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } - @Override - public void onClick(View widget) { - final Uri uri = Uri.parse(getURL()); - final Context context = widget.getContext(); - final boolean candidateToProcessDirectly = "xmpp".equals(uri.getScheme()) || ("https".equals(uri.getScheme()) && "conversations.im".equals(uri.getHost()) && uri.getPathSegments().size() > 1 && Arrays.asList("j","i").contains(uri.getPathSegments().get(0))); - if (candidateToProcessDirectly && context instanceof ConversationsActivity) { - if (((ConversationsActivity) context).onXmppUriClicked(uri)) { - widget.playSoundEffect(0); - return; - } - } - final Intent intent = new Intent(Intent.ACTION_VIEW, uri); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); - //intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); - try { - context.startActivity(intent); - widget.playSoundEffect(0); - } catch (ActivityNotFoundException e) { - Toast.makeText(context, R.string.no_application_found_to_open_link, Toast.LENGTH_SHORT).show(); - } - } + @Override + public void onClick(final View widget) { + final Uri uri = Uri.parse(getURL()); + final Context context = widget.getContext(); + final boolean candidateToProcessDirectly = + "xmpp".equals(uri.getScheme()) + || ("https".equals(uri.getScheme()) + && "conversations.im".equals(uri.getHost()) + && uri.getPathSegments().size() > 1 + && Arrays.asList("j", "i").contains(uri.getPathSegments().get(0))); + if (candidateToProcessDirectly && context instanceof ConversationsActivity) { + if (((ConversationsActivity) context).onXmppUriClicked(uri)) { + widget.playSoundEffect(SoundEffectConstants.CLICK); + return; + } + } + final Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if ("geo".equalsIgnoreCase(uri.getScheme())) { + intent.setClass(context, ShowLocationActivity.class); + } else { + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); + } + try { + context.startActivity(intent); + widget.playSoundEffect(SoundEffectConstants.CLICK); + } catch (ActivityNotFoundException e) { + Toast.makeText(context, R.string.no_application_found_to_open_link, Toast.LENGTH_SHORT) + .show(); + } + } } diff --git a/src/main/java/eu/siacs/conversations/ui/util/UriHelper.java b/src/main/java/eu/siacs/conversations/ui/util/UriHelper.java index e91012ad1..50bd13e93 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/UriHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/UriHelper.java @@ -1,6 +1,8 @@ package eu.siacs.conversations.ui.util; -import java.util.HashMap; +import com.google.common.collect.ImmutableMap; + +import java.util.Map; /** * Helper methods for parsing URI's. @@ -12,19 +14,18 @@ public final class UriHelper { * @param q The query string to split. * @return A hashmap containing the key-value pairs from the query string. */ - public static HashMap parseQueryString(final String q) { + public static Map parseQueryString(final String q) { if (q == null || q.isEmpty()) { - return null; + return ImmutableMap.of(); } + final ImmutableMap.Builder queryMapBuilder = new ImmutableMap.Builder<>(); final String[] query = q.split("&"); - // TODO: Look up the HashMap implementation and figure out what the load factor is and make sure we're not reallocating here. - final HashMap queryMap = new HashMap<>(query.length); for (final String param : query) { final String[] pair = param.split("="); - queryMap.put(pair[0], pair.length == 2 && !pair[1].isEmpty() ? pair[1] : null); + queryMapBuilder.put(pair[0], pair.length == 2 && !pair[1].isEmpty() ? pair[1] : null); } - return queryMap; + return queryMapBuilder.build(); } } \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/utils/GeoHelper.java b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java index 459968f37..46de870ad 100644 --- a/src/main/java/eu/siacs/conversations/utils/GeoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java @@ -46,13 +46,17 @@ public class GeoHelper { } } + public static GeoPoint parseGeoPoint(final Uri uri) { + return parseGeoPoint(uri.toString()); + } + private static GeoPoint parseGeoPoint(String body) throws IllegalArgumentException { - Matcher matcher = GEO_URI.matcher(body); + final Matcher matcher = GEO_URI.matcher(body); if (!matcher.matches()) { throw new IllegalArgumentException("Invalid geo uri"); } - double latitude; - double longitude; + final double latitude; + final double longitude; try { latitude = Double.parseDouble(matcher.group(1)); if (latitude > 90.0 || latitude < -90.0) { @@ -62,7 +66,7 @@ public class GeoHelper { if (longitude > 180.0 || longitude < -180.0) { throw new IllegalArgumentException("Invalid geo uri"); } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { throw new IllegalArgumentException("Invalid geo uri",e); } return new GeoPoint(latitude, longitude); diff --git a/src/main/res/drawable/ic_content_copy_24dp.xml b/src/main/res/drawable/ic_open_with_24dp.xml similarity index 60% rename from src/main/res/drawable/ic_content_copy_24dp.xml rename to src/main/res/drawable/ic_open_with_24dp.xml index 2af133e83..22bb8f7c2 100644 --- a/src/main/res/drawable/ic_content_copy_24dp.xml +++ b/src/main/res/drawable/ic_open_with_24dp.xml @@ -7,6 +7,6 @@ + android:pathData="M10,9h4L14,6h3l-5,-5 -5,5h3v3zM9,10L6,10L6,7l-5,5 5,5v-3h3v-4zM23,12l-5,-5v3h-3v4h3v3l5,-5zM14,15h-4v3L7,18l5,5 5,-5h-3v-3z" /> diff --git a/src/main/res/menu/menu_show_location.xml b/src/main/res/menu/menu_show_location.xml index eb0c128eb..5305040f5 100644 --- a/src/main/res/menu/menu_show_location.xml +++ b/src/main/res/menu/menu_show_location.xml @@ -6,9 +6,13 @@ android:showAsAction="ifRoom" android:title="@string/action_share_location" android:icon="@drawable/ic_share_24dp"/> + \ No newline at end of file