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