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