[ShareLocation] Added background service functionality
Now the sharing of the location should also be working from the background.
This commit is contained in:
@@ -39,6 +39,7 @@ dependencies {
|
|||||||
|
|
||||||
//Google Maps SDK
|
//Google Maps SDK
|
||||||
implementation 'com.google.android.gms:play-services-maps:19.1.0'
|
implementation 'com.google.android.gms:play-services-maps:19.1.0'
|
||||||
|
implementation 'com.google.android.gms:play-services-location:21.3.0'
|
||||||
|
|
||||||
// Glide
|
// Glide
|
||||||
implementation 'com.github.bumptech.glide:glide:4.16.0' // Check for the latest version
|
implementation 'com.github.bumptech.glide:glide:4.16.0' // Check for the latest version
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_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.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
@@ -39,6 +40,9 @@
|
|||||||
android:name=".workers.SleepTimerHelper"
|
android:name=".workers.SleepTimerHelper"
|
||||||
android:foregroundServiceType="specialUse" />
|
android:foregroundServiceType="specialUse" />
|
||||||
|
|
||||||
|
<service android:name=".workers.ShareLocationBackgroundWorker"
|
||||||
|
android:foregroundServiceType="location" />
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name="com.aldo.apps.familyhelpers.DoerflingerHelpersDeviceAdminReceiver"
|
android:name="com.aldo.apps.familyhelpers.DoerflingerHelpersDeviceAdminReceiver"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -51,14 +55,6 @@
|
|||||||
<action android:name="android.app.action.DEVICE_ADMIN_DISABLED" />
|
<action android:name="android.app.action.DEVICE_ADMIN_DISABLED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name=".utils.CancelLocationSharingReceiver"
|
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="com.aldo.apps.familyhelpers.CANCEL_LOCATION_SHARING" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -8,7 +8,7 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
public class DoerflingerHelpersDeviceAdminReceiver extends DeviceAdminReceiver {
|
public class DoerflingerHelpersDeviceAdminReceiver extends DeviceAdminReceiver {
|
||||||
@Override
|
@Override
|
||||||
public void onEnabled(@NonNull final Context context, @NonNull final Intent intent) {
|
public void onEnabled(@NonNull final Context context, @NonNull final Intent intent) {
|
||||||
// Called when the app is enabled as a device administrator.
|
// Called when the app is enabled as a device administrator.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.aldo.apps.familyhelpers;
|
package com.aldo.apps.familyhelpers;
|
||||||
|
|
||||||
import static android.Manifest.permission.POST_NOTIFICATIONS;
|
import static android.Manifest.permission.POST_NOTIFICATIONS;
|
||||||
|
|
||||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SIGN_IN_PROVIDERS;
|
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SIGN_IN_PROVIDERS;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -20,8 +19,8 @@ import androidx.core.content.ContextCompat;
|
|||||||
import com.aldo.apps.familyhelpers.ui.HelperGroupTile;
|
import com.aldo.apps.familyhelpers.ui.HelperGroupTile;
|
||||||
import com.aldo.apps.familyhelpers.ui.SleepTimerPopup;
|
import com.aldo.apps.familyhelpers.ui.SleepTimerPopup;
|
||||||
import com.aldo.apps.familyhelpers.utils.DevicePolicyManagerHelper;
|
import com.aldo.apps.familyhelpers.utils.DevicePolicyManagerHelper;
|
||||||
import com.aldo.apps.familyhelpers.workers.LocationHelper;
|
|
||||||
import com.aldo.apps.familyhelpers.workers.DatabaseHelper;
|
import com.aldo.apps.familyhelpers.workers.DatabaseHelper;
|
||||||
|
import com.aldo.apps.familyhelpers.workers.LocationHelper;
|
||||||
import com.firebase.ui.auth.AuthUI;
|
import com.firebase.ui.auth.AuthUI;
|
||||||
import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract;
|
import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract;
|
||||||
import com.firebase.ui.auth.IdpResponse;
|
import com.firebase.ui.auth.IdpResponse;
|
||||||
@@ -67,11 +66,10 @@ public class HelperGridActivity extends AppCompatActivity {
|
|||||||
private FirebaseUser mCurrentUser = FirebaseAuth.getInstance().getCurrentUser();
|
private FirebaseUser mCurrentUser = FirebaseAuth.getInstance().getCurrentUser();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The instance of the {@link DatabaseHelper}.
|
* The {@link ActivityResultLauncher} for the sign in of a firebase user.
|
||||||
*/
|
*/
|
||||||
private DatabaseHelper mDbHelper;
|
private final ActivityResultLauncher<Intent> mSignInLauncher =
|
||||||
|
registerForActivityResult(new FirebaseAuthUIActivityResultContract(), this::onSignInResult); /**
|
||||||
/**
|
|
||||||
* The {@link ActivityResultLauncher} to ask for the NotificationPermission.
|
* The {@link ActivityResultLauncher} to ask for the NotificationPermission.
|
||||||
*/
|
*/
|
||||||
private final ActivityResultLauncher<String> mRequestPermissionLauncher =
|
private final ActivityResultLauncher<String> mRequestPermissionLauncher =
|
||||||
@@ -87,12 +85,6 @@ public class HelperGridActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link ActivityResultLauncher} for the sign in of a firebase user.
|
|
||||||
*/
|
|
||||||
private final ActivityResultLauncher<Intent> mSignInLauncher =
|
|
||||||
registerForActivityResult(new FirebaseAuthUIActivityResultContract(), this::onSignInResult);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -108,7 +100,6 @@ public class HelperGridActivity extends AppCompatActivity {
|
|||||||
} else {
|
} else {
|
||||||
mWelcomeMessageView.setText(String.format(getString(R.string.welcome_message_placeholder),
|
mWelcomeMessageView.setText(String.format(getString(R.string.welcome_message_placeholder),
|
||||||
mCurrentUser.getDisplayName()));
|
mCurrentUser.getDisplayName()));
|
||||||
mDbHelper = new DatabaseHelper();
|
|
||||||
mLocationHelper = LocationHelper.getInstance(this);
|
mLocationHelper = LocationHelper.getInstance(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +150,13 @@ public class HelperGridActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void launchHelper() {
|
public void launchHelper() {
|
||||||
Log.d(TAG, "launchHelper: Clicked ShareLocation");
|
Log.d(TAG, "launchHelper: Clicked ShareLocation");
|
||||||
if (mLocationHelper.requestLocationPermissions(HelperGridActivity.this)) {
|
// First check if the notification permission is granted
|
||||||
|
if (!requestNotificationPermission()) {
|
||||||
|
Log.d(TAG, "launchHelper: Notifications not allowed, return...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mLocationHelper.requestLocationPermissions(HelperGridActivity.this)
|
||||||
|
&& mLocationHelper.requestBackgroundLocationPermission(HelperGridActivity.this)) {
|
||||||
Log.d(TAG, "launchHelper: Permission already granted");
|
Log.d(TAG, "launchHelper: Permission already granted");
|
||||||
final Intent intent = new Intent(HelperGridActivity.this, ShareLocationActivity.class);
|
final Intent intent = new Intent(HelperGridActivity.this, ShareLocationActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
@@ -196,14 +193,13 @@ public class HelperGridActivity extends AppCompatActivity {
|
|||||||
*
|
*
|
||||||
* @param result The result code, used to determine whether is succeeded or not.
|
* @param result The result code, used to determine whether is succeeded or not.
|
||||||
*/
|
*/
|
||||||
private void onSignInResult(final FirebaseAuthUIAuthenticationResult result) {
|
private void onSignInResult(final FirebaseAuthUIAuthenticationResult result) {
|
||||||
final IdpResponse idpResponse = result.getIdpResponse();
|
final IdpResponse idpResponse = result.getIdpResponse();
|
||||||
if (result.getResultCode() == RESULT_OK) {
|
if (result.getResultCode() == RESULT_OK) {
|
||||||
mCurrentUser = FirebaseAuth.getInstance().getCurrentUser();
|
mCurrentUser = FirebaseAuth.getInstance().getCurrentUser();
|
||||||
Log.d(TAG, "onSignInResult: Successfully logged in [" + mCurrentUser.getDisplayName() + "]");
|
Log.d(TAG, "onSignInResult: Successfully logged in [" + mCurrentUser.getDisplayName() + "]");
|
||||||
mWelcomeMessageView.setText(String.format(getString(R.string.welcome_message_placeholder),
|
mWelcomeMessageView.setText(String.format(getString(R.string.welcome_message_placeholder),
|
||||||
mCurrentUser.getDisplayName()));
|
mCurrentUser.getDisplayName()));
|
||||||
mDbHelper = new DatabaseHelper();
|
|
||||||
mLocationHelper = LocationHelper.getInstance(this);
|
mLocationHelper = LocationHelper.getInstance(this);
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "onSignInResult: Sign-In failed");
|
Log.w(TAG, "onSignInResult: Sign-In failed");
|
||||||
@@ -213,7 +209,7 @@ public class HelperGridActivity extends AppCompatActivity {
|
|||||||
Log.w(TAG, "onSignInResult: User canceled, cannot continue");
|
Log.w(TAG, "onSignInResult: User canceled, cannot continue");
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "onSignInResult: Login failed with errorCode ["
|
Log.e(TAG, "onSignInResult: Login failed with errorCode ["
|
||||||
+ idpResponse.getError().getErrorCode() + "]");
|
+ idpResponse.getError().getErrorCode() + "]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,10 +219,12 @@ public class HelperGridActivity extends AppCompatActivity {
|
|||||||
@NonNull final String[] permissions,
|
@NonNull final String[] permissions,
|
||||||
@NonNull final int[] grantResults) {
|
@NonNull final int[] grantResults) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if (mLocationHelper.handlePermissionResult(requestCode, grantResults)) {
|
if (mLocationHelper.handlePermissionResult(HelperGridActivity.this, requestCode, grantResults)) {
|
||||||
final Intent intent = new Intent(HelperGridActivity.this, ShareLocationActivity.class);
|
final Intent intent = new Intent(HelperGridActivity.this, ShareLocationActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,19 +2,16 @@ package com.aldo.apps.familyhelpers;
|
|||||||
|
|
||||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_HOME_LATITUDE;
|
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_HOME_LATITUDE;
|
||||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_HOME_LONGITUDE;
|
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_HOME_LONGITUDE;
|
||||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_MINIMUM_LOCATION_INTERVAL_METERS;
|
|
||||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_MINIMUM_LOCATION_INTERVAL_MILLIS;
|
|
||||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.METER_PER_SECOND_TO_KMH_CONVERTER;
|
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.METER_PER_SECOND_TO_KMH_CONVERTER;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.CompoundButton;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -27,6 +24,7 @@ import com.aldo.apps.familyhelpers.model.User;
|
|||||||
import com.aldo.apps.familyhelpers.ui.ActiveShareAdapter;
|
import com.aldo.apps.familyhelpers.ui.ActiveShareAdapter;
|
||||||
import com.aldo.apps.familyhelpers.workers.DatabaseHelper;
|
import com.aldo.apps.familyhelpers.workers.DatabaseHelper;
|
||||||
import com.aldo.apps.familyhelpers.workers.LocationHelper;
|
import com.aldo.apps.familyhelpers.workers.LocationHelper;
|
||||||
|
import com.aldo.apps.familyhelpers.workers.ShareLocationBackgroundWorker;
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.google.android.gms.maps.CameraUpdateFactory;
|
import com.google.android.gms.maps.CameraUpdateFactory;
|
||||||
import com.google.android.gms.maps.GoogleMap;
|
import com.google.android.gms.maps.GoogleMap;
|
||||||
@@ -40,8 +38,6 @@ import com.google.android.material.materialswitch.MaterialSwitch;
|
|||||||
import com.google.firebase.auth.FirebaseAuth;
|
import com.google.firebase.auth.FirebaseAuth;
|
||||||
import com.google.firebase.auth.FirebaseUser;
|
import com.google.firebase.auth.FirebaseUser;
|
||||||
|
|
||||||
import org.checkerframework.checker.units.qual.A;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -61,95 +57,77 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
|
|||||||
* Tag for debugging purpose.
|
* Tag for debugging purpose.
|
||||||
*/
|
*/
|
||||||
private static final String TAG = "ShareLocationActivity";
|
private static final String TAG = "ShareLocationActivity";
|
||||||
|
/**
|
||||||
|
* Map containing all markers and the corresponding ID.
|
||||||
|
*/
|
||||||
|
private final Map<String, Marker> mMarkerMap = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* {@link List} of all {@link User}s.
|
||||||
|
*/
|
||||||
|
private final List<User> mAllUsers = new ArrayList<>();
|
||||||
/**
|
/**
|
||||||
* The {@link GoogleMap} view.
|
* The {@link GoogleMap} view.
|
||||||
*/
|
*/
|
||||||
private GoogleMap mGmap;
|
private GoogleMap mGmap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link DatabaseHelper} to read and load data from.
|
* The {@link DatabaseHelper} to read and load data from.
|
||||||
*/
|
*/
|
||||||
private DatabaseHelper mDbHelper;
|
private DatabaseHelper mDbHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently logged in {@link FirebaseUser}.
|
* The currently logged in {@link FirebaseUser}.
|
||||||
*/
|
*/
|
||||||
private FirebaseUser mCurrentUser;
|
private FirebaseUser mCurrentUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the location updates to listen to.
|
* The ID of the location updates to listen to.
|
||||||
*/
|
*/
|
||||||
private String mShareId;
|
private String mShareId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InfoBox {@link ImageView} showing the user icon.
|
* The InfoBox {@link ImageView} showing the user icon.
|
||||||
*/
|
*/
|
||||||
private ImageView mInfoBoxIcon;
|
private ImageView mInfoBoxIcon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InfoBox {@link TextView} showing the title.
|
* The InfoBox {@link TextView} showing the title.
|
||||||
*/
|
*/
|
||||||
private TextView mInfoBoxTitle;
|
private TextView mInfoBoxTitle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InfoBox {@link TextView} showing the Location information.
|
* The InfoBox {@link TextView} showing the Location information.
|
||||||
*/
|
*/
|
||||||
private TextView mInfoBoxLocation;
|
private TextView mInfoBoxLocation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InfoBox {@link TextView} showing the altitude..
|
* The InfoBox {@link TextView} showing the altitude..
|
||||||
*/
|
*/
|
||||||
private TextView mInfoBoxAltitude;
|
private TextView mInfoBoxAltitude;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InfoBox {@link TextView} showing the last received speed.
|
* The InfoBox {@link TextView} showing the last received speed.
|
||||||
*/
|
*/
|
||||||
private TextView mInfoBoxSpeed;
|
private TextView mInfoBoxSpeed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InfoBox {@link TextView} showing the timestamp of the last update.
|
* The InfoBox {@link TextView} showing the timestamp of the last update.
|
||||||
*/
|
*/
|
||||||
private TextView mInfoBoxTimeStamp;
|
private TextView mInfoBoxTimeStamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Disposable} holding the subscription to the {@link LocationObject}
|
* The {@link Disposable} holding the subscription to the {@link LocationObject}
|
||||||
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
|
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
|
||||||
*/
|
*/
|
||||||
private Disposable mLocationUpdateSubscription;
|
private Disposable mLocationUpdateSubscription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Disposable} holding the subscription to the {@link String}
|
* The {@link Disposable} holding the subscription to the {@link String}
|
||||||
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to know whom to follow.
|
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to know whom to follow.
|
||||||
*/
|
*/
|
||||||
private Disposable mCurrentlyFollowingSubscription;
|
private Disposable mCurrentlyFollowingSubscription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Disposable} holding the subscription to {@link User}
|
* The {@link Disposable} holding the subscription to {@link User}
|
||||||
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
|
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
|
||||||
*/
|
*/
|
||||||
private Disposable mUserSubscription;
|
private Disposable mUserSubscription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boolean flag indicating whether the system is currently in night mode or not.
|
* Boolean flag indicating whether the system is currently in night mode or not.
|
||||||
*/
|
*/
|
||||||
private boolean mIsNightMode;
|
private boolean mIsNightMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to the Google Maps Marker, to update it's position rather than adding a new one.
|
* Reference to the Google Maps Marker, to update it's position rather than adding a new one.
|
||||||
*/
|
*/
|
||||||
private Marker mGmapsMarker;
|
private Marker mGmapsMarker;
|
||||||
|
|
||||||
/**
|
|
||||||
* Map containing all markers and the corresponding ID.
|
|
||||||
*/
|
|
||||||
private final Map<String, Marker> mMarkerMap = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link List} of all {@link User}s.
|
|
||||||
*/
|
|
||||||
private final List<User> mAllUsers = new ArrayList<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of who the user is currently following.
|
* The ID of who the user is currently following.
|
||||||
*/
|
*/
|
||||||
@@ -186,22 +164,6 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
|
|||||||
mInfoBoxTimeStamp = findViewById(R.id.share_location_info_timestamp);
|
mInfoBoxTimeStamp = findViewById(R.id.share_location_info_timestamp);
|
||||||
mNoActiveShares = findViewById(R.id.tv_no_active_shares);
|
mNoActiveShares = findViewById(R.id.tv_no_active_shares);
|
||||||
mActiveShares = findViewById(R.id.active_share_layout);
|
mActiveShares = findViewById(R.id.active_share_layout);
|
||||||
final MaterialSwitch shareLocationToggle = findViewById(R.id.switch_start_stop_sharing_location);
|
|
||||||
final LocationHelper locationHelper = LocationHelper.getInstance(this);
|
|
||||||
shareLocationToggle.setChecked(locationHelper.isCurrentlySharing());
|
|
||||||
shareLocationToggle.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
|
||||||
if (isChecked) {
|
|
||||||
locationHelper.startLocationUpdates(ShareLocationActivity.this,
|
|
||||||
DEFAULT_MINIMUM_LOCATION_INTERVAL_MILLIS,
|
|
||||||
DEFAULT_MINIMUM_LOCATION_INTERVAL_METERS)
|
|
||||||
;
|
|
||||||
} else {
|
|
||||||
locationHelper.stopLocationUpdates();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
locationHelper.getSharingStateSubject().subscribe(isSharing -> shareLocationToggle.setChecked(isSharing),
|
|
||||||
this::handleSubscriptionError);
|
|
||||||
|
|
||||||
mActiveShares.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
|
mActiveShares.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
|
||||||
mActiveShareAdapter = new ActiveShareAdapter(this);
|
mActiveShareAdapter = new ActiveShareAdapter(this);
|
||||||
@@ -225,6 +187,8 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
|
|||||||
if (mLocationUpdateSubscription != null) {
|
if (mLocationUpdateSubscription != null) {
|
||||||
mLocationUpdateSubscription.dispose();
|
mLocationUpdateSubscription.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initAndSetupShareToggle();
|
||||||
mUserSubscription = mDbHelper.getAllUsers()
|
mUserSubscription = mDbHelper.getAllUsers()
|
||||||
.subscribe(this::handleAllUsers, this::handleUserSubscriptionFailed);
|
.subscribe(this::handleAllUsers, this::handleUserSubscriptionFailed);
|
||||||
mLocationUpdateSubscription = mDbHelper.subscribeToAllLocationUpdates()
|
mLocationUpdateSubscription = mDbHelper.subscribeToAllLocationUpdates()
|
||||||
@@ -233,6 +197,28 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
|
|||||||
.subscribe(this::handleCurrentlyFollowingChanged, this::handleSubscriptionError);
|
.subscribe(this::handleCurrentlyFollowingChanged, this::handleSubscriptionError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the share switch button.
|
||||||
|
*/
|
||||||
|
private void initAndSetupShareToggle() {
|
||||||
|
final MaterialSwitch shareLocationToggle = findViewById(R.id.switch_start_stop_sharing_location);
|
||||||
|
|
||||||
|
final LocationHelper locationHelper = LocationHelper.getInstance(this);
|
||||||
|
shareLocationToggle.setChecked(locationHelper.isCurrentlySharing());
|
||||||
|
shareLocationToggle.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||||
|
if (isChecked) {
|
||||||
|
final Intent shareLocationService = new Intent(this, ShareLocationBackgroundWorker.class);
|
||||||
|
startForegroundService(shareLocationService);
|
||||||
|
} else {
|
||||||
|
final Intent stopLocationService = new Intent(this, ShareLocationBackgroundWorker.class);
|
||||||
|
stopService(stopLocationService);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
locationHelper.getSharingStateSubject().subscribe(isSharing -> shareLocationToggle.setChecked(isSharing),
|
||||||
|
this::handleSubscriptionError);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
@@ -291,11 +277,11 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removed all markers that became invalid because the sharing was stopped.
|
* Removed all markers that became invalid because the sharing was stopped.
|
||||||
*
|
*
|
||||||
* @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
|
* @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
|
||||||
*/
|
*/
|
||||||
private void removeInvalidExistingMarkers(final Map<String, LocationObject> locationObjectMap) {
|
private void removeInvalidExistingMarkers(final Map<String, LocationObject> locationObjectMap) {
|
||||||
final List<String> toBeRemovedKeys = new ArrayList<>();
|
final List<String> toBeRemovedKeys = new ArrayList<>();
|
||||||
for (Map.Entry<String, Marker> markerEntry : mMarkerMap.entrySet()) {
|
for (Map.Entry<String, Marker> markerEntry : mMarkerMap.entrySet()) {
|
||||||
|
|||||||
@@ -68,6 +68,19 @@ public class LocationObject {
|
|||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to create a {@link LocationObject} from a handed in {@link Location}.
|
||||||
|
*
|
||||||
|
* @param location The received {@link Location} from the locationManager.
|
||||||
|
* @return The created {@link LocationObject}.
|
||||||
|
*/
|
||||||
|
public static LocationObject fromLocation(final Location location) {
|
||||||
|
final FirebaseUser firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
|
||||||
|
return new LocationObject(firebaseUser.getUid(), location.getLatitude(),
|
||||||
|
location.getLongitude(), location.getAltitude(), location.getSpeed(),
|
||||||
|
location.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the unique identifier for the shared location.
|
* Returns the unique identifier for the shared location.
|
||||||
*
|
*
|
||||||
@@ -122,20 +135,6 @@ public class LocationObject {
|
|||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method to create a {@link LocationObject} from a handed in {@link Location}.
|
|
||||||
*
|
|
||||||
* @param location The received {@link Location} from the locationManager.
|
|
||||||
*
|
|
||||||
* @return The created {@link LocationObject}.
|
|
||||||
*/
|
|
||||||
public static LocationObject fromLocation(final Location location) {
|
|
||||||
final FirebaseUser firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
|
|
||||||
return new LocationObject(firebaseUser.getUid(), location.getLatitude(),
|
|
||||||
location.getLongitude(), location.getAltitude(), location.getSpeed(),
|
|
||||||
location.getTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
|||||||
@@ -55,6 +55,30 @@ public class User {
|
|||||||
this.creationDate = creationDate;
|
this.creationDate = creationDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link User} from a handed in {@link FirebaseUser}.
|
||||||
|
*
|
||||||
|
* @param firebaseUser The {@link FirebaseUser} to read the fields from.
|
||||||
|
* @return The {@link User} representation of the {@link FirebaseUser}, or null if no
|
||||||
|
* {@link FirebaseUser} is available.
|
||||||
|
*/
|
||||||
|
public static User fromFirebaseUser(final FirebaseUser firebaseUser) {
|
||||||
|
if (firebaseUser == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final String uid = firebaseUser.getUid();
|
||||||
|
final String displayName = firebaseUser.getDisplayName();
|
||||||
|
final Uri photoUrl = firebaseUser.getPhotoUrl();
|
||||||
|
final FirebaseUserMetadata metaData = firebaseUser.getMetadata();
|
||||||
|
final long creationDate;
|
||||||
|
if (metaData == null) {
|
||||||
|
creationDate = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
|
||||||
|
} else {
|
||||||
|
creationDate = metaData.getCreationTimestamp();
|
||||||
|
}
|
||||||
|
return new User(uid, displayName, photoUrl == null ? null : photoUrl.toString(), creationDate);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the unique Identifies of the user.
|
* Returns the unique Identifies of the user.
|
||||||
*
|
*
|
||||||
@@ -91,31 +115,6 @@ public class User {
|
|||||||
return creationDate;
|
return creationDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@link User} from a handed in {@link FirebaseUser}.
|
|
||||||
*
|
|
||||||
* @param firebaseUser The {@link FirebaseUser} to read the fields from.
|
|
||||||
*
|
|
||||||
* @return The {@link User} representation of the {@link FirebaseUser}, or null if no
|
|
||||||
* {@link FirebaseUser} is available.
|
|
||||||
*/
|
|
||||||
public static User fromFirebaseUser(final FirebaseUser firebaseUser) {
|
|
||||||
if (firebaseUser == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final String uid = firebaseUser.getUid();
|
|
||||||
final String displayName = firebaseUser.getDisplayName();
|
|
||||||
final Uri photoUrl = firebaseUser.getPhotoUrl();
|
|
||||||
final FirebaseUserMetadata metaData = firebaseUser.getMetadata();
|
|
||||||
final long creationDate;
|
|
||||||
if (metaData == null) {
|
|
||||||
creationDate = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
|
|
||||||
} else {
|
|
||||||
creationDate = metaData.getCreationTimestamp();
|
|
||||||
}
|
|
||||||
return new User(uid,displayName, photoUrl == null ? null : photoUrl.toString(), creationDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "User{" +
|
return "User{" +
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.aldo.apps.familyhelpers.ui;
|
package com.aldo.apps.familyhelpers.ui;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -10,7 +9,6 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.aldo.apps.familyhelpers.R;
|
import com.aldo.apps.familyhelpers.R;
|
||||||
@@ -20,20 +18,46 @@ import com.bumptech.glide.Glide;
|
|||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import de.hdodenhof.circleimageview.CircleImageView;
|
import de.hdodenhof.circleimageview.CircleImageView;
|
||||||
import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RecyclerView.Adapter} for the {@link ActiveShareAdapter.ActiveShareViewHolder} to display
|
||||||
|
* which user is currently sharing their location and to offer a selection on whom to follow.
|
||||||
|
*/
|
||||||
public class ActiveShareAdapter extends RecyclerView.Adapter<ActiveShareAdapter.ActiveShareViewHolder> {
|
public class ActiveShareAdapter extends RecyclerView.Adapter<ActiveShareAdapter.ActiveShareViewHolder> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag for debugging purposes.
|
||||||
|
*/
|
||||||
private static final String TAG = "ActiveShareAdapter";
|
private static final String TAG = "ActiveShareAdapter";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link BehaviorSubject} holding the ID of the currently selected user.
|
||||||
|
*/
|
||||||
private final BehaviorSubject<String> mCurrentlySelectedSubject = BehaviorSubject.createDefault("");
|
private final BehaviorSubject<String> mCurrentlySelectedSubject = BehaviorSubject.createDefault("");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link List} of all currently sharing {@link User}s.
|
||||||
|
*/
|
||||||
private final List<User> mSharingUserList = new ArrayList<>();
|
private final List<User> mSharingUserList = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link WeakReference} to the calling {@link Context}.
|
||||||
|
*/
|
||||||
private final WeakReference<Context> mContextRef;
|
private final WeakReference<Context> mContextRef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all {@link View}s so the focus can be cleared again if another view is touched.
|
||||||
|
*/
|
||||||
|
private final List<View> mAllItems = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C'Tor.
|
||||||
|
*
|
||||||
|
* @param context Calling context.
|
||||||
|
*/
|
||||||
public ActiveShareAdapter(final Context context) {
|
public ActiveShareAdapter(final Context context) {
|
||||||
mContextRef = new WeakReference<>(context);
|
mContextRef = new WeakReference<>(context);
|
||||||
}
|
}
|
||||||
@@ -43,6 +67,7 @@ public class ActiveShareAdapter extends RecyclerView.Adapter<ActiveShareAdapter.
|
|||||||
public ActiveShareViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
public ActiveShareViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||||
final View view = LayoutInflater.from(parent.getContext())
|
final View view = LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.active_share_view_holder, parent, false);
|
.inflate(R.layout.active_share_view_holder, parent, false);
|
||||||
|
mAllItems.add(view);
|
||||||
return new ActiveShareViewHolder(view);
|
return new ActiveShareViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,24 +83,60 @@ public class ActiveShareAdapter extends RecyclerView.Adapter<ActiveShareAdapter.
|
|||||||
return mSharingUserList.size();
|
return mSharingUserList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to apply a new List of sharing {@link User}s.
|
||||||
|
* TODO: Make it smarter.
|
||||||
|
*/
|
||||||
public void applyNewData(final List<User> newUsers) {
|
public void applyNewData(final List<User> newUsers) {
|
||||||
mSharingUserList.clear();
|
mSharingUserList.clear();
|
||||||
mSharingUserList.addAll(newUsers);
|
mSharingUserList.addAll(newUsers);
|
||||||
|
mAllItems.clear();
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link BehaviorSubject} holding the ID of the currently selected user.
|
||||||
|
*
|
||||||
|
* @return The {@link BehaviorSubject} holding the ID of the currently selected user.
|
||||||
|
*/
|
||||||
public BehaviorSubject<String> getCurrentlySelectedSubject() {
|
public BehaviorSubject<String> getCurrentlySelectedSubject() {
|
||||||
return mCurrentlySelectedSubject;
|
return mCurrentlySelectedSubject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to clear the selected state of all views, when a new one is tapped.
|
||||||
|
*/
|
||||||
|
private void clearFocusedView() {
|
||||||
|
for (final View view : mAllItems) {
|
||||||
|
view.setSelected(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link RecyclerView.ViewHolder} implementation for the {@link ActiveShareViewHolder}.
|
||||||
|
*/
|
||||||
public class ActiveShareViewHolder extends RecyclerView.ViewHolder {
|
public class ActiveShareViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
private CircleImageView mProfilePicture;
|
/**
|
||||||
|
* The {@link CircleImageView} holding the profile picture of the user.
|
||||||
|
*/
|
||||||
|
private final CircleImageView mProfilePicture;
|
||||||
|
|
||||||
private TextView mName;
|
/**
|
||||||
|
* The {@link TextView} showing the name of the user.
|
||||||
|
*/
|
||||||
|
private final TextView mName;
|
||||||
|
|
||||||
private View mItemView;
|
/**
|
||||||
|
* The overall {@link View}.
|
||||||
|
*/
|
||||||
|
private final View mItemView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C'Tor.
|
||||||
|
*
|
||||||
|
* @param itemView The already inflated itemView.
|
||||||
|
*/
|
||||||
public ActiveShareViewHolder(@NonNull final View itemView) {
|
public ActiveShareViewHolder(@NonNull final View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
mProfilePicture = itemView.findViewById(R.id.active_share_profile_picture);
|
mProfilePicture = itemView.findViewById(R.id.active_share_profile_picture);
|
||||||
@@ -83,6 +144,11 @@ public class ActiveShareAdapter extends RecyclerView.Adapter<ActiveShareAdapter.
|
|||||||
mItemView = itemView;
|
mItemView = itemView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to apply userData to a View.
|
||||||
|
*
|
||||||
|
* @param user The {@link User} to be attached.
|
||||||
|
*/
|
||||||
public void setUserData(final User user) {
|
public void setUserData(final User user) {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
Log.w(TAG, "setUserData: Skip update, no valid update yet");
|
Log.w(TAG, "setUserData: Skip update, no valid update yet");
|
||||||
@@ -90,7 +156,15 @@ public class ActiveShareAdapter extends RecyclerView.Adapter<ActiveShareAdapter.
|
|||||||
}
|
}
|
||||||
mItemView.setOnClickListener(v -> {
|
mItemView.setOnClickListener(v -> {
|
||||||
Log.d(TAG, "setUserData: Clicked on [" + user + "]");
|
Log.d(TAG, "setUserData: Clicked on [" + user + "]");
|
||||||
mCurrentlySelectedSubject.onNext(user.getuId());
|
if (mItemView.isSelected()) {
|
||||||
|
mCurrentlySelectedSubject.onNext("");
|
||||||
|
clearFocusedView();
|
||||||
|
mItemView.setSelected(false);
|
||||||
|
} else {
|
||||||
|
mCurrentlySelectedSubject.onNext(user.getuId());
|
||||||
|
clearFocusedView();
|
||||||
|
mItemView.setSelected(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
mName.setText(user.getDisplayName());
|
mName.setText(user.getDisplayName());
|
||||||
final Context context = mContextRef.get();
|
final Context context = mContextRef.get();
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
package com.aldo.apps.familyhelpers.utils;
|
|
||||||
|
|
||||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SHARE_LOCATION_CANCEL_ACTION;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.aldo.apps.familyhelpers.workers.LocationHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcast received to cancel an ongoing sharing of location.
|
|
||||||
*/
|
|
||||||
public class CancelLocationSharingReceiver extends BroadcastReceiver {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tag for debugging purpose.
|
|
||||||
*/
|
|
||||||
private static final String TAG = "CancelLocationSharingRe";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Empty C'tor.
|
|
||||||
*/
|
|
||||||
public CancelLocationSharingReceiver() {
|
|
||||||
// Empty C'tor.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(final Context context, final Intent intent) {
|
|
||||||
if (intent != null && SHARE_LOCATION_CANCEL_ACTION.equalsIgnoreCase(intent.getAction())) {
|
|
||||||
Log.d(TAG, "onReceive: Notification cancelled, cancel location sharing");
|
|
||||||
final LocationHelper locationHelper = LocationHelper.getInstance(context);
|
|
||||||
locationHelper.stopLocationUpdates();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -54,7 +54,6 @@ public final class DevicePolicyManagerHelper {
|
|||||||
* Returns the singleton instance of the {@link DevicePolicyManagerHelper}.
|
* Returns the singleton instance of the {@link DevicePolicyManagerHelper}.
|
||||||
*
|
*
|
||||||
* @param context The {@link Context} from where this was called.
|
* @param context The {@link Context} from where this was called.
|
||||||
*
|
|
||||||
* @return the singleton instance of the {@link DevicePolicyManagerHelper}.
|
* @return the singleton instance of the {@link DevicePolicyManagerHelper}.
|
||||||
*/
|
*/
|
||||||
public static DevicePolicyManagerHelper getInstance(final Context context) {
|
public static DevicePolicyManagerHelper getInstance(final Context context) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.aldo.apps.familyhelpers.utils;
|
|||||||
import com.firebase.ui.auth.AuthUI;
|
import com.firebase.ui.auth.AuthUI;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,88 +11,75 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public final class GlobalConstants {
|
public final class GlobalConstants {
|
||||||
|
|
||||||
/**
|
|
||||||
* Private C'tor to prevent instantiation.
|
|
||||||
*/
|
|
||||||
private GlobalConstants() {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the NotificationChannel for the SleepTimer Notification.
|
* ID of the NotificationChannel for the SleepTimer Notification.
|
||||||
*/
|
*/
|
||||||
public static final String SLEEP_TIMER_CHANNEL_ID = "CountdownChannel";
|
public static final String SLEEP_TIMER_CHANNEL_ID = "CountdownChannel";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the NotificationChannel for the ShareLocation Notification.
|
* ID of the NotificationChannel for the ShareLocation Notification.
|
||||||
*/
|
*/
|
||||||
public static final String SHARE_LOCATION_CHANNEL_ID = "LocationShareChannel";
|
public static final String SHARE_LOCATION_CHANNEL_ID = "LocationShareChannel";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factor to calculate seconds from milliseconds and vice versa.
|
* Factor to calculate seconds from milliseconds and vice versa.
|
||||||
*/
|
*/
|
||||||
public static final int ONE_SECOND_IN_MILLIS = 1000;
|
public static final int ONE_SECOND_IN_MILLIS = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factor to calculate hours from minutes and vice versa.
|
* Factor to calculate hours from minutes and vice versa.
|
||||||
*/
|
*/
|
||||||
public static final int ONE_HOUR_IN_MINUTES = 60;
|
public static final int ONE_HOUR_IN_MINUTES = 60;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The NotificationID of the SleepTimer notification.
|
* The NotificationID of the SleepTimer notification.
|
||||||
*/
|
*/
|
||||||
public static final int SLEEP_TIMER_NOTIFICATION_ID = 1;
|
public static final int SLEEP_TIMER_NOTIFICATION_ID = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The NotificationID of the ShareLocation notification.
|
* The NotificationID of the ShareLocation notification.
|
||||||
*/
|
*/
|
||||||
public static final int SHARE_LOCATION_NOTIFICATION_ID = 2;
|
public static final int SHARE_LOCATION_NOTIFICATION_ID = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The action to be invoked when the sharing of location should be cancelled.
|
* The action to be invoked when the sharing of location should be cancelled.
|
||||||
*/
|
*/
|
||||||
public static final String SHARE_LOCATION_CANCEL_ACTION = "com.aldo.apps.familyhelpers.CANCEL_LOCATION_SHARING";
|
public static final String SHARE_LOCATION_CANCEL_ACTION = "com.aldo.apps.familyhelpers.CANCEL_LOCATION_SHARING";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The key of the extra to be applied to the starting intent of the sleepTimer service,
|
* The key of the extra to be applied to the starting intent of the sleepTimer service,
|
||||||
* holding the initial duration in millis.
|
* holding the initial duration in millis.
|
||||||
*/
|
*/
|
||||||
public static final String SLEEP_TIMER_DURATION_MILLIS_EXTRA = "sleep_timer_duration";
|
public static final String SLEEP_TIMER_DURATION_MILLIS_EXTRA = "sleep_timer_duration";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action to cancel an ongoing sleep timer.
|
* Action to cancel an ongoing sleep timer.
|
||||||
*/
|
*/
|
||||||
public static final String SLEEP_TIMER_CANCEL_ACTION = "SLEEP_TIMER_CANCEL";
|
public static final String SLEEP_TIMER_CANCEL_ACTION = "SLEEP_TIMER_CANCEL";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default minimum time interval between two location updates.
|
* Default minimum time interval between two location updates.
|
||||||
* Currently set to 5 Seconds.
|
* Currently set to 5 Seconds.
|
||||||
*/
|
*/
|
||||||
public static final int DEFAULT_MINIMUM_LOCATION_INTERVAL_MILLIS = 5000;
|
public static final int DEFAULT_MINIMUM_LOCATION_INTERVAL_MILLIS = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default minimum distance interval between two location updates.
|
* Default minimum distance interval between two location updates.
|
||||||
* Currently set to 5 Meters.
|
* Currently set to 5 Meters.
|
||||||
*/
|
*/
|
||||||
public static final int DEFAULT_MINIMUM_LOCATION_INTERVAL_METERS = 5;
|
public static final int DEFAULT_MINIMUM_LOCATION_INTERVAL_METERS = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Conversion constants to convert m/s into km/h
|
* Conversion constants to convert m/s into km/h
|
||||||
*/
|
*/
|
||||||
public static final double METER_PER_SECOND_TO_KMH_CONVERTER = 3.6;
|
public static final double METER_PER_SECOND_TO_KMH_CONVERTER = 3.6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Latitude of the default home (== Blaustein)
|
* Latitude of the default home (== Blaustein)
|
||||||
*/
|
*/
|
||||||
public static final double DEFAULT_HOME_LATITUDE = 48.41965746149261;
|
public static final double DEFAULT_HOME_LATITUDE = 48.41965746149261;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Longitude of the default home (== Blaustein)
|
* Longitude of the default home (== Blaustein)
|
||||||
*/
|
*/
|
||||||
public static final double DEFAULT_HOME_LONGITUDE = 9.909289365473684;
|
public static final double DEFAULT_HOME_LONGITUDE = 9.909289365473684;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of available Firebase signIn/Login providers.
|
* List of available Firebase signIn/Login providers.
|
||||||
*/
|
*/
|
||||||
public static final List<AuthUI.IdpConfig> SIGN_IN_PROVIDERS = Arrays.asList(
|
public static final List<AuthUI.IdpConfig> SIGN_IN_PROVIDERS = Collections.singletonList(
|
||||||
new AuthUI.IdpConfig.GoogleBuilder().build()
|
new AuthUI.IdpConfig.GoogleBuilder().build()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private C'tor to prevent instantiation.
|
||||||
|
*/
|
||||||
|
private GlobalConstants() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,16 @@ package com.aldo.apps.familyhelpers.workers;
|
|||||||
|
|
||||||
import static com.aldo.apps.familyhelpers.utils.DatabaseConstants.DB_COLL_LOCATION;
|
import static com.aldo.apps.familyhelpers.utils.DatabaseConstants.DB_COLL_LOCATION;
|
||||||
import static com.aldo.apps.familyhelpers.utils.DatabaseConstants.DB_COLL_USERS;
|
import static com.aldo.apps.familyhelpers.utils.DatabaseConstants.DB_COLL_USERS;
|
||||||
import static com.aldo.apps.familyhelpers.utils.DatabaseConstants.DB_DOC_USER_ID;
|
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.aldo.apps.familyhelpers.model.LocationObject;
|
import com.aldo.apps.familyhelpers.model.LocationObject;
|
||||||
import com.aldo.apps.familyhelpers.model.User;
|
import com.aldo.apps.familyhelpers.model.User;
|
||||||
import com.google.firebase.auth.FirebaseAuth;
|
import com.google.firebase.auth.FirebaseAuth;
|
||||||
import com.google.firebase.auth.FirebaseUser;
|
import com.google.firebase.auth.FirebaseUser;
|
||||||
import com.google.firebase.firestore.DocumentSnapshot;
|
import com.google.firebase.firestore.DocumentSnapshot;
|
||||||
import com.google.firebase.firestore.EventListener;
|
|
||||||
import com.google.firebase.firestore.FirebaseFirestore;
|
import com.google.firebase.firestore.FirebaseFirestore;
|
||||||
import com.google.firebase.firestore.FirebaseFirestoreException;
|
|
||||||
import com.google.firebase.firestore.ListenerRegistration;
|
import com.google.firebase.firestore.ListenerRegistration;
|
||||||
import com.google.firebase.firestore.QuerySnapshot;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -64,7 +58,7 @@ public class DatabaseHelper {
|
|||||||
/**
|
/**
|
||||||
* {@link Map} containing all users.
|
* {@link Map} containing all users.
|
||||||
*/
|
*/
|
||||||
private Map<String, User> mAllUserMap = new HashMap<>();
|
private final Map<String, User> mAllUserMap = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C'tor.
|
* C'tor.
|
||||||
@@ -119,6 +113,13 @@ public class DatabaseHelper {
|
|||||||
return mAllUsers;
|
return mAllUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link User} object for the given ID.
|
||||||
|
*
|
||||||
|
* @param userId The identifier of the wanted user.
|
||||||
|
*
|
||||||
|
* @return The identified user or null of not existing.
|
||||||
|
*/
|
||||||
public User getUserForId(final String userId) {
|
public User getUserForId(final String userId) {
|
||||||
Log.d(TAG, "getUserForId() called with: userId = [" + userId + "]");
|
Log.d(TAG, "getUserForId() called with: userId = [" + userId + "]");
|
||||||
if (mAllUserMap.containsKey(userId)) {
|
if (mAllUserMap.containsKey(userId)) {
|
||||||
@@ -162,25 +163,25 @@ public class DatabaseHelper {
|
|||||||
mLocationUpdateListener = mDatabase.collection(DB_COLL_LOCATION)
|
mLocationUpdateListener = mDatabase.collection(DB_COLL_LOCATION)
|
||||||
.document(shareId)
|
.document(shareId)
|
||||||
.addSnapshotListener((value, error) -> {
|
.addSnapshotListener((value, error) -> {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
Log.d(TAG, "onEvent: Location was deleted");
|
Log.d(TAG, "onEvent: Location was deleted");
|
||||||
mObservedLocation.onComplete();
|
mObservedLocation.onComplete();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final LocationObject updatedLocation = value.toObject(LocationObject.class);
|
final LocationObject updatedLocation = value.toObject(LocationObject.class);
|
||||||
if (updatedLocation == null) {
|
if (updatedLocation == null) {
|
||||||
Log.w(TAG, "onEvent: Error while parsing, ignore");
|
Log.w(TAG, "onEvent: Error while parsing, ignore");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mObservedLocation.onNext(updatedLocation);
|
mObservedLocation.onNext(updatedLocation);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to start listening for all available location sharings in progress.
|
* Helper method to start listening for all available location sharings in progress.
|
||||||
*
|
*
|
||||||
* @return The {@link BehaviorSubject} containing a {@link Map} of all {@link LocationObject} matched
|
* @return The {@link BehaviorSubject} containing a {@link Map} of all {@link LocationObject} matched
|
||||||
* to the sharing ID.
|
* to the sharing ID.
|
||||||
*/
|
*/
|
||||||
public BehaviorSubject<Map<String, LocationObject>> subscribeToAllLocationUpdates() {
|
public BehaviorSubject<Map<String, LocationObject>> subscribeToAllLocationUpdates() {
|
||||||
final Map<String, LocationObject> locationMap = new HashMap<>();
|
final Map<String, LocationObject> locationMap = new HashMap<>();
|
||||||
@@ -191,18 +192,18 @@ public class DatabaseHelper {
|
|||||||
}
|
}
|
||||||
mLocationUpdateListener = mDatabase.collection(DB_COLL_LOCATION)
|
mLocationUpdateListener = mDatabase.collection(DB_COLL_LOCATION)
|
||||||
.addSnapshotListener((value, error) -> {
|
.addSnapshotListener((value, error) -> {
|
||||||
if (value == null || value.isEmpty()) {
|
if (value == null || value.isEmpty()) {
|
||||||
Log.d(TAG, "onEvent: No ongoing location shares, return empty map.");
|
Log.d(TAG, "onEvent: No ongoing location shares, return empty map.");
|
||||||
behaviorSubject.onNext(new HashMap<>());
|
behaviorSubject.onNext(new HashMap<>());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final List<DocumentSnapshot> allDocs = value.getDocuments();
|
final List<DocumentSnapshot> allDocs = value.getDocuments();
|
||||||
locationMap.clear();
|
locationMap.clear();
|
||||||
for (final DocumentSnapshot documentSnapshot : allDocs) {
|
for (final DocumentSnapshot documentSnapshot : allDocs) {
|
||||||
locationMap.put(documentSnapshot.getId(), documentSnapshot.toObject(LocationObject.class));
|
locationMap.put(documentSnapshot.getId(), documentSnapshot.toObject(LocationObject.class));
|
||||||
}
|
}
|
||||||
behaviorSubject.onNext(locationMap);
|
behaviorSubject.onNext(locationMap);
|
||||||
});
|
});
|
||||||
|
|
||||||
return behaviorSubject;
|
return behaviorSubject;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package com.aldo.apps.familyhelpers.workers;
|
package com.aldo.apps.familyhelpers.workers;
|
||||||
|
|
||||||
|
import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
|
||||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_MINIMUM_LOCATION_INTERVAL_METERS;
|
|
||||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_MINIMUM_LOCATION_INTERVAL_MILLIS;
|
|
||||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SHARE_LOCATION_CANCEL_ACTION;
|
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SHARE_LOCATION_CANCEL_ACTION;
|
||||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SHARE_LOCATION_CHANNEL_ID;
|
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SHARE_LOCATION_CHANNEL_ID;
|
||||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SHARE_LOCATION_NOTIFICATION_ID;
|
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SHARE_LOCATION_NOTIFICATION_ID;
|
||||||
@@ -18,6 +17,7 @@ import android.location.LocationListener;
|
|||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
@@ -25,9 +25,9 @@ import androidx.core.app.NotificationCompat;
|
|||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.aldo.apps.familyhelpers.R;
|
import com.aldo.apps.familyhelpers.R;
|
||||||
|
import com.aldo.apps.familyhelpers.ShareLocationActivity;
|
||||||
import com.aldo.apps.familyhelpers.model.LocationObject;
|
import com.aldo.apps.familyhelpers.model.LocationObject;
|
||||||
import com.aldo.apps.familyhelpers.ui.NotificationHelper;
|
import com.aldo.apps.familyhelpers.ui.NotificationHelper;
|
||||||
import com.aldo.apps.familyhelpers.utils.CancelLocationSharingReceiver;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -36,6 +36,7 @@ import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to encapsulate all Location specific calls into one utility class.
|
* Helper class to encapsulate all Location specific calls into one utility class.
|
||||||
|
* TODO: Change logic to make use of the FusedLocationProvider.
|
||||||
*/
|
*/
|
||||||
public final class LocationHelper implements LocationListener {
|
public final class LocationHelper implements LocationListener {
|
||||||
|
|
||||||
@@ -47,42 +48,42 @@ public final class LocationHelper implements LocationListener {
|
|||||||
/**
|
/**
|
||||||
* The request code of the for the location permission request.
|
* The request code of the for the location permission request.
|
||||||
*/
|
*/
|
||||||
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
|
private static final int LOCATION_PERMISSION_REQUEST_CODE = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request code of the for the background location permission request.
|
||||||
|
*/
|
||||||
|
private static final int BACKGROUND_LOCATION_REQUEST_CODE = 101;
|
||||||
/**
|
/**
|
||||||
* The singleton instance of this {@link LocationHelper}.
|
* The singleton instance of this {@link LocationHelper}.
|
||||||
*/
|
*/
|
||||||
private static LocationHelper sInstance;
|
private static LocationHelper sInstance;
|
||||||
private final WeakReference<Context> mContextRef;
|
|
||||||
/**
|
/**
|
||||||
* The {@link LocationManager} object to listen to location updates.
|
* The {@link LocationManager} object to listen to location updates.
|
||||||
*/
|
*/
|
||||||
private final LocationManager mLocationManager;
|
private final LocationManager mLocationManager;
|
||||||
|
|
||||||
/**
|
|
||||||
* Boolean flag to check whether a subscription for location updates is already running or not.
|
|
||||||
*/
|
|
||||||
private boolean mIsSubscribed;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link BehaviorSubject} for the {@link LocationObject} for clients to subscribe to.
|
* The {@link BehaviorSubject} for the {@link LocationObject} for clients to subscribe to.
|
||||||
* TODO: Check if needed.
|
* TODO: Check if needed.
|
||||||
*/
|
*/
|
||||||
private final BehaviorSubject<LocationObject> mLocationSubject = BehaviorSubject.create();
|
private final BehaviorSubject<LocationObject> mLocationSubject = BehaviorSubject.create();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link DatabaseHelper} for db access.
|
* The {@link DatabaseHelper} for db access.
|
||||||
*/
|
*/
|
||||||
private final DatabaseHelper mDbHelper;
|
private final DatabaseHelper mDbHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link NotificationHelper} to show and update {@link Notification}s.
|
* The {@link NotificationHelper} to show and update {@link Notification}s.
|
||||||
*/
|
*/
|
||||||
private final NotificationHelper mNotificationHelper;
|
private final NotificationHelper mNotificationHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link BehaviorSubject} holding the state of current subscription.
|
* The {@link BehaviorSubject} holding the state of current subscription.
|
||||||
*/
|
*/
|
||||||
private final BehaviorSubject<Boolean> mSharingStateSubject = BehaviorSubject.createDefault(false);
|
private final BehaviorSubject<Boolean> mSharingStateSubject = BehaviorSubject.createDefault(false);
|
||||||
|
/**
|
||||||
|
* Boolean flag to check whether a subscription for location updates is already running or not.
|
||||||
|
*/
|
||||||
|
private boolean mIsSubscribed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private C'Tor for singleton instance.
|
* Private C'Tor for singleton instance.
|
||||||
@@ -91,7 +92,6 @@ public final class LocationHelper implements LocationListener {
|
|||||||
*/
|
*/
|
||||||
private LocationHelper(final Context context) {
|
private LocationHelper(final Context context) {
|
||||||
mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||||
mContextRef = new WeakReference<>(context);
|
|
||||||
mNotificationHelper = new NotificationHelper(context);
|
mNotificationHelper = new NotificationHelper(context);
|
||||||
mDbHelper = new DatabaseHelper();
|
mDbHelper = new DatabaseHelper();
|
||||||
mNotificationHelper.createAndRegisterNotificationChannel(
|
mNotificationHelper.createAndRegisterNotificationChannel(
|
||||||
@@ -135,16 +135,41 @@ public final class LocationHelper implements LocationListener {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the permission for {@link android.Manifest.permission#ACCESS_BACKGROUND_LOCATION} if not
|
||||||
|
* yet granted before.
|
||||||
|
*
|
||||||
|
* @param activity The {@link Activity} from where this was called.
|
||||||
|
* @return true if permission was already granted before, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean requestBackgroundLocationPermission(final Activity activity) {
|
||||||
|
if (ContextCompat.checkSelfPermission(activity, ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// Provide a justification to the user.
|
||||||
|
Toast.makeText(activity, R.string.share_location_background_permission_rationale,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
ActivityCompat.requestPermissions(activity, new String[]{ACCESS_BACKGROUND_LOCATION},
|
||||||
|
BACKGROUND_LOCATION_REQUEST_CODE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to handle the result of the permission request dialog.
|
* Helper method to handle the result of the permission request dialog.
|
||||||
*
|
*
|
||||||
|
* @param activity The calling {@link Activity}.
|
||||||
* @param requestCode The code of the permission request.
|
* @param requestCode The code of the permission request.
|
||||||
* @param grantResults The result codes.
|
* @param grantResults The result codes.
|
||||||
* @return true if permission was granted, false otherwise.
|
* @return true if permission was granted, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean handlePermissionResult(final int requestCode, final int[] grantResults) {
|
public boolean handlePermissionResult(final Activity activity, final int requestCode, final int[] grantResults) {
|
||||||
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
|
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
|
||||||
// Permission granted, proceed with location updates
|
// Permission granted, ask for background permission.
|
||||||
|
requestBackgroundLocationPermission(activity);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (requestCode == BACKGROUND_LOCATION_REQUEST_CODE) {
|
||||||
|
// Permission granted, continue with updates.
|
||||||
return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -153,17 +178,17 @@ public final class LocationHelper implements LocationListener {
|
|||||||
/**
|
/**
|
||||||
* Helper method to start listening for updates.
|
* Helper method to start listening for updates.
|
||||||
*
|
*
|
||||||
* @param activity The {@link Activity} from where this was called.
|
* @param context The {@link Context} from where this was called.
|
||||||
* @param minIntervalMillis The minimum time interval in millisecond
|
* @param minIntervalMillis The minimum time interval in millisecond
|
||||||
* @param minDistance The minimum distance for an update to be received.
|
* @param minDistance The minimum distance for an update to be received.
|
||||||
* @return true if subscription started, false otherwise.
|
* @return true if subscription started, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean startLocationUpdates(final Activity activity, final int minIntervalMillis, final int minDistance) {
|
public boolean startLocationUpdates(final Context context, final int minIntervalMillis, final int minDistance) {
|
||||||
if (mIsSubscribed) {
|
if (mIsSubscribed) {
|
||||||
Log.d(TAG, "startLocationUpdates: Already subscribed, no need to update");
|
Log.d(TAG, "startLocationUpdates: Already subscribed, no need to update");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (ContextCompat.checkSelfPermission(activity, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||||
mLocationManager.requestLocationUpdates(
|
mLocationManager.requestLocationUpdates(
|
||||||
LocationManager.FUSED_PROVIDER,
|
LocationManager.FUSED_PROVIDER,
|
||||||
minIntervalMillis,
|
minIntervalMillis,
|
||||||
@@ -200,6 +225,7 @@ public final class LocationHelper implements LocationListener {
|
|||||||
* @return true if location sharing is active, false otherwise.
|
* @return true if location sharing is active, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isCurrentlySharing() {
|
public boolean isCurrentlySharing() {
|
||||||
|
Log.d(TAG, "isCurrentlySharing: Called with currently sharing = [" + mIsSubscribed + "]");
|
||||||
return mIsSubscribed;
|
return mIsSubscribed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,8 +244,12 @@ public final class LocationHelper implements LocationListener {
|
|||||||
* @param context The {@link Context} from where this is called.
|
* @param context The {@link Context} from where this is called.
|
||||||
* @return The {@link Notification} to be shown.
|
* @return The {@link Notification} to be shown.
|
||||||
*/
|
*/
|
||||||
private Notification buildNotification(final Context context) {
|
public Notification buildNotification(final Context context) {
|
||||||
final Intent deleteIntent = new Intent(context, CancelLocationSharingReceiver.class);
|
final Intent notificationIntent = new Intent(context, ShareLocationActivity.class);
|
||||||
|
final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
|
||||||
|
notificationIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
|
||||||
|
final Intent deleteIntent = new Intent(context, ShareLocationBackgroundWorker.class);
|
||||||
deleteIntent.setAction(SHARE_LOCATION_CANCEL_ACTION);
|
deleteIntent.setAction(SHARE_LOCATION_CANCEL_ACTION);
|
||||||
final PendingIntent pendingDeleteIntent = PendingIntent.getBroadcast(context, 0,
|
final PendingIntent pendingDeleteIntent = PendingIntent.getBroadcast(context, 0,
|
||||||
deleteIntent, PendingIntent.FLAG_IMMUTABLE);
|
deleteIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
@@ -227,6 +257,7 @@ public final class LocationHelper implements LocationListener {
|
|||||||
return new NotificationCompat.Builder(context, SHARE_LOCATION_CHANNEL_ID)
|
return new NotificationCompat.Builder(context, SHARE_LOCATION_CHANNEL_ID)
|
||||||
.setContentTitle(context.getString(R.string.title_share_location))
|
.setContentTitle(context.getString(R.string.title_share_location))
|
||||||
.setContentText(context.getString(R.string.share_location_notification_content))
|
.setContentText(context.getString(R.string.share_location_notification_content))
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
.setSmallIcon(R.drawable.ic_location_helper)
|
.setSmallIcon(R.drawable.ic_location_helper)
|
||||||
.setDeleteIntent(pendingDeleteIntent)
|
.setDeleteIntent(pendingDeleteIntent)
|
||||||
.addAction(android.R.drawable.ic_menu_close_clear_cancel,
|
.addAction(android.R.drawable.ic_menu_close_clear_cancel,
|
||||||
@@ -238,14 +269,9 @@ public final class LocationHelper implements LocationListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onLocationChanged(@NonNull final Location location) {
|
public void onLocationChanged(@NonNull final Location location) {
|
||||||
final LocationObject locationObject = LocationObject.fromLocation(location);
|
final LocationObject locationObject = LocationObject.fromLocation(location);
|
||||||
|
Log.d(TAG, "onLocationChanged: Received update with " + locationObject);
|
||||||
mLocationSubject.onNext(locationObject);
|
mLocationSubject.onNext(locationObject);
|
||||||
mDbHelper.insertOrUpdateLocation(locationObject);
|
mDbHelper.insertOrUpdateLocation(locationObject);
|
||||||
final Context context = mContextRef.get();
|
|
||||||
if (context != null) {
|
|
||||||
mNotificationHelper.updateNotification(SHARE_LOCATION_NOTIFICATION_ID, buildNotification(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "onLocationChanged: Received update with " + locationObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.aldo.apps.familyhelpers.workers;
|
||||||
|
|
||||||
|
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SHARE_LOCATION_CANCEL_ACTION;
|
||||||
|
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SLEEP_TIMER_NOTIFICATION_ID;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service implementation to keep location updates running also in the background.
|
||||||
|
*/
|
||||||
|
public class ShareLocationBackgroundWorker extends Service {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag for debugging purpose.
|
||||||
|
*/
|
||||||
|
private static final String TAG = "ShareLocationBackground";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Handler} on the MainThread in order to perform the location update.
|
||||||
|
*/
|
||||||
|
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link LocationHelper} to update the location in the database.
|
||||||
|
*/
|
||||||
|
private LocationHelper mLocationHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Runnable} to execute the task in.
|
||||||
|
*/
|
||||||
|
private Runnable mShareLocationRunnable;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
mLocationHelper = LocationHelper.getInstance(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(final Intent intent, final int flags, int startId) {
|
||||||
|
if (intent == null) {
|
||||||
|
Log.e(TAG, "onStartCommand: Invalid intent intent received, do nothing");
|
||||||
|
return START_REDELIVER_INTENT;
|
||||||
|
}
|
||||||
|
if (intent.getAction() != null && intent.getAction().equals(SHARE_LOCATION_CANCEL_ACTION)) {
|
||||||
|
stopSelf();
|
||||||
|
mLocationHelper.stopLocationUpdates();
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
mShareLocationRunnable = () ->
|
||||||
|
mLocationHelper.startLocationUpdates(ShareLocationBackgroundWorker.this,
|
||||||
|
1000, 0);
|
||||||
|
mHandler.post(mShareLocationRunnable);
|
||||||
|
startForeground(SLEEP_TIMER_NOTIFICATION_ID, mLocationHelper.buildNotification(this));
|
||||||
|
return super.onStartCommand(intent, flags, startId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(final Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
mHandler.removeCallbacks(mShareLocationRunnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ public class SleepTimerHelper extends Service {
|
|||||||
* Tag for debugging purpose.
|
* Tag for debugging purpose.
|
||||||
*/
|
*/
|
||||||
private static final String TAG = "SleepTimerHelper";
|
private static final String TAG = "SleepTimerHelper";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link Handler} on the MainThread in order to perform the actual locking.
|
* {@link Handler} on the MainThread in order to perform the actual locking.
|
||||||
*/
|
*/
|
||||||
@@ -87,7 +88,7 @@ public class SleepTimerHelper extends Service {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(final Intent intent) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +145,6 @@ public class SleepTimerHelper extends Service {
|
|||||||
* Formats the provided time in milliseconds in a human readable format.
|
* Formats the provided time in milliseconds in a human readable format.
|
||||||
*
|
*
|
||||||
* @param millis The remaining time in milliseconds.
|
* @param millis The remaining time in milliseconds.
|
||||||
*
|
|
||||||
* @return The String representation of the milliseconds.
|
* @return The String representation of the milliseconds.
|
||||||
*/
|
*/
|
||||||
private String formatTime(long millis) {
|
private String formatTime(long millis) {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_selected="true">
|
||||||
|
<shape android:layout_height="match_parent">
|
||||||
|
<solid android:color="@color/md_theme_secondaryContainer_mediumContrast" />
|
||||||
|
|
||||||
|
<corners
|
||||||
|
android:bottomLeftRadius="4dp"
|
||||||
|
android:bottomRightRadius="4dp"
|
||||||
|
android:topLeftRadius="4dp"
|
||||||
|
android:topRightRadius="4dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:state_selected="false">
|
||||||
|
<shape>
|
||||||
|
<solid android:color="@color/md_theme_background" />
|
||||||
|
<corners
|
||||||
|
android:bottomLeftRadius="4dp"
|
||||||
|
android:bottomRightRadius="4dp"
|
||||||
|
android:topLeftRadius="4dp"
|
||||||
|
android:topRightRadius="4dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="75dp"
|
android:layout_width="75dp"
|
||||||
android:layout_height="75dp"
|
android:layout_height="75dp"
|
||||||
|
android:background="@drawable/currently_followed_user_selector"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
|||||||
@@ -31,5 +31,6 @@
|
|||||||
<string name="share_location_info_speed">The last received velocity was %.2f m/s (%.1f km/h)</string>
|
<string name="share_location_info_speed">The last received velocity was %.2f m/s (%.1f km/h)</string>
|
||||||
<string name="share_location_info_timestamp">This update was received at: %s</string>
|
<string name="share_location_info_timestamp">This update was received at: %s</string>
|
||||||
<string name="share_location_toggle_button">Share your location</string>
|
<string name="share_location_toggle_button">Share your location</string>
|
||||||
|
<string name="share_location_background_permission_rationale">In order to share your position also while in the background, please grant the always permission</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user