~singpolyma/cheogram-android

b21d948ecb1b29d96ed29f0282b312578409bd69 — Sam Whited 5 years ago 9f34e53
Merge Open Street Map plugin
61 files changed, 1386 insertions(+), 17 deletions(-)

A art/marker.svg
M art/render.rb
M build.gradle
M src/main/AndroidManifest.xml
M src/main/java/eu/siacs/conversations/Config.java
A src/main/java/eu/siacs/conversations/ui/ActionBarActivity.java
A src/main/java/eu/siacs/conversations/ui/LocationActivity.java
A src/main/java/eu/siacs/conversations/ui/ShareLocationActivity.java
A src/main/java/eu/siacs/conversations/ui/ShowLocationActivity.java
M src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java
M src/main/java/eu/siacs/conversations/ui/XmppActivity.java
A src/main/java/eu/siacs/conversations/ui/util/LocationHelper.java
A src/main/java/eu/siacs/conversations/ui/util/UriHelper.java
A src/main/java/eu/siacs/conversations/ui/widget/Marker.java
A src/main/java/eu/siacs/conversations/ui/widget/MyLocation.java
A src/main/res/drawable-hdpi/ic_directions_black_24dp.png
A src/main/res/drawable-hdpi/ic_directions_white_24dp.png
A src/main/res/drawable-hdpi/ic_gps_fixed_black_24dp.png
A src/main/res/drawable-hdpi/ic_gps_fixed_white_24dp.png
A src/main/res/drawable-hdpi/ic_gps_not_fixed_black_24dp.png
A src/main/res/drawable-hdpi/ic_gps_not_fixed_white_24dp.png
A src/main/res/drawable-hdpi/marker.png
A src/main/res/drawable-mdpi/ic_directions_black_24dp.png
A src/main/res/drawable-mdpi/ic_directions_white_24dp.png
A src/main/res/drawable-mdpi/ic_gps_fixed_black_24dp.png
A src/main/res/drawable-mdpi/ic_gps_fixed_white_24dp.png
A src/main/res/drawable-mdpi/ic_gps_not_fixed_black_24dp.png
A src/main/res/drawable-mdpi/ic_gps_not_fixed_white_24dp.png
A src/main/res/drawable-mdpi/marker.png
A src/main/res/drawable-xhdpi/ic_directions_black_24dp.png
A src/main/res/drawable-xhdpi/ic_directions_white_24dp.png
A src/main/res/drawable-xhdpi/ic_gps_fixed_black_24dp.png
A src/main/res/drawable-xhdpi/ic_gps_fixed_white_24dp.png
A src/main/res/drawable-xhdpi/ic_gps_not_fixed_black_24dp.png
A src/main/res/drawable-xhdpi/ic_gps_not_fixed_white_24dp.png
A src/main/res/drawable-xhdpi/marker.png
A src/main/res/drawable-xxhdpi/ic_directions_black_24dp.png
A src/main/res/drawable-xxhdpi/ic_directions_white_24dp.png
A src/main/res/drawable-xxhdpi/ic_gps_fixed_black_24dp.png
A src/main/res/drawable-xxhdpi/ic_gps_fixed_white_24dp.png
A src/main/res/drawable-xxhdpi/ic_gps_not_fixed_black_24dp.png
A src/main/res/drawable-xxhdpi/ic_gps_not_fixed_white_24dp.png
A src/main/res/drawable-xxhdpi/marker.png
A src/main/res/drawable-xxxhdpi/ic_directions_black_24dp.png
A src/main/res/drawable-xxxhdpi/ic_directions_white_24dp.png
A src/main/res/drawable-xxxhdpi/ic_gps_fixed_black_24dp.png
A src/main/res/drawable-xxxhdpi/ic_gps_fixed_white_24dp.png
A src/main/res/drawable-xxxhdpi/ic_gps_not_fixed_black_24dp.png
A src/main/res/drawable-xxxhdpi/ic_gps_not_fixed_white_24dp.png
A src/main/res/drawable-xxxhdpi/marker.png
A src/main/res/drawable/ic_directions_black_24dp.xml
A src/main/res/drawable/ic_gps_fixed_black_24dp.xml
A src/main/res/drawable/ic_gps_not_fixed_black_24dp.xml
A src/main/res/drawable/ic_place_black_24dp.xml
A src/main/res/layout/activity_share_location.xml
A src/main/res/layout/activity_show_location.xml
A src/main/res/menu/menu_show_location.xml
M src/main/res/values/about.xml
M src/main/res/values/attrs.xml
M src/main/res/values/strings.xml
M src/main/res/values/themes.xml
A art/marker.svg => art/marker.svg +110 -0
@@ 0,0 1,110 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="48"
   height="48"
   viewBox="0 0 48 48"
   id="svg2"
   version="1.1"
   inkscape:version="0.91 r13725"
   sodipodi:docname="marker.svg">
  <metadata
     id="metadata10">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title />
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <defs
     id="defs8">
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient3913"
       id="radialGradient3883"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(0.2039074,-0.09024614,0.07170697,0.16216229,-92.579229,-90.973095)"
       cx="262.33273"
       cy="945.23846"
       fx="262.33273"
       fy="945.23846"
       r="185.49754" />
    <linearGradient
       inkscape:collect="always"
       id="linearGradient3913">
      <stop
         style="stop-color:#ffffff;stop-opacity:1;"
         offset="0"
         id="stop3915" />
      <stop
         style="stop-color:#ffffff;stop-opacity:0;"
         offset="1"
         id="stop3917" />
    </linearGradient>
    <clipPath
       clipPathUnits="userSpaceOnUse"
       id="clipPath4167">
      <path
         inkscape:connector-curvature="0"
         d="M 24,4.0000001 C 16.27,4.0000001 10,10.27 10,18 10,28.5 24,44 24,44 24,44 38,28.5 38,18 38,10.27 31.73,4.0000001 24,4.0000001 Z M 24,23 c -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 0,2.76 -2.24,5 -5,5 z"
         id="path4169"
         style="fill:#000000;fill-opacity:1" />
    </clipPath>
    <clipPath
       clipPathUnits="userSpaceOnUse"
       id="clipPath4321">
      <path
         inkscape:connector-curvature="0"
         d="m 24,4.0001492 c -7.73,0 -14,6.2699998 -14,14.0000008 0,10.5 14,26 14,26 0,0 14,-15.5 14,-26 C 38,10.270149 31.73,4.0001492 24,4.0001492 Z M 24,23.00015 c -2.76,0 -5,-2.24 -5,-5 0,-2.760001 2.24,-5.000001 5,-5.000001 2.76,0 5,2.24 5,5.000001 0,2.76 -2.24,5 -5,5 z"
         id="path4323"
         style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:18, 3;stroke-dashoffset:0;stroke-opacity:0.53333285" />
    </clipPath>
  </defs>
  <sodipodi:namedview
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1"
     objecttolerance="10"
     gridtolerance="10"
     guidetolerance="10"
     inkscape:pageopacity="0"
     inkscape:pageshadow="2"
     inkscape:window-width="1920"
     inkscape:window-height="1010"
     id="namedview6"
     showgrid="false"
     inkscape:zoom="4.9166667"
     inkscape:cx="-15.254237"
     inkscape:cy="12.20339"
     inkscape:window-x="0"
     inkscape:window-y="41"
     inkscape:window-maximized="1"
     inkscape:current-layer="svg2" />
  <path
     d="M24 4c-7.73 0-14 6.27-14 14 0 10.5 14 26 14 26s14-15.5 14-26c0-7.73-6.27-14-14-14zm0 19c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"
     id="path4"
     style="fill:#00a000;fill-opacity:1;stroke:none;stroke-opacity:0.53333336;stroke-width:1.70000002;stroke-miterlimit:4;stroke-dasharray:none" />
  <path
     style="display:inline;opacity:0.19211821;fill:url(#radialGradient3883);fill-opacity:1;stroke:none"
     d="m 53.884912,1.7373006 c -18.322492,0 -33.173092,14.5823714 -33.173092,32.5686504 0,3.794038 0.661899,7.436601 1.877335,10.821463 1.505391,0.209531 3.044508,0.317391 4.607513,0.317391 5.584539,0 9.890238,-1.147853 14.805425,-2.934259 l 15.611481,6.295152 a 2.0568126,2.0577227 0 0 0 2.766588,-2.403594 l -4.227888,-17.09591 c 2.717518,-4.771967 3.645449,-10.205846 3.645449,-15.810885 0,-4.0761111 -0.781533,-7.9714274 -2.20495,-11.5551094 -1.217366,-0.132888 -2.454715,-0.202899 -3.707861,-0.202899 z"
     id="path3878"
     inkscape:connector-curvature="0"
     clip-path="url(#clipPath4167)" />
  <path
     inkscape:connector-curvature="0"
     d="M 24,4.0000003 C 16.27,4.0000003 10,10.27 10,18 10,28.5 24,44 24,44 24,44 38,28.5 38,18 38,10.27 31.73,4.0000003 24,4.0000003 Z M 24,23 c -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 0,2.76 -2.24,5 -5,5 z"
     id="path4-3"
     style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-dasharray:30,5;stroke-opacity:0.53333336;stroke-dashoffset:44"
     clip-path="url(#clipPath4321)" />
</svg>

M art/render.rb => art/render.rb +1 -0
@@ 68,6 68,7 @@ images = {
	'message_bubble_sent_grey.svg' => ['message_bubble_sent_grey.9', 0],
	'date_bubble_white.svg' => ['date_bubble_white.9', 0],
	'date_bubble_grey.svg' => ['date_bubble_grey.9', 0],
	'marker.svg' => ['marker', 0]
	}

# Executable paths for Mac OSX

M build.gradle => build.gradle +1 -0
@@ 52,6 52,7 @@ dependencies {
    implementation "com.wefika:flowlayout:0.4.1"
    implementation 'net.ypresto.androidtranscoder:android-transcoder:0.2.0'
    implementation 'rocks.xmpp:xmpp-addr:0.8.0-SNAPSHOT'
    implementation 'org.osmdroid:osmdroid-android:6.0.1'
}

ext {

M src/main/AndroidManifest.xml => src/main/AndroidManifest.xml +28 -1
@@ 13,6 13,13 @@
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

    <uses-feature android:name="android.hardware.location" android:required="false" />
    <uses-feature android:name="android.hardware.location.gps" android:required="false" />
    <uses-feature android:name="android.hardware.location.network" android:required="false" />

    <uses-permission android:name="android.permission.CAMERA" />



@@ 49,7 56,27 @@
                <action android:name="android.media.RINGER_MODE_CHANGED" />
            </intent-filter>
        </receiver>

        <activity
            android:name=".ui.ShareLocationActivity"
            android:label="@string/title_activity_share_location" >
            <intent-filter>
                <action android:name="eu.siacs.conversations.location.request" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ui.ShowLocationActivity"
            android:label="@string/title_activity_show_location" >
            <intent-filter>
                <action android:name="eu.siacs.conversations.location.show" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <data android:scheme="geo" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ui.ConversationActivity"
            android:theme="@style/SplashTheme">

M src/main/java/eu/siacs/conversations/Config.java => src/main/java/eu/siacs/conversations/Config.java +13 -3
@@ 2,7 2,8 @@ package eu.siacs.conversations;

import android.graphics.Bitmap;

import java.util.Arrays;
import org.osmdroid.util.GeoPoint;

import java.util.Collections;
import java.util.List;



@@ 10,8 11,6 @@ import eu.siacs.conversations.xmpp.chatstate.ChatState;
import rocks.xmpp.addr.Jid;

public final class Config {


	private static final int UNENCRYPTED = 1;
	private static final int OPENPGP = 2;
	private static final int OTR = 4;


@@ 160,4 159,15 @@ public final class Config {

	private Config() {
	}

	public static final class Map {
		public final static double INITIAL_ZOOM_LEVEL = 4;
		public final static double FINAL_ZOOM_LEVEL = 15;
		public final static GeoPoint INITIAL_POS = new GeoPoint(33.805278, -84.171389);
		public final static int MY_LOCATION_INDICATOR_SIZE = 10;
		public final static int MY_LOCATION_INDICATOR_OUTLINE_SIZE = 3;
		public final static long LOCATION_FIX_TIME_DELTA = 1000 * 10; // ms
		public final static float LOCATION_FIX_SPACE_DELTA = 10; // m
		public final static int LOCATION_FIX_SIGNIFICANT_TIME_DELTA = 1000 * 60 * 2; // ms
	}
}

A src/main/java/eu/siacs/conversations/ui/ActionBarActivity.java => src/main/java/eu/siacs/conversations/ui/ActionBarActivity.java +13 -0
@@ 0,0 1,13 @@
package eu.siacs.conversations.ui;

import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;

public abstract class ActionBarActivity extends AppCompatActivity {
    public static void configureActionBar(ActionBar actionBar) {
        if (actionBar != null) {
            actionBar.setHomeButtonEnabled(true);
            actionBar.setDisplayHomeAsUpEnabled(true);
        }
    }
}
\ No newline at end of file

A src/main/java/eu/siacs/conversations/ui/LocationActivity.java => src/main/java/eu/siacs/conversations/ui/LocationActivity.java +316 -0
@@ 0,0 1,316 @@
package eu.siacs.conversations.ui;

import android.Manifest;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import android.view.MenuItem;

import org.osmdroid.api.IGeoPoint;
import org.osmdroid.api.IMapController;
import org.osmdroid.config.Configuration;
import org.osmdroid.config.IConfigurationProvider;
import org.osmdroid.tileprovider.tilesource.XYTileSource;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.Overlay;

import java.io.File;

import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.ui.util.LocationHelper;
import eu.siacs.conversations.ui.widget.Marker;
import eu.siacs.conversations.ui.widget.MyLocation;

public abstract class LocationActivity extends ActionBarActivity implements LocationListener {
	protected LocationManager locationManager;
	protected boolean hasLocationFeature;

	public static final int REQUEST_CODE_CREATE = 0;
	public static final int REQUEST_CODE_FAB_PRESSED = 1;
	public static final int REQUEST_CODE_SNACKBAR_PRESSED = 2;

	protected static final String KEY_LOCATION = "loc";
	protected static final String KEY_ZOOM_LEVEL = "zoom";

	protected Location myLoc = null;
	protected MapView map = null;
	protected IMapController mapController = null;

	protected Bitmap marker_icon;

	protected void clearMarkers() {
		synchronized (this.map.getOverlays()) {
			for (final Overlay overlay : this.map.getOverlays()) {
				if (overlay instanceof Marker || overlay instanceof MyLocation) {
					this.map.getOverlays().remove(overlay);
				}
			}
		}
	}

	protected void updateLocationMarkers() {
		clearMarkers();
	}

	protected XYTileSource tileSource() {
		return new XYTileSource("OpenStreetMap",
				0, 19, 256, ".png", new String[] {
				"https://a.tile.openstreetmap.org/",
				"https://b.tile.openstreetmap.org/",
				"https://c.tile.openstreetmap.org/" },"© OpenStreetMap contributors");
	}

	@Override
	protected void onCreate(final Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		final Context ctx = getApplicationContext();

		final PackageManager packageManager = ctx.getPackageManager();
		hasLocationFeature = packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION) ||
				packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS) ||
				packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_NETWORK);
		this.locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
		this.marker_icon = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.marker);
		final Boolean dark = PreferenceManager.getDefaultSharedPreferences(ctx)
				.getString("theme", "light").equals("dark");
		final int mTheme = dark ? R.style.ConversationsTheme_Dark : R.style.ConversationsTheme;
		setTheme(mTheme);

		// Ask for location permissions if location services are enabled and we're
		// just starting the activity (we don't want to keep pestering them on every
		// screen rotation or if there's no point because it's disabled anyways).
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && savedInstanceState == null) {
			requestPermissions(REQUEST_CODE_CREATE);
		}

		final IConfigurationProvider config = Configuration.getInstance();
		config.load(ctx, getPreferences());
		config.setUserAgentValue(BuildConfig.APPLICATION_ID + "_" + BuildConfig.VERSION_CODE);

		final File f = new File(ctx.getCacheDir() + "/tiles");
		try {
			//noinspection ResultOfMethodCallIgnored
			f.mkdirs();
		} catch (final SecurityException ignored) {
		}
		if (f.exists() && f.isDirectory() && f.canRead() && f.canWrite()) {
			Log.d(Config.LOGTAG, "Using tile cache at: " + f.getAbsolutePath());
			config.setOsmdroidTileCache(f.getAbsoluteFile());
		}
	}

	@Override
	protected void onSaveInstanceState(@NonNull final Bundle outState) {
		super.onSaveInstanceState(outState);

		final IGeoPoint center = map.getMapCenter();
		outState.putParcelable(KEY_LOCATION, new GeoPoint(
				center.getLatitude(),
				center.getLongitude()
		));
		outState.putDouble(KEY_ZOOM_LEVEL, map.getZoomLevelDouble());
	}

	@Override
	protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);

		if (savedInstanceState.containsKey(KEY_LOCATION)) {
			mapController.setCenter(savedInstanceState.getParcelable(KEY_LOCATION));
		}
		if (savedInstanceState.containsKey(KEY_ZOOM_LEVEL)) {
			mapController.setZoom(savedInstanceState.getDouble(KEY_ZOOM_LEVEL));
		}
	}

	protected void setupMapView(final GeoPoint pos) {
		// Get map view and configure it.
		map = findViewById(R.id.map);
		map.setTileSource(tileSource());
		map.setBuiltInZoomControls(false);
		map.setMultiTouchControls(true);
		map.setTilesScaledToDpi(getPreferences().getBoolean("scale_tiles_for_high_dpi", false));
		mapController = map.getController();
		mapController.setZoom(Config.Map.INITIAL_ZOOM_LEVEL);
		mapController.setCenter(pos);
	}

	protected void gotoLoc() {
		gotoLoc(map.getZoomLevelDouble() == Config.Map.INITIAL_ZOOM_LEVEL);
	}

	protected abstract void gotoLoc(final boolean setZoomLevel);

	protected abstract void setMyLoc(final Location location);

	protected void requestLocationUpdates() {
		if (!hasLocationFeature || locationManager == null) {
			return;
		}

		Log.d(Config.LOGTAG, "Requesting location updates...");
		final Location lastKnownLocationGps;
		final Location lastKnownLocationNetwork;

		try {
			if (locationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER)) {
				lastKnownLocationGps = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);

				if (lastKnownLocationGps != null) {
					setMyLoc(lastKnownLocationGps);
				}
				locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, Config.Map.LOCATION_FIX_TIME_DELTA,
						Config.Map.LOCATION_FIX_SPACE_DELTA, this);
			} else {
				lastKnownLocationGps = null;
			}

			if (locationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER)) {
				lastKnownLocationNetwork = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
				if (lastKnownLocationNetwork != null && LocationHelper.isBetterLocation(lastKnownLocationNetwork,
						lastKnownLocationGps)) {
					setMyLoc(lastKnownLocationNetwork);
				}
				locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, Config.Map.LOCATION_FIX_TIME_DELTA,
						Config.Map.LOCATION_FIX_SPACE_DELTA, this);
			}

			// If something else is also querying for location more frequently than we are, the battery is already being
			// drained. Go ahead and use the existing locations as often as we can get them.
			if (locationManager.getAllProviders().contains(LocationManager.PASSIVE_PROVIDER)) {
				locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this);
			}
		} catch (final SecurityException ignored) {
			// Do nothing if the users device has no location providers.
		}
	}

	protected void pauseLocationUpdates() throws SecurityException {
		if (locationManager != null) {
			locationManager.removeUpdates(this);
		}
	}

	@Override
	public boolean onOptionsItemSelected(final MenuItem item) {
		switch (item.getItemId()) {
			case android.R.id.home:
				finish();
				return true;
		}
		return super.onOptionsItemSelected(item);
	}

	@Override
	protected void onPause() {
		super.onPause();
		Configuration.getInstance().save(this, getPreferences());
		map.onPause();
		try {
			pauseLocationUpdates();
		} catch (final SecurityException ignored) {
		}
	}

	protected abstract void updateUi();

	protected boolean mapAtInitialLoc() {
		return map.getZoomLevelDouble() == Config.Map.INITIAL_ZOOM_LEVEL;
	}

	@Override
	protected void onResume() {
		super.onResume();
		Configuration.getInstance().load(this, getPreferences());
		map.onResume();
		this.setMyLoc(null);
		requestLocationUpdates();
		updateLocationMarkers();
		updateUi();
		map.setTileSource(tileSource());
		map.setTilesScaledToDpi(getPreferences().getBoolean("scale_tiles_for_high_dpi", false));

		if (mapAtInitialLoc()) {
			gotoLoc();
		}
	}

	@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(
					new String[]{
							Manifest.permission.ACCESS_FINE_LOCATION,
							Manifest.permission.ACCESS_COARSE_LOCATION,
					},
					request_code
			);
		}
	}

	@Override
	public void onRequestPermissionsResult(final int requestCode,
										   @NonNull final String[] permissions,
										   @NonNull final int[] grantResults) {
		super.onRequestPermissionsResult(requestCode, permissions, grantResults);
		for (int i = 0; i < grantResults.length; i++) {
			if (Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[i]) ||
					Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[i])) {
				if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
					requestLocationUpdates();
				}
			}
		}
	}

	protected SharedPreferences getPreferences() {
		return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
	}

	@TargetApi(Build.VERSION_CODES.KITKAT)
	private boolean isLocationEnabledKitkat() {
		try {
			final int locationMode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE);
			return locationMode != Settings.Secure.LOCATION_MODE_OFF;
		} catch( final Settings.SettingNotFoundException e ){
			return false;
		}
	}

	@SuppressWarnings("deprecation")
	private boolean isLocationEnabledLegacy() {
		final String locationProviders = Settings.Secure.getString(getContentResolver(),
				Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
		return !TextUtils.isEmpty(locationProviders);
	}

	protected boolean isLocationEnabled() {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
			return isLocationEnabledKitkat();
		} else {
			return isLocationEnabledLegacy();
		}
	}
}

A src/main/java/eu/siacs/conversations/ui/ShareLocationActivity.java => src/main/java/eu/siacs/conversations/ui/ShareLocationActivity.java +247 -0
@@ 0,0 1,247 @@
package eu.siacs.conversations.ui;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.view.View;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;

import org.osmdroid.api.IGeoPoint;
import org.osmdroid.util.GeoPoint;

import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.ui.util.LocationHelper;
import eu.siacs.conversations.ui.widget.Marker;
import eu.siacs.conversations.ui.widget.MyLocation;

public class ShareLocationActivity extends LocationActivity implements LocationListener {

	private RelativeLayout snackBar;
	private boolean marker_fixed_to_loc = false;
	private static final String KEY_FIXED_TO_LOC = "fixed_to_loc";
	private Boolean noAskAgain = false;

	@Override
	protected void onSaveInstanceState(@NonNull final Bundle outState) {
		super.onSaveInstanceState(outState);

		outState.putBoolean(KEY_FIXED_TO_LOC, marker_fixed_to_loc);
	}

	@Override
	protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);

		if (savedInstanceState.containsKey(KEY_FIXED_TO_LOC)) {
			this.marker_fixed_to_loc = savedInstanceState.getBoolean(KEY_FIXED_TO_LOC);
		}
	}

	@Override
	protected void onCreate(final Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_share_location);
		setSupportActionBar(findViewById(R.id.toolbar));
		configureActionBar(getSupportActionBar());
		setupMapView(Config.Map.INITIAL_POS);

		// Setup the cancel button
		final Button cancelButton = findViewById(R.id.cancel_button);
		cancelButton.setOnClickListener(view -> {
			setResult(RESULT_CANCELED);
			finish();
		});

		// Setup the snackbar
		this.snackBar = findViewById(R.id.snackbar);
		final TextView snackbarAction = findViewById(R.id.snackbar_action);
		snackbarAction.setOnClickListener(view -> {
			if (isLocationEnabledAndAllowed()) {
				updateUi();
			} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !hasLocationPermissions()) {
				requestPermissions(REQUEST_CODE_SNACKBAR_PRESSED);
			} else if (!isLocationEnabled()) {
				startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
			}
		});

		// Setup the share button
		final Button shareButton = findViewById(R.id.share_button);
		if (shareButton != null) {
			shareButton.setOnClickListener(view -> {
				final Intent result = new Intent();

				if (marker_fixed_to_loc && myLoc != null) {
					result.putExtra("latitude", myLoc.getLatitude());
					result.putExtra("longitude", myLoc.getLongitude());
					result.putExtra("altitude", myLoc.getAltitude());
					result.putExtra("accuracy", (int) myLoc.getAccuracy());
				} else {
					final IGeoPoint markerPoint = map.getMapCenter();
					result.putExtra("latitude", markerPoint.getLatitude());
					result.putExtra("longitude", markerPoint.getLongitude());
				}

				setResult(RESULT_OK, result);
				finish();
			});
		}

		this.marker_fixed_to_loc = isLocationEnabledAndAllowed();

		// Setup the fab button
		final FloatingActionButton toggleFixedMarkerButton = findViewById(R.id.fab);
		toggleFixedMarkerButton.setOnClickListener(view -> {
			if (!marker_fixed_to_loc) {
				if (!isLocationEnabled()) {
					startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
				} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
					requestPermissions(REQUEST_CODE_FAB_PRESSED);
				}
			}
			toggleFixedLocation();
		});
	}

	@Override
	public void onRequestPermissionsResult(final int requestCode,
										   @NonNull final String[] permissions,
										   @NonNull final int[] grantResults) {
		super.onRequestPermissionsResult(requestCode, permissions, grantResults);

		if (grantResults.length > 0 &&
				grantResults[0] != PackageManager.PERMISSION_GRANTED &&
				Build.VERSION.SDK_INT >= 23 &&
				permissions.length > 0 &&
				(
						Manifest.permission.LOCATION_HARDWARE.equals(permissions[0]) ||
								Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[0]) ||
								Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[0])
				) &&
				!shouldShowRequestPermissionRationale(permissions[0])) {
			noAskAgain = true;
		}

		if (!noAskAgain && requestCode == REQUEST_CODE_SNACKBAR_PRESSED && !isLocationEnabled() && hasLocationPermissions()) {
			startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
		}
		updateUi();
	}

	@Override
	protected void gotoLoc(final boolean setZoomLevel) {
		if (this.myLoc != null && mapController != null) {
			if (setZoomLevel) {
				mapController.setZoom(Config.Map.FINAL_ZOOM_LEVEL);
			}
			mapController.animateTo(new GeoPoint(this.myLoc));
		}
	}

	@Override
	protected void setMyLoc(final Location location) {
		this.myLoc = location;
	}

	@Override
	protected void onPause() {
		super.onPause();
	}

	@Override
	protected void updateLocationMarkers() {
		super.updateLocationMarkers();
		if (this.myLoc != null) {
			this.map.getOverlays().add(new MyLocation(this, null, this.myLoc));
			if (this.marker_fixed_to_loc) {
				map.getOverlays().add(new Marker(marker_icon, new GeoPoint(this.myLoc)));
			} else {
				map.getOverlays().add(new Marker(marker_icon));
			}
		} else {
			map.getOverlays().add(new Marker(marker_icon));
		}
	}

	@Override
	public void onLocationChanged(final Location location) {
		if (this.myLoc == null) {
			this.marker_fixed_to_loc = true;
		}
		updateUi();
		if (LocationHelper.isBetterLocation(location, this.myLoc)) {
			final Location oldLoc = this.myLoc;
			this.myLoc = location;

			// Don't jump back to the users location if they're not moving (more or less).
			if (oldLoc == null || (this.marker_fixed_to_loc && this.myLoc.distanceTo(oldLoc) > 1)) {
				gotoLoc();
			}

			updateLocationMarkers();
		}
	}

	@Override
	public void onStatusChanged(final String provider, final int status, final Bundle extras) {

	}

	@Override
	public void onProviderEnabled(final String provider) {

	}

	@Override
	public void onProviderDisabled(final String provider) {

	}

	private boolean isLocationEnabledAndAllowed() {
		return this.hasLocationFeature && (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || this.hasLocationPermissions()) && this.isLocationEnabled();
	}

	private void toggleFixedLocation() {
		this.marker_fixed_to_loc = isLocationEnabledAndAllowed() && !this.marker_fixed_to_loc;
		if (this.marker_fixed_to_loc) {
			gotoLoc(false);
		}
		updateLocationMarkers();
		updateUi();
	}

	@Override
	protected void updateUi() {
		if (!hasLocationFeature || noAskAgain || isLocationEnabledAndAllowed()) {
			this.snackBar.setVisibility(View.GONE);
		} else {
			this.snackBar.setVisibility(View.VISIBLE);
		}

		// Setup the fab button
		final FloatingActionButton fab = findViewById(R.id.fab);
		if (isLocationEnabledAndAllowed()) {
			fab.setVisibility(View.VISIBLE);
			runOnUiThread(() -> {
				fab.setImageResource(marker_fixed_to_loc ? R.drawable.ic_gps_fixed_white_24dp :
						R.drawable.ic_gps_not_fixed_white_24dp);
				fab.setContentDescription(getResources().getString(
						marker_fixed_to_loc ? R.string.action_unfix_from_location : R.string.action_fix_to_location
				));
				fab.invalidate();
			});
		} else {
			fab.setVisibility(View.GONE);
		}
	}
}
\ No newline at end of file

A src/main/java/eu/siacs/conversations/ui/ShowLocationActivity.java => src/main/java/eu/siacs/conversations/ui/ShowLocationActivity.java +234 -0
@@ 0,0 1,234 @@
package eu.siacs.conversations.ui;

import android.app.ActionBar;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

import org.osmdroid.util.GeoPoint;

import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
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;


public class ShowLocationActivity extends LocationActivity implements LocationListener {

	private GeoPoint loc = Config.Map.INITIAL_POS;
	private FloatingActionButton navigationButton;


	private Uri createGeoUri() {
		return Uri.parse("geo:" + this.loc.getLatitude() + "," + this.loc.getLongitude());
	}

	@Override
	protected void onCreate(final Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		final ActionBar actionBar = getActionBar();
		if (actionBar != null) {
			actionBar.setDisplayHomeAsUpEnabled(true);
		}

		setContentView(R.layout.activity_show_location);
		setSupportActionBar(findViewById(R.id.toolbar));
		configureActionBar(getSupportActionBar());
		setupMapView(this.loc);

		// Setup the fab button
		this.navigationButton = findViewById(R.id.fab);
		this.navigationButton.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<String, String> 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();
		}
	}

	@Override
	protected void gotoLoc(final boolean setZoomLevel) {
		if (this.loc != null && mapController != null) {
			if (setZoomLevel) {
				mapController.setZoom(Config.Map.FINAL_ZOOM_LEVEL);
			}
			mapController.animateTo(new GeoPoint(this.loc));
		}
	}

	@Override
	public void onRequestPermissionsResult(final int requestCode,
										   @NonNull final String[] permissions,
										   @NonNull final int[] grantResults) {
		super.onRequestPermissionsResult(requestCode, permissions, grantResults);
		updateUi();
	}

	@Override
	protected void setMyLoc(final Location location) {
		this.myLoc = location;
	}

	@Override
	public boolean onCreateOptionsMenu(final Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.menu_show_location, menu);
		updateUi();
		return true;
	}

	@Override
	protected void updateLocationMarkers() {
		super.updateLocationMarkers();
		if (this.myLoc != null) {
			this.map.getOverlays().add(new MyLocation(this, null, this.myLoc));
		}
		this.map.getOverlays().add(new Marker(this.marker_icon, this.loc));
	}

	@Override
	protected void onPause() {
		super.onPause();
	}

	@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);
				}
				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;
		}
		return super.onOptionsItemSelected(item);
	}

	private void startNavigation() {
		startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(
				"google.navigation:q=" +
						String.valueOf(this.loc.getLatitude()) + "," + String.valueOf(this.loc.getLongitude())
		)));
	}

	@Override
	protected void updateUi() {
		final Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("google.navigation:q=0,0"));
		final ComponentName component = i.resolveActivity(getPackageManager());
		if (this.navigationButton != null) {
			this.navigationButton.setVisibility(component == null ? View.GONE : View.VISIBLE);
		}
	}

	@Override
	public void onLocationChanged(final Location location) {
		if (LocationHelper.isBetterLocation(location, this.myLoc)) {
			this.myLoc = location;
			updateLocationMarkers();
		}
	}

	@Override
	public void onStatusChanged(final String provider, final int status, final Bundle extras) {

	}

	@Override
	public void onProviderEnabled(final String provider) {

	}

	@Override
	public void onProviderDisabled(final String provider) {

	}
}

M src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java => src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +1 -1
@@ 165,4 165,4 @@ public class UriHandlerActivity extends AppCompatActivity {
		}
		finish();
	}
}
}
\ No newline at end of file

M src/main/java/eu/siacs/conversations/ui/XmppActivity.java => src/main/java/eu/siacs/conversations/ui/XmppActivity.java +3 -12
@@ 3,9 3,6 @@ package eu.siacs.conversations.ui;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AlertDialog.Builder;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.ClipData;


@@ 37,7 34,8 @@ import android.os.PowerManager;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AlertDialog.Builder;
import android.support.v7.app.AppCompatDelegate;
import android.text.InputType;
import android.util.DisplayMetrics;


@@ 76,7 74,7 @@ import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import rocks.xmpp.addr.Jid;

public abstract class XmppActivity extends AppCompatActivity {
public abstract class XmppActivity extends ActionBarActivity {

	public static final String EXTRA_ACCOUNT = "account";
	protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;


@@ 610,13 608,6 @@ public abstract class XmppActivity extends AppCompatActivity {
		}
	}

	public static void configureActionBar(ActionBar actionBar) {
		if (actionBar != null) {
			actionBar.setHomeButtonEnabled(true);
			actionBar.setDisplayHomeAsUpEnabled(true);
		}
	}

	protected boolean noAccountUsesPgp() {
		if (!hasPgp()) {
			return true;

A src/main/java/eu/siacs/conversations/ui/util/LocationHelper.java => src/main/java/eu/siacs/conversations/ui/util/LocationHelper.java +72 -0
@@ 0,0 1,72 @@
package eu.siacs.conversations.ui.util;

import android.location.Location;

import org.osmdroid.util.GeoPoint;

import eu.siacs.conversations.Config;

public final class LocationHelper {
	/**
	 * Parses a lat long string in the form "lat,long".
	 *
	 * @param latlong A string in the form "lat,long"
	 * @return A GeoPoint representing the lat,long string.
	 * @throws NumberFormatException If an invalid lat or long is specified.
	 */
	public static GeoPoint parseLatLong(final String latlong) throws NumberFormatException {
		if (latlong == null || latlong.isEmpty()) {
			return null;
		}

		final String[] parts = latlong.split(",");
		if (parts[1].contains("?")) {
			parts[1] = parts[1].substring(0, parts[1].indexOf("?"));
		}
		return new GeoPoint(Double.valueOf(parts[0]), Double.valueOf(parts[1]));
	}

	private static boolean isSameProvider(final String provider1, final String provider2) {
		if (provider1 == null) {
			return provider2 == null;
		}
		return provider1.equals(provider2);
	}

	public static boolean isBetterLocation(final Location location, final Location prevLoc) {
		if (prevLoc == null) {
			return true;
		}

		// Check whether the new location fix is newer or older
		final long timeDelta = location.getTime() - prevLoc.getTime();
		final boolean isSignificantlyNewer = timeDelta > Config.Map.LOCATION_FIX_SIGNIFICANT_TIME_DELTA;
		final boolean isSignificantlyOlder = timeDelta < -Config.Map.LOCATION_FIX_SIGNIFICANT_TIME_DELTA;
		final boolean isNewer = timeDelta > 0;

		if (isSignificantlyNewer) {
			return true;
		} else if (isSignificantlyOlder) {
			return false;
		}

		// Check whether the new location fix is more or less accurate
		final int accuracyDelta = (int) (location.getAccuracy() - prevLoc.getAccuracy());
		final boolean isLessAccurate = accuracyDelta > 0;
		final boolean isMoreAccurate = accuracyDelta < 0;
		final boolean isSignificantlyLessAccurate = accuracyDelta > 200;

		// Check if the old and new location are from the same provider
		final boolean isFromSameProvider = isSameProvider(location.getProvider(), prevLoc.getProvider());

		// Determine location quality using a combination of timeliness and accuracy
		if (isMoreAccurate) {
			return true;
		} else if (isNewer && !isLessAccurate) {
			return true;
		} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
			return true;
		}
		return false;
	}
}
\ No newline at end of file

A src/main/java/eu/siacs/conversations/ui/util/UriHelper.java => src/main/java/eu/siacs/conversations/ui/util/UriHelper.java +30 -0
@@ 0,0 1,30 @@
package eu.siacs.conversations.ui.util;

import java.util.HashMap;

/**
 * Helper methods for parsing URI's.
 */
public final class UriHelper {
	/**
	 * Parses a query string into a hashmap.
	 *
	 * @param q The query string to split.
	 * @return A hashmap containing the key-value pairs from the query string.
	 */
	public static HashMap<String, String> parseQueryString(final String q) {
		if (q == null || q.isEmpty()) {
			return null;
		}

		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<String, String> 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);
		}

		return queryMap;
	}
}
\ No newline at end of file

A src/main/java/eu/siacs/conversations/ui/widget/Marker.java => src/main/java/eu/siacs/conversations/ui/widget/Marker.java +52 -0
@@ 0,0 1,52 @@
package eu.siacs.conversations.ui.widget;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;

import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.mylocation.SimpleLocationOverlay;

/**
 * An immutable marker overlay.
 */
public class Marker extends SimpleLocationOverlay {
	private final GeoPoint position;
	private final Bitmap icon;
	private final Point mapPoint;

	/**
	 * Create a marker overlay which will be drawn at the current Geographical position.
	 * @param icon A bitmap icon for the marker
	 * @param position The geographic position where the marker will be drawn (if it is inside the view)
	 */
	public Marker(final Bitmap icon, final GeoPoint position) {
		super(icon);
		this.icon = icon;
		this.position = position;
		this.mapPoint = new Point();
	}

	/**
	 * Create a marker overlay which will be drawn centered in the view.
	 * @param icon A bitmap icon for the marker
	 */
	public Marker(final Bitmap icon) {
		this(icon, null);
	}

	@Override
	public void draw(final Canvas c, final MapView view, final boolean shadow) {
		super.draw(c, view, shadow);

		// If no position was set for the marker, draw it centered in the view.
		view.getProjection().toPixels(this.position == null ? view.getMapCenter() : position, mapPoint);

		c.drawBitmap(icon,
				mapPoint.x - icon.getWidth() / 2,
				mapPoint.y - icon.getHeight(),
				null);

	}
}
\ No newline at end of file

A src/main/java/eu/siacs/conversations/ui/widget/MyLocation.java => src/main/java/eu/siacs/conversations/ui/widget/MyLocation.java +65 -0
@@ 0,0 1,65 @@
package eu.siacs.conversations.ui.widget;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.location.Location;
import android.os.Build;

import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.mylocation.SimpleLocationOverlay;

import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import microsoft.mappoint.TileSystem;

public class MyLocation extends SimpleLocationOverlay {
	private final GeoPoint position;
	private final float accuracy;
	private final Point mapCenterPoint;
	private final Paint fill;
	private final Paint outline;

	@TargetApi(Build.VERSION_CODES.LOLLIPOP)
	private int getColor(final Context ctx) {
		final int accent;
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
			accent = ctx.getResources().getColor(R.color.accent, ctx.getTheme());
		} else {
			//noinspection deprecation
			accent = ctx.getResources().getColor(R.color.accent);
		}
		return accent;
	}

	public MyLocation(final Context ctx, final Bitmap icon, final Location position) {
		super(icon);
		this.mapCenterPoint = new Point();
		this.fill = new Paint(Paint.ANTI_ALIAS_FLAG);
		final int accent = this.getColor(ctx);
		fill.setColor(accent);
		fill.setStyle(Paint.Style.FILL);
		this.outline = new Paint(Paint.ANTI_ALIAS_FLAG);
		outline.setColor(accent);
		outline.setAlpha(50);
		outline.setStyle(Paint.Style.FILL);
		this.position = new GeoPoint(position);
		this.accuracy = position.getAccuracy();
	}

	@Override
	public void draw(final Canvas c, final MapView view, final boolean shadow) {
		super.draw(c, view, shadow);

		view.getProjection().toPixels(position, mapCenterPoint);
		c.drawCircle(mapCenterPoint.x, mapCenterPoint.y,
				Math.max(Config.Map.MY_LOCATION_INDICATOR_SIZE + Config.Map.MY_LOCATION_INDICATOR_OUTLINE_SIZE,
						accuracy / (float) TileSystem.GroundResolution(position.getLatitude(), view.getZoomLevel())
				), this.outline);
		c.drawCircle(mapCenterPoint.x, mapCenterPoint.y, Config.Map.MY_LOCATION_INDICATOR_SIZE, this.fill);
	}
}

A src/main/res/drawable-hdpi/ic_directions_black_24dp.png => src/main/res/drawable-hdpi/ic_directions_black_24dp.png +0 -0
A src/main/res/drawable-hdpi/ic_directions_white_24dp.png => src/main/res/drawable-hdpi/ic_directions_white_24dp.png +0 -0
A src/main/res/drawable-hdpi/ic_gps_fixed_black_24dp.png => src/main/res/drawable-hdpi/ic_gps_fixed_black_24dp.png +0 -0
A src/main/res/drawable-hdpi/ic_gps_fixed_white_24dp.png => src/main/res/drawable-hdpi/ic_gps_fixed_white_24dp.png +0 -0
A src/main/res/drawable-hdpi/ic_gps_not_fixed_black_24dp.png => src/main/res/drawable-hdpi/ic_gps_not_fixed_black_24dp.png +0 -0
A src/main/res/drawable-hdpi/ic_gps_not_fixed_white_24dp.png => src/main/res/drawable-hdpi/ic_gps_not_fixed_white_24dp.png +0 -0
A src/main/res/drawable-hdpi/marker.png => src/main/res/drawable-hdpi/marker.png +0 -0
A src/main/res/drawable-mdpi/ic_directions_black_24dp.png => src/main/res/drawable-mdpi/ic_directions_black_24dp.png +0 -0
A src/main/res/drawable-mdpi/ic_directions_white_24dp.png => src/main/res/drawable-mdpi/ic_directions_white_24dp.png +0 -0
A src/main/res/drawable-mdpi/ic_gps_fixed_black_24dp.png => src/main/res/drawable-mdpi/ic_gps_fixed_black_24dp.png +0 -0
A src/main/res/drawable-mdpi/ic_gps_fixed_white_24dp.png => src/main/res/drawable-mdpi/ic_gps_fixed_white_24dp.png +0 -0
A src/main/res/drawable-mdpi/ic_gps_not_fixed_black_24dp.png => src/main/res/drawable-mdpi/ic_gps_not_fixed_black_24dp.png +0 -0
A src/main/res/drawable-mdpi/ic_gps_not_fixed_white_24dp.png => src/main/res/drawable-mdpi/ic_gps_not_fixed_white_24dp.png +0 -0
A src/main/res/drawable-mdpi/marker.png => src/main/res/drawable-mdpi/marker.png +0 -0
A src/main/res/drawable-xhdpi/ic_directions_black_24dp.png => src/main/res/drawable-xhdpi/ic_directions_black_24dp.png +0 -0
A src/main/res/drawable-xhdpi/ic_directions_white_24dp.png => src/main/res/drawable-xhdpi/ic_directions_white_24dp.png +0 -0
A src/main/res/drawable-xhdpi/ic_gps_fixed_black_24dp.png => src/main/res/drawable-xhdpi/ic_gps_fixed_black_24dp.png +0 -0
A src/main/res/drawable-xhdpi/ic_gps_fixed_white_24dp.png => src/main/res/drawable-xhdpi/ic_gps_fixed_white_24dp.png +0 -0
A src/main/res/drawable-xhdpi/ic_gps_not_fixed_black_24dp.png => src/main/res/drawable-xhdpi/ic_gps_not_fixed_black_24dp.png +0 -0
A src/main/res/drawable-xhdpi/ic_gps_not_fixed_white_24dp.png => src/main/res/drawable-xhdpi/ic_gps_not_fixed_white_24dp.png +0 -0
A src/main/res/drawable-xhdpi/marker.png => src/main/res/drawable-xhdpi/marker.png +0 -0
A src/main/res/drawable-xxhdpi/ic_directions_black_24dp.png => src/main/res/drawable-xxhdpi/ic_directions_black_24dp.png +0 -0
A src/main/res/drawable-xxhdpi/ic_directions_white_24dp.png => src/main/res/drawable-xxhdpi/ic_directions_white_24dp.png +0 -0
A src/main/res/drawable-xxhdpi/ic_gps_fixed_black_24dp.png => src/main/res/drawable-xxhdpi/ic_gps_fixed_black_24dp.png +0 -0
A src/main/res/drawable-xxhdpi/ic_gps_fixed_white_24dp.png => src/main/res/drawable-xxhdpi/ic_gps_fixed_white_24dp.png +0 -0
A src/main/res/drawable-xxhdpi/ic_gps_not_fixed_black_24dp.png => src/main/res/drawable-xxhdpi/ic_gps_not_fixed_black_24dp.png +0 -0
A src/main/res/drawable-xxhdpi/ic_gps_not_fixed_white_24dp.png => src/main/res/drawable-xxhdpi/ic_gps_not_fixed_white_24dp.png +0 -0
A src/main/res/drawable-xxhdpi/marker.png => src/main/res/drawable-xxhdpi/marker.png +0 -0
A src/main/res/drawable-xxxhdpi/ic_directions_black_24dp.png => src/main/res/drawable-xxxhdpi/ic_directions_black_24dp.png +0 -0
A src/main/res/drawable-xxxhdpi/ic_directions_white_24dp.png => src/main/res/drawable-xxxhdpi/ic_directions_white_24dp.png +0 -0
A src/main/res/drawable-xxxhdpi/ic_gps_fixed_black_24dp.png => src/main/res/drawable-xxxhdpi/ic_gps_fixed_black_24dp.png +0 -0
A src/main/res/drawable-xxxhdpi/ic_gps_fixed_white_24dp.png => src/main/res/drawable-xxxhdpi/ic_gps_fixed_white_24dp.png +0 -0
A src/main/res/drawable-xxxhdpi/ic_gps_not_fixed_black_24dp.png => src/main/res/drawable-xxxhdpi/ic_gps_not_fixed_black_24dp.png +0 -0
A src/main/res/drawable-xxxhdpi/ic_gps_not_fixed_white_24dp.png => src/main/res/drawable-xxxhdpi/ic_gps_not_fixed_white_24dp.png +0 -0
A src/main/res/drawable-xxxhdpi/marker.png => src/main/res/drawable-xxxhdpi/marker.png +0 -0
A src/main/res/drawable/ic_directions_black_24dp.xml => src/main/res/drawable/ic_directions_black_24dp.xml +9 -0
@@ 0,0 1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 -1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5V12h-4v3H8v-4c0,-0.55 0.45,-1 1,-1h5V7.5l3.5,3.5 -3.5,3.5z"/>
</vector>

A src/main/res/drawable/ic_gps_fixed_black_24dp.xml => src/main/res/drawable/ic_gps_fixed_black_24dp.xml +9 -0
@@ 0,0 1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>

A src/main/res/drawable/ic_gps_not_fixed_black_24dp.xml => src/main/res/drawable/ic_gps_not_fixed_black_24dp.xml +9 -0
@@ 0,0 1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94H23v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>

A src/main/res/drawable/ic_place_black_24dp.xml => src/main/res/drawable/ic_place_black_24dp.xml +9 -0
@@ 0,0 1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
</vector>

A src/main/res/layout/activity_share_location.xml => src/main/res/layout/activity_share_location.xml +99 -0
@@ 0,0 1,99 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.ShareLocationActivity">

    <include layout="@layout/toolbar" />

    <org.osmdroid.views.MapView android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/button_bar"/>

    <RelativeLayout
        android:id="@+id/snackbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:layout_marginBottom="4dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_above="@+id/button_bar"
        android:background="@drawable/snackbar"
        android:minHeight="48dp"
        android:visibility="visible">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_centerVertical="true"
            android:layout_toStartOf="@+id/snackbar_action"
            android:paddingStart="24dp"
            android:text="@string/location_disabled"
            tools:ignore="RtlSymmetry"
            android:textAppearance="@style/TextAppearance.Conversations.Body1.OnDark"/>
        <TextView
            android:id="@+id/snackbar_action"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingBottom="16dp"
            android:paddingLeft="24dp"
            android:paddingRight="24dp"
            android:paddingTop="16dp"
            android:textAllCaps="true"
            android:textStyle="bold"
            android:text="@string/enable"
            android:layout_alignParentTop="true"
            android:layout_alignParentEnd="true"
            android:textAppearance="@style/TextAppearance.Conversations.Body1.OnDark" />
    </RelativeLayout>

    <LinearLayout
        android:id="@+id/button_bar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        tools:ignore="RtlHardcoded">

        <Button
            android:id="@+id/cancel_button"
            style="?android:attr/borderlessButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/cancel"
            android:textAppearance="@style/TextAppearance.Conversations.Body1"/>

        <View
            android:layout_width="1dp"
            android:layout_height="match_parent"
            android:layout_marginBottom="7dp"
            android:layout_marginTop="7dp"
            android:background="@color/accent"/>

        <Button
            android:id="@+id/share_button"
            style="?android:attr/borderlessButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/share_with"
            android:textAppearance="@style/TextAppearance.Conversations.Body1"/>
    </LinearLayout>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:src="?attr/icon_gps_fixed"
        android:layout_alignParentEnd="true"
        android:layout_above="@+id/button_bar"
        android:contentDescription="@string/action_unfix_from_location"
        android:layout_margin="16dp" />

</RelativeLayout>

A src/main/res/layout/activity_show_location.xml => src/main/res/layout/activity_show_location.xml +25 -0
@@ 0,0 1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.ShowLocationActivity">

    <include layout="@layout/toolbar" />

    <org.osmdroid.views.MapView android:id="@+id/map"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"/>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:src="?attr/icon_directions"
        android:tint="@color/white"
        android:layout_alignParentEnd="true"
        android:contentDescription="@string/action_unfix_from_location"
        android:layout_margin="16dp"
        android:layout_alignParentBottom="true"/>
</RelativeLayout>
\ No newline at end of file

A src/main/res/menu/menu_show_location.xml => src/main/res/menu/menu_show_location.xml +14 -0
@@ 0,0 1,14 @@
<menu
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:id="@+id/action_share_location"
        app:showAsAction="ifRoom"
        android:showAsAction="ifRoom"
        android:title="@string/action_share_location"
        android:icon="?attr/icon_share"/>
    <item android:id="@+id/action_copy_location"
        android:title="@string/action_copy_location"
        android:icon="?attr/icon_copy_bar"
        app:showAsAction="ifRoom"
        android:showAsAction="ifRoom"/>
</menu>
\ No newline at end of file

M src/main/res/values/about.xml => src/main/res/values/about.xml +3 -0
@@ 57,5 57,8 @@
			\n\nhttps://github.com/vinc3m1/RoundedImageView\n(Apache License, Version 2.0)
			\n\nhttps://github.com/jdamcd/android-crop\n(Apache License, Version 2.0)
			\n\nhttps://github.com/zxing/zxing\n(Apache License, Version 2.0)
			\n\nhttps://github.com/osmdroid/osmdroid\n(Apache License, Version 2.0)
			\n\n\nMaps
			\n\nMaps by Open Street Map (https://www.openstreetmap.org). Copyright restrictions may apply.
	</string>
</resources>

M src/main/res/values/attrs.xml => src/main/res/values/attrs.xml +5 -0
@@ 66,6 66,11 @@
    <attr name="icon_enable_undecided_device" format="reference"/>
    <attr name="icon_scroll_down" format="reference"/>

    <attr name="icon_gps_not_fixed" format="reference"/>
    <attr name="icon_gps_fixed" format="reference"/>
    <attr name="icon_directions" format="reference"/>
    <attr name="icon_copy_bar" format="reference"/>

    <attr name="icon_notifications" format="reference"/>
    <attr name="icon_notifications_off" format="reference"/>
    <attr name="icon_notifications_paused" format="reference"/>

M src/main/res/values/strings.xml => src/main/res/values/strings.xml +8 -0
@@ 692,4 692,12 @@
    <string name="large">Large</string>
    <string name="not_encrypted_for_this_device">Message was not encrypted for this device.</string>
    <string name="undo">undo</string>
    <string name="location_disabled">Location sharing is disabled</string>
    <string name="action_fix_to_location">Fix position</string>
    <string name="action_unfix_from_location">Unfix position</string>
    <string name="action_copy_location">Copy Location</string>
    <string name="action_share_location">Share Location</string>
    <string name="action_directions">Directions</string>
    <string name="title_activity_share_location">Share location</string>
    <string name="title_activity_show_location">Show location</string>
</resources>

M src/main/res/values/themes.xml => src/main/res/values/themes.xml +10 -0
@@ 79,6 79,11 @@
        <item type="reference" name="icon_scan_qr_code">@drawable/ic_qr_code_scan_white_24dp</item>
        <item type="reference" name="icon_scroll_down">@drawable/ic_scroll_to_end_black</item>

        <item type="reference" name="icon_gps_not_fixed">@drawable/ic_gps_not_fixed_black_24dp</item>
        <item type="reference" name="icon_gps_fixed">@drawable/ic_gps_fixed_black_24dp</item>
        <item type="reference" name="icon_directions">@drawable/ic_directions_black_24dp</item>
        <item type="reference" name="icon_copy_bar">@drawable/ic_content_copy_white_24dp</item>

        <item type="reference" name="icon_notifications">@drawable/ic_notifications_black_24dp</item>
        <item type="reference" name="icon_notifications_off">@drawable/ic_notifications_off_black_24dp</item>
        <item type="reference" name="icon_notifications_paused">@drawable/ic_notifications_paused_black_24dp</item>


@@ 164,6 169,11 @@
        <item type="reference" name="icon_scan_qr_code">@drawable/ic_qr_code_scan_white_24dp</item>
        <item type="reference" name="icon_scroll_down">@drawable/ic_scroll_to_end_white</item>

        <item type="reference" name="icon_gps_not_fixed">@drawable/ic_gps_not_fixed_white_24dp</item>
        <item type="reference" name="icon_gps_fixed">@drawable/ic_gps_fixed_white_24dp</item>
        <item type="reference" name="icon_directions">@drawable/ic_directions_white_24dp</item>
        <item type="reference" name="icon_copy_bar">@drawable/ic_content_copy_white_24dp</item>

        <item type="reference" name="icon_notifications">@drawable/ic_notifications_white_24dp</item>
        <item type="reference" name="icon_notifications_off">@drawable/ic_notifications_off_white_24dp</item>
        <item type="reference" name="icon_notifications_paused">@drawable/ic_notifications_paused_white_24dp</item>