[ShareLocation] Added sharing of location logic.
Sharing of location is enabled yet, though no receiving part is implemented as of now.
This commit is contained in:
@@ -5,6 +5,9 @@
|
||||
<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.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -39,6 +42,13 @@
|
||||
<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>
|
||||
@@ -11,19 +11,19 @@ import android.util.Log;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
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.firebase.ui.auth.AuthUI;
|
||||
import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract;
|
||||
import com.firebase.ui.auth.FirebaseUiException;
|
||||
import com.firebase.ui.auth.IdpResponse;
|
||||
import com.firebase.ui.auth.data.model.FirebaseAuthUIAuthenticationResult;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
@@ -39,6 +39,8 @@ public class HelperGridActivity extends AppCompatActivity {
|
||||
*/
|
||||
private static final String TAG = "HelperGridActivity";
|
||||
|
||||
private LocationHelper mLocationHelper;
|
||||
|
||||
/**
|
||||
* {@link HelperGroupTile} holding the option for a sleep timer.
|
||||
*/
|
||||
@@ -95,6 +97,7 @@ public class HelperGridActivity extends AppCompatActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
mLocationHelper = LocationHelper.getInstance(this);
|
||||
mWelcomeMessageView = findViewById(R.id.tv_welcome_message);
|
||||
if (mCurrentUser == null) {
|
||||
final Intent signInIntent = AuthUI.getInstance()
|
||||
@@ -156,9 +159,13 @@ public class HelperGridActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public void launchHelper() {
|
||||
Log.d(TAG, "launchHelper: Clicked ShareLocation");
|
||||
if (mLocationHelper.requestLocationPermissions(HelperGridActivity.this)) {
|
||||
Log.d(TAG, "launchHelper: Permission already granted");
|
||||
mLocationHelper.toggleUpdate(HelperGridActivity.this);
|
||||
}
|
||||
}
|
||||
};
|
||||
mShareLocationTile.setLogo(R.drawable.ic_share_location);
|
||||
mShareLocationTile.setLogo(R.drawable.ic_location_helper);
|
||||
mShareLocationTile.setTitle(R.string.title_share_location);
|
||||
}
|
||||
|
||||
@@ -210,5 +217,14 @@ public class HelperGridActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(final int requestCode,
|
||||
@NonNull final String[] permissions,
|
||||
@NonNull final int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (mLocationHelper.handlePermissionResult(requestCode, grantResults)) {
|
||||
mLocationHelper.toggleUpdate(HelperGridActivity.this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.aldo.apps.familyhelpers.model;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
import com.google.firebase.auth.FirebaseUser;
|
||||
|
||||
/**
|
||||
* Model class for a {@link LocationObject}.
|
||||
*/
|
||||
public class LocationObject {
|
||||
/**
|
||||
* Unique identifier for the shared location.
|
||||
*/
|
||||
private String shareId;
|
||||
|
||||
/**
|
||||
* Last received latitude.
|
||||
*/
|
||||
private double latitude;
|
||||
|
||||
/**
|
||||
* Last received longitude.
|
||||
*/
|
||||
private double longitude;
|
||||
|
||||
/**
|
||||
* Last received altitude.
|
||||
*/
|
||||
private double altitude;
|
||||
|
||||
/**
|
||||
* Last received speed.
|
||||
*/
|
||||
private float speed;
|
||||
|
||||
/**
|
||||
* Last received timestamp.
|
||||
*/
|
||||
private long timestamp;
|
||||
|
||||
/**
|
||||
* Empty C'Tor for database usage.
|
||||
*/
|
||||
public LocationObject() {
|
||||
//Empty C'Tor for database usage.
|
||||
}
|
||||
|
||||
/**
|
||||
* C'Tor filling all arguments.
|
||||
*
|
||||
* @param shareId Unique identifier for the shared location.
|
||||
* @param latitude Last received latitude.
|
||||
* @param longitude Last received longitude.
|
||||
* @param altitude Last received altitude.
|
||||
* @param speed Last received speed.
|
||||
* @param timestamp Last received timestamp.
|
||||
*/
|
||||
public LocationObject(final String shareId, final double latitude, final double longitude,
|
||||
final double altitude, final float speed, final long timestamp) {
|
||||
this.shareId = shareId;
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
this.altitude = altitude;
|
||||
this.speed = speed;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique identifier for the shared location.
|
||||
*
|
||||
* @return The unique identifier for the shared location.
|
||||
*/
|
||||
public String getShareId() {
|
||||
return shareId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last received latitude.
|
||||
*
|
||||
* @return The last received latitude.
|
||||
*/
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last received longitude.
|
||||
*
|
||||
* @return The last received longitude.
|
||||
*/
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last received altitude.
|
||||
*
|
||||
* @return The last received altitude.
|
||||
*/
|
||||
public double getAltitude() {
|
||||
return altitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last received speed.
|
||||
*
|
||||
* @return The last received speed.
|
||||
*/
|
||||
public float getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last received timestamp.
|
||||
*
|
||||
* @return The last received timestamp.
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
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() {
|
||||
return "LocationObject with shareId = [" + shareId + "], lat = [" + latitude
|
||||
+ "], long = [" + longitude + "], alt = [" + altitude + "], speed = [" + speed
|
||||
+ "], time = [" + timestamp + "]";
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public class User {
|
||||
private long creationDate;
|
||||
|
||||
/**
|
||||
* //Empty C'Tor for database usage.
|
||||
* Empty C'Tor for database usage.
|
||||
*/
|
||||
public User() {
|
||||
//Empty C'Tor for database usage.
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.aldo.apps.familyhelpers.ui;
|
||||
|
||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Helper class to encapsulate common {@link Notification} related tasks.
|
||||
*/
|
||||
public class NotificationHelper {
|
||||
|
||||
/**
|
||||
* {@link WeakReference} on the {@link Context}.
|
||||
*/
|
||||
private final WeakReference<Context> mContextRef;
|
||||
|
||||
/**
|
||||
* {@link NotificationManager} to show and update the notifications.
|
||||
*/
|
||||
private final NotificationManager mNotificationManager;
|
||||
|
||||
/**
|
||||
* C'Tor.
|
||||
*
|
||||
* @param context The {@link Context} from which this was called.
|
||||
*/
|
||||
public NotificationHelper(final Context context) {
|
||||
mContextRef = new WeakReference<>(context);
|
||||
mNotificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or updates an existing {@link Notification}.
|
||||
*
|
||||
* @param notificationId The identifier of the {@link Notification}.
|
||||
* @param notification The actual {@link Notification}.
|
||||
*/
|
||||
public void updateNotification(final int notificationId, final Notification notification) {
|
||||
mNotificationManager.notify(notificationId, notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel an ongoing {@link Notification}.
|
||||
*
|
||||
* @param notificationId The ID of the notification to cancel.
|
||||
*/
|
||||
public void cancelNotification(final int notificationId) {
|
||||
mNotificationManager.cancel(notificationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create and register a new {@link NotificationChannel}.
|
||||
*
|
||||
* @param channelId The ID of the channel to be created.
|
||||
* @param channelLabel The label of the channel to be created.
|
||||
*/
|
||||
public void createAndRegisterNotificationChannel(final String channelId,
|
||||
@StringRes final int channelLabel) {
|
||||
final Context context = mContextRef.get();
|
||||
if (context != null) {
|
||||
final NotificationChannel channel = new NotificationChannel(channelId,
|
||||
context.getString(channelLabel),
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
mNotificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,33 @@
|
||||
package com.aldo.apps.familyhelpers.utils;
|
||||
|
||||
/**
|
||||
* Utility class for database constants.
|
||||
*/
|
||||
public final class DatabaseConstants {
|
||||
|
||||
/**
|
||||
* Key for the collection of Users in the Database.
|
||||
*/
|
||||
public static final String DB_COLL_USERS = "users";
|
||||
|
||||
/**
|
||||
* The unique identifier of a user document.
|
||||
*/
|
||||
public static final String DB_DOC_USER_ID = "uId";
|
||||
|
||||
/**
|
||||
* Key for the collection of shared locations in the Database.
|
||||
*/
|
||||
public static final String DB_COLL_LOCATION = "locations";
|
||||
|
||||
/**
|
||||
* The unique identifier for the location sharing.
|
||||
*/
|
||||
public static final String DB_DOC_SHARE_ID = "shareId";
|
||||
|
||||
/**
|
||||
* Private C'Tor to prevent instantiation.
|
||||
*/
|
||||
private DatabaseConstants() {
|
||||
//Private C'Tor to prevent instantiation.
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ public final class GlobalConstants {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -35,6 +40,16 @@ public final class GlobalConstants {
|
||||
*/
|
||||
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.
|
||||
@@ -46,6 +61,18 @@ public final class GlobalConstants {
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* List of available Firebase signIn/Login providers.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
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 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;
|
||||
@@ -80,7 +82,28 @@ public class DatabaseHelper {
|
||||
Log.w(TAG, "onEvent: Read failed, do not update local values.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to insert or update a {@link LocationObject} in the database.
|
||||
*
|
||||
* @param locationObject The {@link LocationObject} to be inserted/updated.
|
||||
*/
|
||||
public void insertOrUpdateLocation(final LocationObject locationObject) {
|
||||
mDatabase.collection(DB_COLL_LOCATION)
|
||||
.document(locationObject.getShareId())
|
||||
.set(locationObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels (by deleting) the ongoing {@link LocationObject} sharing in the database.
|
||||
*/
|
||||
public void cancelOngoingLocationSharing() {
|
||||
if (mCurrentUser == null) {
|
||||
Log.w(TAG, "cancelOngoingLocationSharing: No user logged in, cannot stop sharing");
|
||||
return;
|
||||
}
|
||||
mDatabase.collection(DB_COLL_LOCATION).document(mCurrentUser.getUid()).delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
package com.aldo.apps.familyhelpers.workers;
|
||||
|
||||
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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.aldo.apps.familyhelpers.R;
|
||||
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;
|
||||
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
||||
|
||||
/**
|
||||
* Helper class to encapsulate all Location specific calls into one utility class.
|
||||
*/
|
||||
public final class LocationHelper implements LocationListener {
|
||||
|
||||
/**
|
||||
* Tag for debugging purpose.
|
||||
*/
|
||||
private static final String TAG = "LocationHelper";
|
||||
|
||||
/**
|
||||
* The request code of the for the location permission request.
|
||||
*/
|
||||
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
|
||||
/**
|
||||
* 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 BehaviorSubject<LocationObject> mLocationSubject = BehaviorSubject.create();
|
||||
|
||||
/**
|
||||
* The {@link DatabaseHelper} for db access.
|
||||
*/
|
||||
private DatabaseHelper mDbHelper = new DatabaseHelper();
|
||||
|
||||
/**
|
||||
* The {@link NotificationHelper} to show and update {@link Notification}s.
|
||||
*/
|
||||
private NotificationHelper mNotificationHelper;
|
||||
|
||||
/**
|
||||
* Private C'Tor for singleton instance.
|
||||
*
|
||||
* @param context The {@link Context) from where this was instantiated.
|
||||
*/
|
||||
private LocationHelper(final Context context) {
|
||||
mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
mContextRef = new WeakReference<>(context);
|
||||
mNotificationHelper = new NotificationHelper(context);
|
||||
mNotificationHelper.createAndRegisterNotificationChannel(
|
||||
SHARE_LOCATION_CHANNEL_ID, R.string.share_location_notification_channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton instance of thie {@link LocationHelper}.
|
||||
*
|
||||
* @param context The {@link Context) from where this was instantiated.
|
||||
* @return The singleton instance of thie {@link LocationHelper}.
|
||||
*/
|
||||
public static LocationHelper getInstance(final Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new LocationHelper(context);
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link BehaviorSubject} of {@link LocationObject} for clients to subscribe to.
|
||||
*
|
||||
* @return The {@link BehaviorSubject} of {@link LocationObject} for clients to subscribe to.
|
||||
*/
|
||||
public BehaviorSubject<LocationObject> getLocationSubject() {
|
||||
return mLocationSubject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the permission for {@link android.Manifest.permission#ACCESS_FINE_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 requestLocationPermissions(final Activity activity) {
|
||||
if (ContextCompat.checkSelfPermission(activity, ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(activity, new String[]{ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to handle the result of the permission request dialog.
|
||||
*
|
||||
* @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) {
|
||||
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
|
||||
// Permission granted, proceed with location updates
|
||||
return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to start listening for updates.
|
||||
*
|
||||
* @param activity The {@link Activity} 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) {
|
||||
if (ContextCompat.checkSelfPermission(activity, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
mLocationManager.requestLocationUpdates(
|
||||
LocationManager.FUSED_PROVIDER,
|
||||
minIntervalMillis,
|
||||
minDistance,
|
||||
this,
|
||||
Looper.getMainLooper()
|
||||
);
|
||||
mIsSubscribed = true;
|
||||
return true;
|
||||
}
|
||||
mIsSubscribed = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops listening for updates.
|
||||
*/
|
||||
public void stopLocationUpdates() {
|
||||
mLocationManager.removeUpdates(this);
|
||||
mIsSubscribed = false;
|
||||
mDbHelper.cancelOngoingLocationSharing();
|
||||
mNotificationHelper.cancelNotification(SHARE_LOCATION_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to toggle the subscription state, if already running, cancel it, if not yet,
|
||||
* start it.
|
||||
*
|
||||
* @param activity The {@link Activity} from where this was called.
|
||||
*/
|
||||
public void toggleUpdate(final Activity activity) {
|
||||
if (mIsSubscribed) {
|
||||
Log.d(TAG, "toggleUpdate: Stop subscription");
|
||||
stopLocationUpdates();
|
||||
} else {
|
||||
Log.d(TAG, "toggleUpdate: Start subscription");
|
||||
startLocationUpdates(activity, DEFAULT_MINIMUM_LOCATION_INTERVAL_MILLIS,
|
||||
DEFAULT_MINIMUM_LOCATION_INTERVAL_METERS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a notification to be shown while location sharing is in progress.
|
||||
*
|
||||
* @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);
|
||||
deleteIntent.setAction(SHARE_LOCATION_CANCEL_ACTION);
|
||||
final PendingIntent pendingDeleteIntent = PendingIntent.getBroadcast(context, 0,
|
||||
deleteIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
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))
|
||||
.setSmallIcon(R.drawable.ic_location_helper)
|
||||
.setDeleteIntent(pendingDeleteIntent)
|
||||
.addAction(android.R.drawable.ic_menu_close_clear_cancel,
|
||||
context.getString(R.string.sleep_timer_notification_cancel), pendingDeleteIntent)
|
||||
.setStyle(new NotificationCompat.BigTextStyle())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(@NonNull final Location location) {
|
||||
final LocationObject locationObject = LocationObject.fromLocation(location);
|
||||
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
|
||||
public void onLocationChanged(@NonNull final List<Location> locations) {
|
||||
LocationListener.super.onLocationChanged(locations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFlushComplete(final int requestCode) {
|
||||
LocationListener.super.onFlushComplete(requestCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(@NonNull final String provider) {
|
||||
LocationListener.super.onProviderEnabled(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(@NonNull final String provider) {
|
||||
LocationListener.super.onProviderDisabled(provider);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.aldo.apps.familyhelpers.HelperGridActivity;
|
||||
import com.aldo.apps.familyhelpers.R;
|
||||
import com.aldo.apps.familyhelpers.ui.NotificationHelper;
|
||||
import com.aldo.apps.familyhelpers.utils.DevicePolicyManagerHelper;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -57,16 +58,16 @@ public class SleepTimerHelper extends Service {
|
||||
private DevicePolicyManagerHelper mDevicePolicyHelper;
|
||||
|
||||
/**
|
||||
* {@link NotificationManager} to show and update the foreground service notification.
|
||||
* The {@link NotificationHelper} to show and update {@link Notification}s.
|
||||
*/
|
||||
private NotificationManager mNotificationManager;
|
||||
private NotificationHelper mNotificationHelper;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.d(TAG, "onCreate() called");
|
||||
mDevicePolicyHelper = DevicePolicyManagerHelper.getInstance(this);
|
||||
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
mNotificationHelper = new NotificationHelper(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,9 +81,9 @@ public class SleepTimerHelper extends Service {
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
mCountdownTimeMillis = intent.getLongExtra(SLEEP_TIMER_DURATION_MILLIS_EXTRA, 0);
|
||||
createNotificationChannel();
|
||||
mNotificationHelper.createAndRegisterNotificationChannel(SLEEP_TIMER_CHANNEL_ID, R.string.sleep_timer_notification_channel);
|
||||
startForeground(SLEEP_TIMER_NOTIFICATION_ID, buildNotification());
|
||||
mNotificationManager.notify(SLEEP_TIMER_NOTIFICATION_ID, buildNotification());
|
||||
mNotificationHelper.updateNotification(SLEEP_TIMER_NOTIFICATION_ID, buildNotification());
|
||||
startCountdown();
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
@@ -102,7 +103,7 @@ public class SleepTimerHelper extends Service {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mCountdownTimeMillis > 0) {
|
||||
updateNotification();
|
||||
mNotificationHelper.updateNotification(SLEEP_TIMER_NOTIFICATION_ID, buildNotification());
|
||||
mCountdownTimeMillis -= ONE_SECOND_IN_MILLIS;
|
||||
mHandler.postDelayed(this, ONE_SECOND_IN_MILLIS);
|
||||
} else {
|
||||
@@ -115,14 +116,6 @@ public class SleepTimerHelper extends Service {
|
||||
mHandler.post(mCountdownRunnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to update the notification with the new time.
|
||||
*/
|
||||
private void updateNotification() {
|
||||
final Notification notification = buildNotification();
|
||||
mNotificationManager.notify(SLEEP_TIMER_NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to build the Notification to be shown showing the current progress of the countdown.#
|
||||
*
|
||||
@@ -143,25 +136,13 @@ public class SleepTimerHelper extends Service {
|
||||
.setContentTitle(getString(R.string.title_sleep_timer))
|
||||
.setContentText(String.format(getString(R.string.sleep_timer_notification_content),
|
||||
countdownText))
|
||||
.setSmallIcon(android.R.drawable.ic_lock_idle_alarm)
|
||||
.setSmallIcon(R.drawable.ic_sleep_timer)
|
||||
.setContentIntent(pendingIntent)
|
||||
.addAction(android.R.drawable.ic_menu_close_clear_cancel,
|
||||
getString(R.string.sleep_timer_notification_cancel), cancelPendingIntent)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ad registers a new {@link NotificationChannel}.
|
||||
*/
|
||||
private void createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
final NotificationChannel channel = new NotificationChannel(SLEEP_TIMER_CHANNEL_ID,
|
||||
getString(R.string.sleep_timer_notification_channel),
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
mNotificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the provided time in milliseconds in a human readable format.
|
||||
*
|
||||
|
||||
12
app/src/main/res/drawable/ic_follow_location.xml
Normal file
12
app/src/main/res/drawable/ic_follow_location.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="800dp"
|
||||
android:height="800dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M21.616,25.777C21.433,25.927 21.216,26 21,26c-0.297,0 -0.591,-0.139 -0.789,-0.404L17,21.087v5.81C17,27.506 16.552,28 16,28s-1,-0.494 -1,-1.102v-5.81l-3.212,4.509c-0.34,0.458 -0.968,0.538 -1.404,0.181c-0.435,-0.358 -0.512,-1.019 -0.172,-1.477l5,-6.917c0.379,-0.511 1.197,-0.511 1.576,0l5,6.917C22.128,24.758 22.051,25.419 21.616,25.777zM6.803,20.404c-0.329,-0.443 -0.956,-0.536 -1.399,-0.206c-0.443,0.329 -0.536,0.956 -0.206,1.399c0.329,0.443 0.956,0.536 1.399,0.206C7.04,21.473 7.132,20.847 6.803,20.404z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M13.97,13.283l1.719,2.314c0.178,0.239 0.108,0.58 -0.149,0.73l-6.952,4.057c-0.22,0.128 -0.501,0.071 -0.653,-0.134l-0.659,-0.887C7.124,19.159 7.15,18.873 7.337,18.7l5.892,-5.484C13.446,13.012 13.793,13.044 13.97,13.283zM21.667,4.498l-6.933,7.435c-0.17,0.176 -0.187,0.449 -0.041,0.645l1.88,2.531c0.146,0.196 0.412,0.258 0.629,0.147l9.12,-4.492c0.275,-0.141 0.357,-0.495 0.173,-0.743l-4.067,-5.474C22.244,4.299 21.881,4.276 21.667,4.498z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_location_share.xml
Normal file
11
app/src/main/res/drawable/ic_location_share.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="800dp"
|
||||
android:height="800dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M9.611,12.4L10.818,18.535C11.046,19.694 12.603,19.924 13.156,18.882L19.021,7.843C19.248,7.416 19.201,6.944 18.974,6.584M9.611,12.4L5.226,8.155C4.417,7.371 4.972,6 6.099,6H17.913C18.376,6 18.757,6.241 18.974,6.584M9.611,12.4L18.974,6.584M19.056,6.533L18.974,6.584"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
</vector>
|
||||
@@ -21,5 +21,7 @@
|
||||
|
||||
<!-- Share Location Funtionality Strings -->
|
||||
<string name="title_share_location">Share Location</string>
|
||||
<string name="share_location_notification_channel">ShareLocation</string>
|
||||
<string name="share_location_notification_content">You are sharing your current location within the family</string>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user