LocationActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.Manifest;
  4import android.annotation.TargetApi;
  5import android.content.Context;
  6import android.content.SharedPreferences;
  7import android.content.pm.PackageManager;
  8import android.graphics.Bitmap;
  9import android.graphics.BitmapFactory;
 10import android.location.Location;
 11import android.location.LocationListener;
 12import android.location.LocationManager;
 13import android.os.Build;
 14import android.os.Bundle;
 15import android.preference.PreferenceManager;
 16import android.provider.Settings;
 17import android.support.annotation.BoolRes;
 18import android.support.annotation.NonNull;
 19import android.util.Log;
 20import android.view.MenuItem;
 21
 22import org.osmdroid.api.IGeoPoint;
 23import org.osmdroid.api.IMapController;
 24import org.osmdroid.config.Configuration;
 25import org.osmdroid.config.IConfigurationProvider;
 26import org.osmdroid.tileprovider.tilesource.XYTileSource;
 27import org.osmdroid.util.GeoPoint;
 28import org.osmdroid.views.MapView;
 29import org.osmdroid.views.overlay.Overlay;
 30
 31import java.io.File;
 32import java.io.IOException;
 33
 34import eu.siacs.conversations.BuildConfig;
 35import eu.siacs.conversations.Config;
 36import eu.siacs.conversations.R;
 37import eu.siacs.conversations.http.HttpConnectionManager;
 38import eu.siacs.conversations.services.QuickConversationsService;
 39import eu.siacs.conversations.ui.util.LocationHelper;
 40import eu.siacs.conversations.ui.widget.Marker;
 41import eu.siacs.conversations.ui.widget.MyLocation;
 42import eu.siacs.conversations.utils.ThemeHelper;
 43
 44public abstract class LocationActivity extends ActionBarActivity implements LocationListener {
 45	protected LocationManager locationManager;
 46	protected boolean hasLocationFeature;
 47
 48	public static final int REQUEST_CODE_CREATE = 0;
 49	public static final int REQUEST_CODE_FAB_PRESSED = 1;
 50	public static final int REQUEST_CODE_SNACKBAR_PRESSED = 2;
 51
 52	protected static final String KEY_LOCATION = "loc";
 53	protected static final String KEY_ZOOM_LEVEL = "zoom";
 54
 55	protected Location myLoc = null;
 56	private MapView map = null;
 57	protected IMapController mapController = null;
 58
 59	protected Bitmap marker_icon;
 60
 61	protected void clearMarkers() {
 62		synchronized (this.map.getOverlays()) {
 63			for (final Overlay overlay : this.map.getOverlays()) {
 64				if (overlay instanceof Marker || overlay instanceof MyLocation) {
 65					this.map.getOverlays().remove(overlay);
 66				}
 67			}
 68		}
 69	}
 70
 71	protected void updateLocationMarkers() {
 72		clearMarkers();
 73	}
 74
 75	protected XYTileSource tileSource() {
 76		return new XYTileSource("OpenStreetMap",
 77				0, 19, 256, ".png", new String[] {
 78				"https://a.tile.openstreetmap.org/",
 79				"https://b.tile.openstreetmap.org/",
 80				"https://c.tile.openstreetmap.org/" },"© OpenStreetMap contributors");
 81	}
 82
 83	@Override
 84	protected void onCreate(final Bundle savedInstanceState) {
 85		super.onCreate(savedInstanceState);
 86		final Context ctx = getApplicationContext();
 87		setTheme(ThemeHelper.find(this));
 88
 89		final PackageManager packageManager = ctx.getPackageManager();
 90		hasLocationFeature = packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION) ||
 91				packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS) ||
 92				packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_NETWORK);
 93		this.locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
 94		this.marker_icon = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.marker);
 95
 96		// Ask for location permissions if location services are enabled and we're
 97		// just starting the activity (we don't want to keep pestering them on every
 98		// screen rotation or if there's no point because it's disabled anyways).
 99		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && savedInstanceState == null) {
100			requestPermissions(REQUEST_CODE_CREATE);
101		}
102
103		final IConfigurationProvider config = Configuration.getInstance();
104		config.load(ctx, getPreferences());
105		config.setUserAgentValue(BuildConfig.APPLICATION_ID + "_" + BuildConfig.VERSION_CODE);
106		if (QuickConversationsService.isConversations() && getBooleanPreference("use_tor", R.bool.use_tor)) {
107			try {
108				config.setHttpProxy(HttpConnectionManager.getProxy());
109			} catch (IOException e) {
110				throw new RuntimeException("Unable to configure proxy");
111			}
112		}
113
114		final File f = new File(ctx.getCacheDir() + "/tiles");
115		try {
116			//noinspection ResultOfMethodCallIgnored
117			f.mkdirs();
118		} catch (final SecurityException ignored) {
119		}
120		if (f.exists() && f.isDirectory() && f.canRead() && f.canWrite()) {
121			Log.d(Config.LOGTAG, "Using tile cache at: " + f.getAbsolutePath());
122			config.setOsmdroidTileCache(f.getAbsoluteFile());
123		}
124	}
125
126	@Override
127	protected void onSaveInstanceState(@NonNull final Bundle outState) {
128		super.onSaveInstanceState(outState);
129
130		final IGeoPoint center = map.getMapCenter();
131		outState.putParcelable(KEY_LOCATION, new GeoPoint(
132				center.getLatitude(),
133				center.getLongitude()
134		));
135		outState.putDouble(KEY_ZOOM_LEVEL, map.getZoomLevelDouble());
136	}
137
138	@Override
139	protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
140		super.onRestoreInstanceState(savedInstanceState);
141
142		if (savedInstanceState.containsKey(KEY_LOCATION)) {
143			mapController.setCenter(savedInstanceState.getParcelable(KEY_LOCATION));
144		}
145		if (savedInstanceState.containsKey(KEY_ZOOM_LEVEL)) {
146			mapController.setZoom(savedInstanceState.getDouble(KEY_ZOOM_LEVEL));
147		}
148	}
149
150	protected void setupMapView(MapView mapView, final GeoPoint pos) {
151		map = mapView;
152		map.setTileSource(tileSource());
153		map.setBuiltInZoomControls(false);
154		map.setMultiTouchControls(true);
155		map.setTilesScaledToDpi(true);
156		mapController = map.getController();
157		mapController.setZoom(Config.Map.INITIAL_ZOOM_LEVEL);
158		mapController.setCenter(pos);
159	}
160
161	protected void gotoLoc() {
162		gotoLoc(map.getZoomLevelDouble() == Config.Map.INITIAL_ZOOM_LEVEL);
163	}
164
165	protected abstract void gotoLoc(final boolean setZoomLevel);
166
167	protected abstract void setMyLoc(final Location location);
168
169	protected void requestLocationUpdates() {
170		if (!hasLocationFeature || locationManager == null) {
171			return;
172		}
173
174		Log.d(Config.LOGTAG, "Requesting location updates...");
175		final Location lastKnownLocationGps;
176		final Location lastKnownLocationNetwork;
177
178		try {
179			if (locationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER)) {
180				lastKnownLocationGps = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
181
182				if (lastKnownLocationGps != null) {
183					setMyLoc(lastKnownLocationGps);
184				}
185				locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, Config.Map.LOCATION_FIX_TIME_DELTA,
186						Config.Map.LOCATION_FIX_SPACE_DELTA, this);
187			} else {
188				lastKnownLocationGps = null;
189			}
190
191			if (locationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER)) {
192				lastKnownLocationNetwork = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
193				if (lastKnownLocationNetwork != null && LocationHelper.isBetterLocation(lastKnownLocationNetwork,
194						lastKnownLocationGps)) {
195					setMyLoc(lastKnownLocationNetwork);
196				}
197				locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, Config.Map.LOCATION_FIX_TIME_DELTA,
198						Config.Map.LOCATION_FIX_SPACE_DELTA, this);
199			}
200
201			// If something else is also querying for location more frequently than we are, the battery is already being
202			// drained. Go ahead and use the existing locations as often as we can get them.
203			if (locationManager.getAllProviders().contains(LocationManager.PASSIVE_PROVIDER)) {
204				locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this);
205			}
206		} catch (final SecurityException ignored) {
207			// Do nothing if the users device has no location providers.
208		}
209	}
210
211	protected void pauseLocationUpdates() throws SecurityException {
212		if (locationManager != null) {
213			locationManager.removeUpdates(this);
214		}
215	}
216
217	@Override
218	public boolean onOptionsItemSelected(final MenuItem item) {
219		switch (item.getItemId()) {
220			case android.R.id.home:
221				finish();
222				return true;
223		}
224		return super.onOptionsItemSelected(item);
225	}
226
227	@Override
228	protected void onPause() {
229		super.onPause();
230		Configuration.getInstance().save(this, getPreferences());
231		map.onPause();
232		try {
233			pauseLocationUpdates();
234		} catch (final SecurityException ignored) {
235		}
236	}
237
238	protected abstract void updateUi();
239
240	protected boolean mapAtInitialLoc() {
241		return map.getZoomLevelDouble() == Config.Map.INITIAL_ZOOM_LEVEL;
242	}
243
244	@Override
245	protected void onResume() {
246		super.onResume();
247		Configuration.getInstance().load(this, getPreferences());
248		map.onResume();
249		this.setMyLoc(null);
250		requestLocationUpdates();
251		updateLocationMarkers();
252		updateUi();
253		map.setTileSource(tileSource());
254		map.setTilesScaledToDpi(true);
255
256		if (mapAtInitialLoc()) {
257			gotoLoc();
258		}
259	}
260
261	@TargetApi(Build.VERSION_CODES.M)
262	protected boolean hasLocationPermissions() {
263		return (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
264				checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED);
265	}
266
267	@TargetApi(Build.VERSION_CODES.M)
268	protected void requestPermissions(final int request_code) {
269		if (!hasLocationPermissions()) {
270			requestPermissions(
271					new String[]{
272							Manifest.permission.ACCESS_FINE_LOCATION,
273							Manifest.permission.ACCESS_COARSE_LOCATION,
274					},
275					request_code
276			);
277		}
278	}
279
280	@Override
281	public void onRequestPermissionsResult(final int requestCode,
282										   @NonNull final String[] permissions,
283										   @NonNull final int[] grantResults) {
284		super.onRequestPermissionsResult(requestCode, permissions, grantResults);
285		for (int i = 0; i < grantResults.length; i++) {
286			if (Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[i]) ||
287					Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[i])) {
288				if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
289					requestLocationUpdates();
290				}
291			}
292		}
293	}
294
295	protected SharedPreferences getPreferences() {
296		return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
297	}
298
299	protected boolean getBooleanPreference(String name, @BoolRes int res) {
300		return getPreferences().getBoolean(name, getResources().getBoolean(res));
301	}
302
303	protected boolean isLocationEnabled() {
304		try {
305			final int locationMode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE);
306			return locationMode != Settings.Secure.LOCATION_MODE_OFF;
307		} catch( final Settings.SettingNotFoundException e ){
308			return false;
309		}
310	}
311}