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}