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