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.util.GeoPoint;
28import org.osmdroid.views.CustomZoomButtonsController;
29import org.osmdroid.views.MapView;
30import org.osmdroid.views.overlay.Overlay;
31
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 @Override
76 protected void onCreate(final Bundle savedInstanceState) {
77 super.onCreate(savedInstanceState);
78 final Context ctx = getApplicationContext();
79 setTheme(ThemeHelper.find(this));
80
81 final PackageManager packageManager = ctx.getPackageManager();
82 hasLocationFeature = packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION) ||
83 packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS) ||
84 packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_NETWORK);
85 this.locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
86 this.marker_icon = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.marker);
87
88 // Ask for location permissions if location services are enabled and we're
89 // just starting the activity (we don't want to keep pestering them on every
90 // screen rotation or if there's no point because it's disabled anyways).
91 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && savedInstanceState == null) {
92 requestPermissions(REQUEST_CODE_CREATE);
93 }
94
95 final IConfigurationProvider config = Configuration.getInstance();
96 config.load(ctx, getPreferences());
97 config.setUserAgentValue(BuildConfig.APPLICATION_ID + "/" + BuildConfig.VERSION_CODE);
98 if (QuickConversationsService.isConversations() && getBooleanPreference("use_tor", R.bool.use_tor)) {
99 try {
100 config.setHttpProxy(HttpConnectionManager.getProxy());
101 } catch (IOException e) {
102 throw new RuntimeException("Unable to configure proxy");
103 }
104 }
105 }
106
107 @Override
108 protected void onSaveInstanceState(@NonNull final Bundle outState) {
109 super.onSaveInstanceState(outState);
110
111 final IGeoPoint center = map.getMapCenter();
112 outState.putParcelable(KEY_LOCATION, new GeoPoint(
113 center.getLatitude(),
114 center.getLongitude()
115 ));
116 outState.putDouble(KEY_ZOOM_LEVEL, map.getZoomLevelDouble());
117 }
118
119 @Override
120 protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
121 super.onRestoreInstanceState(savedInstanceState);
122
123 if (savedInstanceState.containsKey(KEY_LOCATION)) {
124 mapController.setCenter(savedInstanceState.getParcelable(KEY_LOCATION));
125 }
126 if (savedInstanceState.containsKey(KEY_ZOOM_LEVEL)) {
127 mapController.setZoom(savedInstanceState.getDouble(KEY_ZOOM_LEVEL));
128 }
129 }
130
131 protected void setupMapView(MapView mapView, final GeoPoint pos) {
132 map = mapView;
133 map.setTileSource(TileSourceFactory.MAPNIK);
134 map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
135 map.setMultiTouchControls(true);
136 map.setTilesScaledToDpi(false);
137 mapController = map.getController();
138 mapController.setZoom(Config.Map.INITIAL_ZOOM_LEVEL);
139 mapController.setCenter(pos);
140 }
141
142 protected void gotoLoc() {
143 gotoLoc(map.getZoomLevelDouble() == Config.Map.INITIAL_ZOOM_LEVEL);
144 }
145
146 protected abstract void gotoLoc(final boolean setZoomLevel);
147
148 protected abstract void setMyLoc(final Location location);
149
150 protected void requestLocationUpdates() {
151 if (!hasLocationFeature || locationManager == null) {
152 return;
153 }
154
155 Log.d(Config.LOGTAG, "Requesting location updates...");
156 final Location lastKnownLocationGps;
157 final Location lastKnownLocationNetwork;
158
159 try {
160 if (locationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER)) {
161 lastKnownLocationGps = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
162
163 if (lastKnownLocationGps != null) {
164 setMyLoc(lastKnownLocationGps);
165 }
166 locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, Config.Map.LOCATION_FIX_TIME_DELTA,
167 Config.Map.LOCATION_FIX_SPACE_DELTA, this);
168 } else {
169 lastKnownLocationGps = null;
170 }
171
172 if (locationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER)) {
173 lastKnownLocationNetwork = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
174 if (lastKnownLocationNetwork != null && LocationHelper.isBetterLocation(lastKnownLocationNetwork,
175 lastKnownLocationGps)) {
176 setMyLoc(lastKnownLocationNetwork);
177 }
178 locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, Config.Map.LOCATION_FIX_TIME_DELTA,
179 Config.Map.LOCATION_FIX_SPACE_DELTA, this);
180 }
181
182 // If something else is also querying for location more frequently than we are, the battery is already being
183 // drained. Go ahead and use the existing locations as often as we can get them.
184 if (locationManager.getAllProviders().contains(LocationManager.PASSIVE_PROVIDER)) {
185 locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this);
186 }
187 } catch (final SecurityException ignored) {
188 // Do nothing if the users device has no location providers.
189 }
190 }
191
192 protected void pauseLocationUpdates() throws SecurityException {
193 if (locationManager != null) {
194 locationManager.removeUpdates(this);
195 }
196 }
197
198 @Override
199 public boolean onOptionsItemSelected(final MenuItem item) {
200 switch (item.getItemId()) {
201 case android.R.id.home:
202 finish();
203 return true;
204 }
205 return super.onOptionsItemSelected(item);
206 }
207
208 @Override
209 protected void onPause() {
210 super.onPause();
211 Configuration.getInstance().save(this, getPreferences());
212 map.onPause();
213 try {
214 pauseLocationUpdates();
215 } catch (final SecurityException ignored) {
216 }
217 }
218
219 protected abstract void updateUi();
220
221 protected boolean mapAtInitialLoc() {
222 return map.getZoomLevelDouble() == Config.Map.INITIAL_ZOOM_LEVEL;
223 }
224
225 @Override
226 protected void onResume() {
227 super.onResume();
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}