initial location service implementation, adds a map activity

This commit is contained in:
cyBerta
2019-03-06 12:16:52 +01:00
committed by B. Petersen
parent ea6144d963
commit b0534c44f6
12 changed files with 527 additions and 15 deletions
+14 -10
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms">
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms">
<uses-sdk tools:overrideLibrary="com.amulyakhare.textdrawable,com.astuetz.pagerslidingtabstrip,pl.tajchert.waitingdots,com.h6ah4i.android.multiselectlistpreferencecompat,android.support.v13,com.davemorrissey.labs.subscaleview,com.tomergoldst.tooltips,com.klinker.android.send_message,com.takisoft.colorpicker,android.support.v14.preference"/>
@@ -15,21 +15,21 @@
<uses-feature android:name="android.hardware.portrait" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
<uses-permission android:name="android.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
@@ -262,7 +262,11 @@
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<service android:name=".connect.KeepAliveService" android:enabled="true"/>
<activity android:name="com.b44t.messenger.map.MapActivity" />
<service android:name=".connect.KeepAliveService" android:enabled="true" />
<service android:name="com.b44t.messenger.geolocation.LocationBackgroundService" />
<service android:name=".service.DirectShareService"
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.b44t.messenger.map.MapActivity">
<com.mapbox.mapboxsdk.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</android.support.v7.widget.LinearLayoutCompat>
+8 -1
View File
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<item android:title="@string/menu_view_profile"
android:id="@+id/menu_conversation_settings"/>
@@ -10,4 +12,9 @@
<item android:title="@string/menu_all_media"
android:id="@+id/menu_view_media" />
<item android:id="@+id/menu_show_map"
android:title="@string/menu_show_map"
android:icon="?menu_map_icon"
app:showAsAction="ifRoom" />
</menu>
+1
View File
@@ -112,6 +112,7 @@
<attr name="menu_lock_icon_small" format="reference" />
<attr name="menu_trash_icon" format="reference" />
<attr name="menu_group_icon" format="reference" />
<attr name="menu_map_icon" format="reference" />
<attr name="menu_accept_icon" format="reference" />
<attr name="menu_copy_icon" format="reference" />
<attr name="menu_info_icon" format="reference" />
+3
View File
@@ -111,6 +111,7 @@
<string name="menu_send">Send</string>
<string name="menu_toggle_keyboard">Toggle emoji keyboard</string>
<string name="menu_edit_group">Edit group</string>
<string name="menu_show_map">Show map</string>
<string name="menu_archive_chat">Archive chat</string>
<string name="menu_unarchive_chat">Unarchive chat</string>
<string name="menu_add_attachment">Add attachment</string>
@@ -606,6 +607,7 @@
<string name="menu.view.developer.open.log.folder">Open Log Folder</string>
<string name="menu.view.developer.open.current.log.file">Open Current Logfile</string>
<string name="user_location_permission_explanation">Delta Chat needs the location permission in order to show and share your location.</string>
<!-- deprecated on android, please remove if unused on desktop -->
<!-- see qrscan_ask_join_group -->
@@ -619,4 +621,5 @@
<!-- see qrshow_join_group_hint -->
<string name="qrshow_join_verified_group_hint">Scan this to join the group \"%1$s\".</string>
</resources>
+2
View File
@@ -186,6 +186,7 @@
<item name="menu_new_conversation_icon">@drawable/ic_add_white_24dp</item>
<item name="menu_group_icon">@drawable/ic_group_white_24dp</item>
<item name="menu_map_icon">@android:drawable/ic_dialog_map</item>
<item name="menu_search_icon">@drawable/ic_search_white_24dp</item>
<item name="menu_popup_expand">@drawable/ic_launch_white_24dp</item>
<item name="menu_unlock_icon">@drawable/ic_unlocked_white_24dp</item>
@@ -331,6 +332,7 @@
<item name="menu_new_conversation_icon">@drawable/ic_add_white_24dp</item>
<item name="menu_group_icon">@drawable/ic_group_white_24dp</item>
<item name="menu_map_icon">@android:drawable/ic_dialog_map</item>
<item name="menu_search_icon">@drawable/ic_search_white_24dp</item>
<item name="menu_popup_expand">@drawable/ic_launch_white_24dp</item>
<item name="menu_unlock_icon">@drawable/ic_unlocked_white_24dp</item>
@@ -0,0 +1,43 @@
package com.b44t.messenger.geolocation;
import android.location.Location;
import java.util.Observable;
/**
* Created by cyberta on 06.03.19.
*/
public class DcLocation extends Observable {
private Location lastLocation;
private static DcLocation instance;
private DcLocation() {
lastLocation = new Location("?");
}
public static DcLocation getInstance() {
if (instance == null) {
instance = new DcLocation();
}
return instance;
}
public Location getLastLocation() {
return instance.lastLocation;
}
void updateLocation(Location location) {
lastLocation = location;
instance.setChanged();
instance.notifyObservers();
}
void reset() {
updateLocation(new Location("?"));
}
}
@@ -0,0 +1,85 @@
package com.b44t.messenger.geolocation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.location.Location;
import android.os.IBinder;
import android.util.Log;
import java.util.Observable;
import java.util.Observer;
import static android.content.Context.BIND_AUTO_CREATE;
/**
* Created by cyberta on 06.03.19.
*/
public class DcLocationManager implements Observer {
private static final String TAG = DcLocationManager.class.getSimpleName();
private LocationBackgroundService.LocationBackgroundServiceBinder serviceBinder;
private Context context;
private DcLocation dcLocation = DcLocation.getInstance();
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
serviceBinder = (LocationBackgroundService.LocationBackgroundServiceBinder) service;
startLocationEngine();
}
@Override
public void onServiceDisconnected(ComponentName name) {
serviceBinder = null;
}
};
public DcLocationManager(Context context) {
this.context = context.getApplicationContext();
DcLocation.getInstance().addObserver(this);
}
private void initializeLocationEngine() {
Intent intent = new Intent(context.getApplicationContext(), LocationBackgroundService.class);
context.bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
public void startLocationEngine() {
if (serviceBinder == null) {
initializeLocationEngine();
}
}
public void stopLocationEngine() {
if (serviceBinder == null) {
return;
}
serviceBinder.stop();
}
public void shareLastLocation() {
if (serviceBinder == null) {
initializeLocationEngine();
return;
}
Location location = dcLocation.getLastLocation();
Log.d(TAG, "share lastLocation: " + location.getLatitude() + ", " + location.getLongitude());
//TODO: implement me! write share location message
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof DcLocation) {
dcLocation = (DcLocation) o;
writeDcLocationUpdateMessage();
}
}
private void writeDcLocationUpdateMessage() {
//TODO: implement me!
}
}
@@ -0,0 +1,193 @@
package com.b44t.messenger.geolocation;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
/**
* Created by cyberta on 06.03.19.
*/
public class LocationBackgroundService extends Service {
private static final int TIMEOUT = 1000 * 30;
private static final String TAG = LocationBackgroundService.class.getSimpleName();
private LocationManager locationManager = null;
private static final int LOCATION_INTERVAL = 1000;
private static final float LOCATION_DISTANCE = 20f;
ServiceLocationListener locationListener;
private final IBinder mBinder = new LocationBackgroundServiceBinder();
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
return super.bindService(service, conn, flags);
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onCreate() {
locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
if (locationManager == null) {
Log.e(TAG, "Unable to initialize location service");
return;
}
locationListener = new ServiceLocationListener();
requestLocationUpdate(LocationManager.NETWORK_PROVIDER);
requestLocationUpdate(LocationManager.GPS_PROVIDER);
initialLocationUpdate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if (locationManager == null) {
return;
}
try {
locationManager.removeUpdates(locationListener);
} catch (Exception ex) {
Log.i(TAG, "fail to remove location listners, ignore", ex);
}
}
private void requestLocationUpdate(String provider) {
try {
locationManager.requestLocationUpdates(
provider, LOCATION_INTERVAL, LOCATION_DISTANCE,
locationListener);
} catch (SecurityException | IllegalArgumentException ex) {
Log.e(TAG, String.format("Unable to request %s provider based location updates.", provider), ex);
}
}
private void initialLocationUpdate() {
try {
Location networkLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
Location gpsLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
locationListener.onLocationChanged(networkLocation);
locationListener.onLocationChanged(gpsLocation);
} catch (NullPointerException | SecurityException e) {
e.printStackTrace();
}
}
class LocationBackgroundServiceBinder extends Binder {
LocationBackgroundServiceBinder getService() {
return LocationBackgroundServiceBinder.this;
}
void stop() {
DcLocation.getInstance().reset();
stopSelf();
}
}
private class ServiceLocationListener implements LocationListener {
@Override
public void onLocationChanged(Location location) {
Log.e(TAG, "onLocationChanged: " + location);
if (isBetterLocation(location, DcLocation.getInstance().getLastLocation())) {
DcLocation.getInstance().updateLocation(location);
}
}
@Override
public void onProviderDisabled(String provider) {
Log.e(TAG, "onProviderDisabled: " + provider);
}
@Override
public void onProviderEnabled(String provider) {
Log.e(TAG, "onProviderEnabled: " + provider);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.e(TAG, "onStatusChanged: " + provider);
}
/** https://developer.android.com/guide/topics/location/strategies
* Determines whether one Location reading is better than the current Location fix
* @param location The new Location that you want to evaluate
* @param currentBestLocation The current Location fix, to which you want to compare the new one
*/
private boolean isBetterLocation(Location location, Location currentBestLocation) {
if (currentBestLocation == null) {
// A new location is always better than no location
return true;
}
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TIMEOUT;
boolean isSignificantlyOlder = timeDelta < -TIMEOUT;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return true;
// If the new location is more than two minutes older, it must be worse
} else if (isSignificantlyOlder) {
return false;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(),
currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and accuracy
if (isMoreAccurate) {
return true;
} else if (isNewer && !isLessAccurate) {
return true;
} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
return true;
}
return false;
}
/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) {
return provider2 == null;
}
return provider1.equals(provider2);
}
}
}
+150
View File
@@ -0,0 +1,150 @@
package com.b44t.messenger.map;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.util.Log;
import android.widget.Toast;
import com.b44t.messenger.geolocation.DcLocation;
import com.b44t.messenger.geolocation.DcLocationManager;
import com.mapbox.android.core.permissions.PermissionsListener;
import com.mapbox.android.core.permissions.PermissionsManager;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Style;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.BaseActivity;
import org.thoughtcrime.securesms.R;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
public class MapActivity extends BaseActivity implements PermissionsListener, Observer {
private static final String TAG = MapActivity.class.getSimpleName();
private MapView mapView;
private PermissionsManager permissionsManager;
private DcLocation dcLocation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
mapView = findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(mapboxMap -> mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> {
mapboxMap.setCameraPosition(new CameraPosition.Builder()
.target(new LatLng(53.5505D, 10.001D))
.zoom(12)
.build());
mapboxMap.getUiSettings().setLogoEnabled(false);
mapboxMap.getUiSettings().setAttributionEnabled(false);
permissionsManager = new PermissionsManager(this);
if (PermissionsManager.areLocationPermissionsGranted(this)) {
showDeviceLocation();
} else {
permissionsManager = new PermissionsManager(this);
permissionsManager.requestLocationPermissions(this);
}
}));
dcLocation = DcLocation.getInstance();
}
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
DcLocation.getInstance().addObserver(this);
}
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
DcLocation.getInstance().deleteObserver(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
mapView.onDestroy();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
@Override
public void onBackPressed() {
super.onBackPressed();
//FIXME: only for testing --v
DcLocationManager locationManager = ApplicationContext.getInstance(this).dcLocationManager;
locationManager.stopLocationEngine();
}
//Android SDK callback for the result from requesting permissions
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
//map sdk callbacks for the result from requesting permissions
@Override
public void onPermissionResult(boolean granted) {
if (granted) {
showDeviceLocation();
} else {
Toast.makeText(this, R.string.user_location_permission_explanation, Toast.LENGTH_LONG).show();
}
}
@Override
public void onExplanationNeeded(List<String> permissionsToExplain) {
// no explanation text before the permission pop up is shown
}
private void showDeviceLocation() {
DcLocationManager locationManager = ApplicationContext.getInstance(this).dcLocationManager;
locationManager.startLocationEngine();
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof DcLocation) {
this.dcLocation = (DcLocation) o;
Log.d(TAG, "show marker on map: " + dcLocation.getLastLocation().getLatitude() + ", " + dcLocation.getLastLocation().getLongitude());
}
}
}
@@ -26,6 +26,7 @@ import android.support.annotation.NonNull;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEventCenter;
import com.b44t.messenger.geolocation.DcLocationManager;
import com.mapbox.mapboxsdk.Mapbox;
import org.thoughtcrime.securesms.connect.ApplicationDcContext;
@@ -50,11 +51,10 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
private static final String TAG = ApplicationContext.class.getName();
private JobManager jobManager;
private volatile boolean isAppVisible;
public ApplicationDcContext dcContext;
public DcLocationManager dcLocationManager;
private JobManager jobManager;
private volatile boolean isAppVisible;
public static ApplicationContext getInstance(Context context) {
return (ApplicationContext)context.getApplicationContext();
@@ -73,6 +73,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
initializeIncomingMessageNotifier();
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
Mapbox.getInstance(getApplicationContext(), BuildConfig.MAP_ACCESS_TOKEN);
dcLocationManager = new DcLocationManager(this);
}
@Override
@@ -63,6 +63,7 @@ import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEventCenter;
import com.b44t.messenger.DcMsg;
import com.b44t.messenger.map.MapActivity;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.audio.AudioRecorder;
@@ -446,6 +447,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
case R.id.menu_mute_notifications: handleMuteNotifications(); return true;
case R.id.menu_unmute_notifications: handleUnmuteNotifications(); return true;
case R.id.menu_conversation_settings: handleConversationSettings(); return true;
case R.id.menu_show_map: handleShowMap(); return true;
case android.R.id.home: handleReturnToConversationList(); return true;
}
@@ -471,6 +473,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
//////// Event Handlers
private void handleShowMap() {
Intent intent = new Intent(this, MapActivity.class);
startActivity(intent);
}
private void handleReturnToConversationList() {
Intent intent = new Intent(this, (archived ? ConversationListArchiveActivity.class : ConversationListActivity.class));
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);