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 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.ui.util.SettingsUtils;
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
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 (getBooleanPreference("use_tor", R.bool.use_tor)) {
101 config.setHttpProxy(HttpConnectionManager.getProxy());
102 }
103 }
104
105 @Override
106 protected void onSaveInstanceState(@NonNull final Bundle outState) {
107 super.onSaveInstanceState(outState);
108
109 final IGeoPoint center = map.getMapCenter();
110 outState.putParcelable(KEY_LOCATION, new GeoPoint(
111 center.getLatitude(),
112 center.getLongitude()
113 ));
114 outState.putDouble(KEY_ZOOM_LEVEL, map.getZoomLevelDouble());
115 }
116
117 @Override
118 protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
119 super.onRestoreInstanceState(savedInstanceState);
120
121 if (savedInstanceState.containsKey(KEY_LOCATION)) {
122 mapController.setCenter(savedInstanceState.getParcelable(KEY_LOCATION));
123 }
124 if (savedInstanceState.containsKey(KEY_ZOOM_LEVEL)) {
125 mapController.setZoom(savedInstanceState.getDouble(KEY_ZOOM_LEVEL));
126 }
127 }
128
129 protected void setupMapView(MapView mapView, final GeoPoint pos) {
130 map = mapView;
131 map.getOverlays().add(new CopyrightOverlay(this));
132 map.setTileSource(TileSourceFactory.MAPNIK);
133 map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
134 map.setMultiTouchControls(true);
135 map.setTilesScaledToDpi(false);
136 mapController = map.getController();
137 mapController.setZoom(Config.Map.INITIAL_ZOOM_LEVEL);
138 mapController.setCenter(pos);
139 }
140
141 protected void gotoLoc() {
142 gotoLoc(map.getZoomLevelDouble() == Config.Map.INITIAL_ZOOM_LEVEL);
143 }
144
145 protected abstract void gotoLoc(final boolean setZoomLevel);
146
147 protected abstract void setMyLoc(final Location location);
148
149 protected void requestLocationUpdates() {
150 if (!hasLocationFeature || locationManager == null) {
151 return;
152 }
153
154 Log.d(Config.LOGTAG, "Requesting location updates...");
155 final Location lastKnownLocationGps;
156 final Location lastKnownLocationNetwork;
157
158 try {
159 if (locationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER)) {
160 lastKnownLocationGps = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
161
162 if (lastKnownLocationGps != null) {
163 setMyLoc(lastKnownLocationGps);
164 }
165 locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, Config.Map.LOCATION_FIX_TIME_DELTA,
166 Config.Map.LOCATION_FIX_SPACE_DELTA, this);
167 } else {
168 lastKnownLocationGps = null;
169 }
170
171 if (locationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER)) {
172 lastKnownLocationNetwork = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
173 if (lastKnownLocationNetwork != null && LocationHelper.isBetterLocation(lastKnownLocationNetwork,
174 lastKnownLocationGps)) {
175 setMyLoc(lastKnownLocationNetwork);
176 }
177 locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, Config.Map.LOCATION_FIX_TIME_DELTA,
178 Config.Map.LOCATION_FIX_SPACE_DELTA, this);
179 }
180
181 // If something else is also querying for location more frequently than we are, the battery is already being
182 // drained. Go ahead and use the existing locations as often as we can get them.
183 if (locationManager.getAllProviders().contains(LocationManager.PASSIVE_PROVIDER)) {
184 locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this);
185 }
186 } catch (final SecurityException ignored) {
187 // Do nothing if the users device has no location providers.
188 }
189 }
190
191 protected void pauseLocationUpdates() throws SecurityException {
192 if (locationManager != null) {
193 locationManager.removeUpdates(this);
194 }
195 }
196
197 @Override
198 public boolean onOptionsItemSelected(final MenuItem item) {
199 switch (item.getItemId()) {
200 case android.R.id.home:
201 finish();
202 return true;
203 }
204 return super.onOptionsItemSelected(item);
205 }
206
207 @Override
208 protected void onPause() {
209 super.onPause();
210 Configuration.getInstance().save(this, getPreferences());
211 map.onPause();
212 try {
213 pauseLocationUpdates();
214 } catch (final SecurityException ignored) {
215 }
216 }
217
218 protected abstract void updateUi();
219
220 protected boolean mapAtInitialLoc() {
221 return map.getZoomLevelDouble() == Config.Map.INITIAL_ZOOM_LEVEL;
222 }
223
224 @Override
225 protected void onResume() {
226 super.onResume();
227 SettingsUtils.applyScreenshotPreventionSetting(this);
228 Configuration.getInstance().load(this, getPreferences());
229 map.onResume();
230 this.setMyLoc(null);
231 requestLocationUpdates();
232 updateLocationMarkers();
233 updateUi();
234 map.setTileSource(TileSourceFactory.MAPNIK);
235 map.setTilesScaledToDpi(true);
236
237 if (mapAtInitialLoc()) {
238 gotoLoc();
239 }
240 }
241
242 @TargetApi(Build.VERSION_CODES.M)
243 protected boolean hasLocationPermissions() {
244 return (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
245 checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED);
246 }
247
248 @TargetApi(Build.VERSION_CODES.M)
249 protected void requestPermissions(final int request_code) {
250 if (!hasLocationPermissions()) {
251 requestPermissions(
252 new String[]{
253 Manifest.permission.ACCESS_FINE_LOCATION,
254 Manifest.permission.ACCESS_COARSE_LOCATION,
255 },
256 request_code
257 );
258 }
259 }
260
261 @Override
262 public void onRequestPermissionsResult(final int requestCode,
263 @NonNull final String[] permissions,
264 @NonNull final int[] grantResults) {
265 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
266 for (int i = 0; i < grantResults.length; i++) {
267 if (Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[i]) ||
268 Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[i])) {
269 if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
270 requestLocationUpdates();
271 }
272 }
273 }
274 }
275
276 protected SharedPreferences getPreferences() {
277 return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
278 }
279
280 protected boolean getBooleanPreference(String name, @BoolRes int res) {
281 return getPreferences().getBoolean(name, getResources().getBoolean(res));
282 }
283
284 protected boolean isLocationEnabled() {
285 try {
286 final int locationMode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE);
287 return locationMode != Settings.Secure.LOCATION_MODE_OFF;
288 } catch( final Settings.SettingNotFoundException e ){
289 return false;
290 }
291 }
292}