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.TileSourceFactory;
27import org.osmdroid.tileprovider.tilesource.XYTileSource;
28import org.osmdroid.util.GeoPoint;
29import org.osmdroid.views.CustomZoomButtonsController;
30import org.osmdroid.views.MapView;
31import org.osmdroid.views.overlay.Overlay;
32
33import java.io.File;
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.LocationProvider;
45import eu.siacs.conversations.utils.ThemeHelper;
46
47public abstract class LocationActivity extends ActionBarActivity implements LocationListener {
48 protected LocationManager locationManager;
49 protected boolean hasLocationFeature;
50
51 public static final int REQUEST_CODE_CREATE = 0;
52 public static final int REQUEST_CODE_FAB_PRESSED = 1;
53 public static final int REQUEST_CODE_SNACKBAR_PRESSED = 2;
54
55 protected static final String KEY_LOCATION = "loc";
56 protected static final String KEY_ZOOM_LEVEL = "zoom";
57
58 protected Location myLoc = null;
59 private MapView map = null;
60 protected IMapController mapController = null;
61
62 protected Bitmap marker_icon;
63
64 protected void clearMarkers() {
65 synchronized (this.map.getOverlays()) {
66 for (final Overlay overlay : this.map.getOverlays()) {
67 if (overlay instanceof Marker || overlay instanceof MyLocation) {
68 this.map.getOverlays().remove(overlay);
69 }
70 }
71 }
72 }
73
74 protected void updateLocationMarkers() {
75 clearMarkers();
76 }
77
78 @Override
79 protected void onCreate(final Bundle savedInstanceState) {
80 super.onCreate(savedInstanceState);
81 final Context ctx = getApplicationContext();
82 setTheme(ThemeHelper.find(this));
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
91 // Ask for location permissions if location services are enabled and we're
92 // just starting the activity (we don't want to keep pestering them on every
93 // screen rotation or if there's no point because it's disabled anyways).
94 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && savedInstanceState == null) {
95 requestPermissions(REQUEST_CODE_CREATE);
96 }
97
98 final IConfigurationProvider config = Configuration.getInstance();
99 config.load(ctx, getPreferences());
100 config.setUserAgentValue(BuildConfig.APPLICATION_ID + "/" + BuildConfig.VERSION_CODE);
101 if (QuickConversationsService.isConversations() && getBooleanPreference("use_tor", R.bool.use_tor)) {
102 try {
103 config.setHttpProxy(HttpConnectionManager.getProxy());
104 } catch (IOException e) {
105 throw new RuntimeException("Unable to configure proxy");
106 }
107 }
108 }
109
110 @Override
111 protected void onSaveInstanceState(@NonNull final Bundle outState) {
112 super.onSaveInstanceState(outState);
113
114 final IGeoPoint center = map.getMapCenter();
115 outState.putParcelable(KEY_LOCATION, new GeoPoint(
116 center.getLatitude(),
117 center.getLongitude()
118 ));
119 outState.putDouble(KEY_ZOOM_LEVEL, map.getZoomLevelDouble());
120 }
121
122 @Override
123 protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
124 super.onRestoreInstanceState(savedInstanceState);
125
126 if (savedInstanceState.containsKey(KEY_LOCATION)) {
127 mapController.setCenter(savedInstanceState.getParcelable(KEY_LOCATION));
128 }
129 if (savedInstanceState.containsKey(KEY_ZOOM_LEVEL)) {
130 mapController.setZoom(savedInstanceState.getDouble(KEY_ZOOM_LEVEL));
131 }
132 }
133
134 protected void setupMapView(MapView mapView, final GeoPoint pos) {
135 map = mapView;
136 map.setTileSource(TileSourceFactory.MAPNIK);
137 map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
138 map.setMultiTouchControls(true);
139 map.setTilesScaledToDpi(true);
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}