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}