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