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