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