[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
|
||||
implementation 'com.google.android.gms:play-services-maps:19.1.0'
|
||||
implementation 'com.google.android.gms:play-services-location:21.3.0'
|
||||
|
||||
// Glide
|
||||
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.FOREGROUND_SERVICE" />
|
||||
<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_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
@@ -39,6 +40,9 @@
|
||||
android:name=".workers.SleepTimerHelper"
|
||||
android:foregroundServiceType="specialUse" />
|
||||
|
||||
<service android:name=".workers.ShareLocationBackgroundWorker"
|
||||
android:foregroundServiceType="location" />
|
||||
|
||||
<receiver
|
||||
android:name="com.aldo.apps.familyhelpers.DoerflingerHelpersDeviceAdminReceiver"
|
||||
android:exported="true"
|
||||
@@ -51,14 +55,6 @@
|
||||
<action android:name="android.app.action.DEVICE_ADMIN_DISABLED" />
|
||||
</intent-filter>
|
||||
</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>
|
||||
|
||||
</manifest>
|
||||
@@ -8,7 +8,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
public class DoerflingerHelpersDeviceAdminReceiver extends DeviceAdminReceiver {
|
||||
@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.
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.aldo.apps.familyhelpers;
|
||||
|
||||
import static android.Manifest.permission.POST_NOTIFICATIONS;
|
||||
|
||||
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SIGN_IN_PROVIDERS;
|
||||
|
||||
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.SleepTimerPopup;
|
||||
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.LocationHelper;
|
||||
import com.firebase.ui.auth.AuthUI;
|
||||
import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract;
|
||||
import com.firebase.ui.auth.IdpResponse;
|
||||
@@ -67,11 +66,10 @@ public class HelperGridActivity extends AppCompatActivity {
|
||||
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.
|
||||
*/
|
||||
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
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -108,7 +100,6 @@ public class HelperGridActivity extends AppCompatActivity {
|
||||
} else {
|
||||
mWelcomeMessageView.setText(String.format(getString(R.string.welcome_message_placeholder),
|
||||
mCurrentUser.getDisplayName()));
|
||||
mDbHelper = new DatabaseHelper();
|
||||
mLocationHelper = LocationHelper.getInstance(this);
|
||||
}
|
||||
}
|
||||
@@ -159,7 +150,13 @@ public class HelperGridActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public void launchHelper() {
|
||||
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");
|
||||
final Intent intent = new Intent(HelperGridActivity.this, ShareLocationActivity.class);
|
||||
startActivity(intent);
|
||||
@@ -196,14 +193,13 @@ public class HelperGridActivity extends AppCompatActivity {
|
||||
*
|
||||
* @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();
|
||||
if (result.getResultCode() == RESULT_OK) {
|
||||
mCurrentUser = FirebaseAuth.getInstance().getCurrentUser();
|
||||
Log.d(TAG, "onSignInResult: Successfully logged in [" + mCurrentUser.getDisplayName() + "]");
|
||||
mWelcomeMessageView.setText(String.format(getString(R.string.welcome_message_placeholder),
|
||||
mCurrentUser.getDisplayName()));
|
||||
mDbHelper = new DatabaseHelper();
|
||||
mLocationHelper = LocationHelper.getInstance(this);
|
||||
} else {
|
||||
Log.w(TAG, "onSignInResult: Sign-In failed");
|
||||
@@ -213,7 +209,7 @@ public class HelperGridActivity extends AppCompatActivity {
|
||||
Log.w(TAG, "onSignInResult: User canceled, cannot continue");
|
||||
} else {
|
||||
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 int[] 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);
|
||||
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_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 android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.workers.DatabaseHelper;
|
||||
import com.aldo.apps.familyhelpers.workers.LocationHelper;
|
||||
import com.aldo.apps.familyhelpers.workers.ShareLocationBackgroundWorker;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.google.android.gms.maps.CameraUpdateFactory;
|
||||
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.FirebaseUser;
|
||||
|
||||
import org.checkerframework.checker.units.qual.A;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
@@ -61,95 +57,77 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
|
||||
* Tag for debugging purpose.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
private GoogleMap mGmap;
|
||||
|
||||
/**
|
||||
* The {@link DatabaseHelper} to read and load data from.
|
||||
*/
|
||||
private DatabaseHelper mDbHelper;
|
||||
|
||||
/**
|
||||
* The currently logged in {@link FirebaseUser}.
|
||||
*/
|
||||
private FirebaseUser mCurrentUser;
|
||||
|
||||
/**
|
||||
* The ID of the location updates to listen to.
|
||||
*/
|
||||
private String mShareId;
|
||||
|
||||
/**
|
||||
* The InfoBox {@link ImageView} showing the user icon.
|
||||
*/
|
||||
private ImageView mInfoBoxIcon;
|
||||
|
||||
/**
|
||||
* The InfoBox {@link TextView} showing the title.
|
||||
*/
|
||||
private TextView mInfoBoxTitle;
|
||||
|
||||
/**
|
||||
* The InfoBox {@link TextView} showing the Location information.
|
||||
*/
|
||||
private TextView mInfoBoxLocation;
|
||||
|
||||
/**
|
||||
* The InfoBox {@link TextView} showing the altitude..
|
||||
*/
|
||||
private TextView mInfoBoxAltitude;
|
||||
|
||||
/**
|
||||
* The InfoBox {@link TextView} showing the last received speed.
|
||||
*/
|
||||
private TextView mInfoBoxSpeed;
|
||||
|
||||
/**
|
||||
* The InfoBox {@link TextView} showing the timestamp of the last update.
|
||||
*/
|
||||
private TextView mInfoBoxTimeStamp;
|
||||
|
||||
/**
|
||||
* The {@link Disposable} holding the subscription to the {@link LocationObject}
|
||||
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
|
||||
*/
|
||||
private Disposable mLocationUpdateSubscription;
|
||||
|
||||
/**
|
||||
* The {@link Disposable} holding the subscription to the {@link String}
|
||||
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to know whom to follow.
|
||||
*/
|
||||
private Disposable mCurrentlyFollowingSubscription;
|
||||
|
||||
/**
|
||||
* The {@link Disposable} holding the subscription to {@link User}
|
||||
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
|
||||
*/
|
||||
private Disposable mUserSubscription;
|
||||
|
||||
/**
|
||||
* Boolean flag indicating whether the system is currently in night mode or not.
|
||||
*/
|
||||
private boolean mIsNightMode;
|
||||
|
||||
/**
|
||||
* Reference to the Google Maps Marker, to update it's position rather than adding a new one.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -186,22 +164,6 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
|
||||
mInfoBoxTimeStamp = findViewById(R.id.share_location_info_timestamp);
|
||||
mNoActiveShares = findViewById(R.id.tv_no_active_shares);
|
||||
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));
|
||||
mActiveShareAdapter = new ActiveShareAdapter(this);
|
||||
@@ -225,6 +187,8 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
|
||||
if (mLocationUpdateSubscription != null) {
|
||||
mLocationUpdateSubscription.dispose();
|
||||
}
|
||||
|
||||
initAndSetupShareToggle();
|
||||
mUserSubscription = mDbHelper.getAllUsers()
|
||||
.subscribe(this::handleAllUsers, this::handleUserSubscriptionFailed);
|
||||
mLocationUpdateSubscription = mDbHelper.subscribeToAllLocationUpdates()
|
||||
@@ -233,6 +197,28 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
|
||||
.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
|
||||
protected void 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.
|
||||
*
|
||||
* @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
|
||||
*/
|
||||
/**
|
||||
* Removed all markers that became invalid because the sharing was stopped.
|
||||
*
|
||||
* @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
|
||||
*/
|
||||
private void removeInvalidExistingMarkers(final Map<String, LocationObject> locationObjectMap) {
|
||||
final List<String> toBeRemovedKeys = new ArrayList<>();
|
||||
for (Map.Entry<String, Marker> markerEntry : mMarkerMap.entrySet()) {
|
||||
|
||||
@@ -68,6 +68,19 @@ public class LocationObject {
|
||||
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.
|
||||
*
|
||||
@@ -122,20 +135,6 @@ public class LocationObject {
|
||||
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
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -55,6 +55,30 @@ public class User {
|
||||
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.
|
||||
*
|
||||
@@ -91,31 +115,6 @@ public class User {
|
||||
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
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.aldo.apps.familyhelpers.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -10,7 +9,6 @@ import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.aldo.apps.familyhelpers.R;
|
||||
@@ -20,20 +18,46 @@ import com.bumptech.glide.Glide;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
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> {
|
||||
|
||||
/**
|
||||
* Tag for debugging purposes.
|
||||
*/
|
||||
private static final String TAG = "ActiveShareAdapter";
|
||||
|
||||
/**
|
||||
* {@link BehaviorSubject} holding the ID of the currently selected user.
|
||||
*/
|
||||
private final BehaviorSubject<String> mCurrentlySelectedSubject = BehaviorSubject.createDefault("");
|
||||
|
||||
/**
|
||||
* {@link List} of all currently sharing {@link User}s.
|
||||
*/
|
||||
private final List<User> mSharingUserList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* A {@link WeakReference} to the calling {@link Context}.
|
||||
*/
|
||||
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) {
|
||||
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) {
|
||||
final View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.active_share_view_holder, parent, false);
|
||||
mAllItems.add(view);
|
||||
return new ActiveShareViewHolder(view);
|
||||
}
|
||||
|
||||
@@ -58,24 +83,60 @@ public class ActiveShareAdapter extends RecyclerView.Adapter<ActiveShareAdapter.
|
||||
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) {
|
||||
mSharingUserList.clear();
|
||||
mSharingUserList.addAll(newUsers);
|
||||
mAllItems.clear();
|
||||
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() {
|
||||
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 {
|
||||
|
||||
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) {
|
||||
super(itemView);
|
||||
mProfilePicture = itemView.findViewById(R.id.active_share_profile_picture);
|
||||
@@ -83,6 +144,11 @@ public class ActiveShareAdapter extends RecyclerView.Adapter<ActiveShareAdapter.
|
||||
mItemView = itemView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to apply userData to a View.
|
||||
*
|
||||
* @param user The {@link User} to be attached.
|
||||
*/
|
||||
public void setUserData(final User user) {
|
||||
if (user == null) {
|
||||
Log.w(TAG, "setUserData: Skip update, no valid update yet");
|
||||
@@ -90,7 +156,15 @@ public class ActiveShareAdapter extends RecyclerView.Adapter<ActiveShareAdapter.
|
||||
}
|
||||
mItemView.setOnClickListener(v -> {
|
||||
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());
|
||||
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}.
|
||||
*
|
||||
* @param context The {@link Context} from where this was called.
|
||||
*
|
||||
* @return the singleton instance of the {@link DevicePolicyManagerHelper}.
|
||||
*/
|
||||
public static DevicePolicyManagerHelper getInstance(final Context context) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.aldo.apps.familyhelpers.utils;
|
||||
import com.firebase.ui.auth.AuthUI;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -10,88 +11,75 @@ import java.util.List;
|
||||
*/
|
||||
public final class GlobalConstants {
|
||||
|
||||
/**
|
||||
* Private C'tor to prevent instantiation.
|
||||
*/
|
||||
private GlobalConstants() {}
|
||||
|
||||
/**
|
||||
* ID of the NotificationChannel for the SleepTimer Notification.
|
||||
*/
|
||||
public static final String SLEEP_TIMER_CHANNEL_ID = "CountdownChannel";
|
||||
|
||||
/**
|
||||
* ID of the NotificationChannel for the ShareLocation Notification.
|
||||
*/
|
||||
public static final String SHARE_LOCATION_CHANNEL_ID = "LocationShareChannel";
|
||||
|
||||
/**
|
||||
* Factor to calculate seconds from milliseconds and vice versa.
|
||||
*/
|
||||
public static final int ONE_SECOND_IN_MILLIS = 1000;
|
||||
|
||||
/**
|
||||
* Factor to calculate hours from minutes and vice versa.
|
||||
*/
|
||||
public static final int ONE_HOUR_IN_MINUTES = 60;
|
||||
|
||||
/**
|
||||
* The NotificationID of the SleepTimer notification.
|
||||
*/
|
||||
public static final int SLEEP_TIMER_NOTIFICATION_ID = 1;
|
||||
|
||||
/**
|
||||
* The NotificationID of the ShareLocation notification.
|
||||
*/
|
||||
public static final int SHARE_LOCATION_NOTIFICATION_ID = 2;
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* The key of the extra to be applied to the starting intent of the sleepTimer service,
|
||||
* holding the initial duration in millis.
|
||||
*/
|
||||
public static final String SLEEP_TIMER_DURATION_MILLIS_EXTRA = "sleep_timer_duration";
|
||||
|
||||
/**
|
||||
* Action to cancel an ongoing sleep timer.
|
||||
*/
|
||||
public static final String SLEEP_TIMER_CANCEL_ACTION = "SLEEP_TIMER_CANCEL";
|
||||
|
||||
/**
|
||||
* Default minimum time interval between two location updates.
|
||||
* Currently set to 5 Seconds.
|
||||
*/
|
||||
public static final int DEFAULT_MINIMUM_LOCATION_INTERVAL_MILLIS = 5000;
|
||||
|
||||
/**
|
||||
* Default minimum distance interval between two location updates.
|
||||
* Currently set to 5 Meters.
|
||||
*/
|
||||
public static final int DEFAULT_MINIMUM_LOCATION_INTERVAL_METERS = 5;
|
||||
|
||||
/**
|
||||
* Conversion constants to convert m/s into km/h
|
||||
*/
|
||||
public static final double METER_PER_SECOND_TO_KMH_CONVERTER = 3.6;
|
||||
|
||||
/**
|
||||
* Latitude of the default home (== Blaustein)
|
||||
*/
|
||||
public static final double DEFAULT_HOME_LATITUDE = 48.41965746149261;
|
||||
|
||||
/**
|
||||
* Longitude of the default home (== Blaustein)
|
||||
*/
|
||||
public static final double DEFAULT_HOME_LONGITUDE = 9.909289365473684;
|
||||
|
||||
/**
|
||||
* 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()
|
||||
);
|
||||
|
||||
/**
|
||||
* 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_USERS;
|
||||
import static com.aldo.apps.familyhelpers.utils.DatabaseConstants.DB_DOC_USER_ID;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.aldo.apps.familyhelpers.model.LocationObject;
|
||||
import com.aldo.apps.familyhelpers.model.User;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
import com.google.firebase.auth.FirebaseUser;
|
||||
import com.google.firebase.firestore.DocumentSnapshot;
|
||||
import com.google.firebase.firestore.EventListener;
|
||||
import com.google.firebase.firestore.FirebaseFirestore;
|
||||
import com.google.firebase.firestore.FirebaseFirestoreException;
|
||||
import com.google.firebase.firestore.ListenerRegistration;
|
||||
import com.google.firebase.firestore.QuerySnapshot;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -64,7 +58,7 @@ public class DatabaseHelper {
|
||||
/**
|
||||
* {@link Map} containing all users.
|
||||
*/
|
||||
private Map<String, User> mAllUserMap = new HashMap<>();
|
||||
private final Map<String, User> mAllUserMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* C'tor.
|
||||
@@ -119,6 +113,13 @@ public class DatabaseHelper {
|
||||
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) {
|
||||
Log.d(TAG, "getUserForId() called with: userId = [" + userId + "]");
|
||||
if (mAllUserMap.containsKey(userId)) {
|
||||
@@ -162,25 +163,25 @@ public class DatabaseHelper {
|
||||
mLocationUpdateListener = mDatabase.collection(DB_COLL_LOCATION)
|
||||
.document(shareId)
|
||||
.addSnapshotListener((value, error) -> {
|
||||
if (value == null) {
|
||||
Log.d(TAG, "onEvent: Location was deleted");
|
||||
mObservedLocation.onComplete();
|
||||
return;
|
||||
}
|
||||
final LocationObject updatedLocation = value.toObject(LocationObject.class);
|
||||
if (updatedLocation == null) {
|
||||
Log.w(TAG, "onEvent: Error while parsing, ignore");
|
||||
return;
|
||||
}
|
||||
mObservedLocation.onNext(updatedLocation);
|
||||
});
|
||||
if (value == null) {
|
||||
Log.d(TAG, "onEvent: Location was deleted");
|
||||
mObservedLocation.onComplete();
|
||||
return;
|
||||
}
|
||||
final LocationObject updatedLocation = value.toObject(LocationObject.class);
|
||||
if (updatedLocation == null) {
|
||||
Log.w(TAG, "onEvent: Error while parsing, ignore");
|
||||
return;
|
||||
}
|
||||
mObservedLocation.onNext(updatedLocation);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* to the sharing ID.
|
||||
* to the sharing ID.
|
||||
*/
|
||||
public BehaviorSubject<Map<String, LocationObject>> subscribeToAllLocationUpdates() {
|
||||
final Map<String, LocationObject> locationMap = new HashMap<>();
|
||||
@@ -191,18 +192,18 @@ public class DatabaseHelper {
|
||||
}
|
||||
mLocationUpdateListener = mDatabase.collection(DB_COLL_LOCATION)
|
||||
.addSnapshotListener((value, error) -> {
|
||||
if (value == null || value.isEmpty()) {
|
||||
Log.d(TAG, "onEvent: No ongoing location shares, return empty map.");
|
||||
behaviorSubject.onNext(new HashMap<>());
|
||||
return;
|
||||
}
|
||||
final List<DocumentSnapshot> allDocs = value.getDocuments();
|
||||
locationMap.clear();
|
||||
for (final DocumentSnapshot documentSnapshot : allDocs) {
|
||||
locationMap.put(documentSnapshot.getId(), documentSnapshot.toObject(LocationObject.class));
|
||||
}
|
||||
behaviorSubject.onNext(locationMap);
|
||||
});
|
||||
if (value == null || value.isEmpty()) {
|
||||
Log.d(TAG, "onEvent: No ongoing location shares, return empty map.");
|
||||
behaviorSubject.onNext(new HashMap<>());
|
||||
return;
|
||||
}
|
||||
final List<DocumentSnapshot> allDocs = value.getDocuments();
|
||||
locationMap.clear();
|
||||
for (final DocumentSnapshot documentSnapshot : allDocs) {
|
||||
locationMap.put(documentSnapshot.getId(), documentSnapshot.toObject(LocationObject.class));
|
||||
}
|
||||
behaviorSubject.onNext(locationMap);
|
||||
});
|
||||
|
||||
return behaviorSubject;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.aldo.apps.familyhelpers.workers;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_BACKGROUND_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_CHANNEL_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.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
@@ -25,9 +25,9 @@ import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.aldo.apps.familyhelpers.R;
|
||||
import com.aldo.apps.familyhelpers.ShareLocationActivity;
|
||||
import com.aldo.apps.familyhelpers.model.LocationObject;
|
||||
import com.aldo.apps.familyhelpers.ui.NotificationHelper;
|
||||
import com.aldo.apps.familyhelpers.utils.CancelLocationSharingReceiver;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
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.
|
||||
* TODO: Change logic to make use of the FusedLocationProvider.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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}.
|
||||
*/
|
||||
private static LocationHelper sInstance;
|
||||
private final WeakReference<Context> mContextRef;
|
||||
|
||||
/**
|
||||
* The {@link LocationManager} object to listen to location updates.
|
||||
*/
|
||||
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.
|
||||
* TODO: Check if needed.
|
||||
*/
|
||||
private final BehaviorSubject<LocationObject> mLocationSubject = BehaviorSubject.create();
|
||||
|
||||
private final BehaviorSubject<LocationObject> mLocationSubject = BehaviorSubject.create();
|
||||
/**
|
||||
* The {@link DatabaseHelper} for db access.
|
||||
*/
|
||||
private final DatabaseHelper mDbHelper;
|
||||
|
||||
/**
|
||||
* The {@link NotificationHelper} to show and update {@link Notification}s.
|
||||
*/
|
||||
private final NotificationHelper mNotificationHelper;
|
||||
|
||||
/**
|
||||
* The {@link BehaviorSubject} holding the state of current subscription.
|
||||
*/
|
||||
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.
|
||||
@@ -91,7 +92,6 @@ public final class LocationHelper implements LocationListener {
|
||||
*/
|
||||
private LocationHelper(final Context context) {
|
||||
mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
mContextRef = new WeakReference<>(context);
|
||||
mNotificationHelper = new NotificationHelper(context);
|
||||
mDbHelper = new DatabaseHelper();
|
||||
mNotificationHelper.createAndRegisterNotificationChannel(
|
||||
@@ -135,16 +135,41 @@ public final class LocationHelper implements LocationListener {
|
||||
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.
|
||||
*
|
||||
* @param activity The calling {@link Activity}.
|
||||
* @param requestCode The code of the permission request.
|
||||
* @param grantResults The result codes.
|
||||
* @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) {
|
||||
// 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 false;
|
||||
@@ -153,17 +178,17 @@ public final class LocationHelper implements LocationListener {
|
||||
/**
|
||||
* 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 minDistance The minimum distance for an update to be received.
|
||||
* @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) {
|
||||
Log.d(TAG, "startLocationUpdates: Already subscribed, no need to update");
|
||||
return true;
|
||||
}
|
||||
if (ContextCompat.checkSelfPermission(activity, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
if (ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
mLocationManager.requestLocationUpdates(
|
||||
LocationManager.FUSED_PROVIDER,
|
||||
minIntervalMillis,
|
||||
@@ -200,6 +225,7 @@ public final class LocationHelper implements LocationListener {
|
||||
* @return true if location sharing is active, false otherwise.
|
||||
*/
|
||||
public boolean isCurrentlySharing() {
|
||||
Log.d(TAG, "isCurrentlySharing: Called with currently sharing = [" + mIsSubscribed + "]");
|
||||
return mIsSubscribed;
|
||||
}
|
||||
|
||||
@@ -218,8 +244,12 @@ public final class LocationHelper implements LocationListener {
|
||||
* @param context The {@link Context} from where this is called.
|
||||
* @return The {@link Notification} to be shown.
|
||||
*/
|
||||
private Notification buildNotification(final Context context) {
|
||||
final Intent deleteIntent = new Intent(context, CancelLocationSharingReceiver.class);
|
||||
public Notification buildNotification(final Context context) {
|
||||
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);
|
||||
final PendingIntent pendingDeleteIntent = PendingIntent.getBroadcast(context, 0,
|
||||
deleteIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
@@ -227,6 +257,7 @@ public final class LocationHelper implements LocationListener {
|
||||
return new NotificationCompat.Builder(context, SHARE_LOCATION_CHANNEL_ID)
|
||||
.setContentTitle(context.getString(R.string.title_share_location))
|
||||
.setContentText(context.getString(R.string.share_location_notification_content))
|
||||
.setContentIntent(pendingIntent)
|
||||
.setSmallIcon(R.drawable.ic_location_helper)
|
||||
.setDeleteIntent(pendingDeleteIntent)
|
||||
.addAction(android.R.drawable.ic_menu_close_clear_cancel,
|
||||
@@ -238,14 +269,9 @@ public final class LocationHelper implements LocationListener {
|
||||
@Override
|
||||
public void onLocationChanged(@NonNull final Location location) {
|
||||
final LocationObject locationObject = LocationObject.fromLocation(location);
|
||||
Log.d(TAG, "onLocationChanged: Received update with " + locationObject);
|
||||
mLocationSubject.onNext(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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
private static final String TAG = "SleepTimerHelper";
|
||||
|
||||
/**
|
||||
* {@link Handler} on the MainThread in order to perform the actual locking.
|
||||
*/
|
||||
@@ -87,7 +88,7 @@ public class SleepTimerHelper extends Service {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
public IBinder onBind(final Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -144,7 +145,6 @@ public class SleepTimerHelper extends Service {
|
||||
* Formats the provided time in milliseconds in a human readable format.
|
||||
*
|
||||
* @param millis The remaining time in milliseconds.
|
||||
*
|
||||
* @return The String representation of the milliseconds.
|
||||
*/
|
||||
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"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="75dp"
|
||||
android:background="@drawable/currently_followed_user_selector"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
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_timestamp">This update was received at: %s</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>
|
||||
Reference in New Issue
Block a user