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