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