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