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