From 09f389d6de9532de601b0828a26a4a80d94c7bdd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:00:57 +0000 Subject: [PATCH 01/14] Initial plan From db10790ecd891af6b9db3b634567251dc0c6c68a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:07:35 +0000 Subject: [PATCH 02/14] Implement foreground notification for location sharing Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- .../geolocation/DcLocationManager.java | 24 +++++++-- .../LocationBackgroundService.java | 53 +++++++++++++++++++ .../securesms/mms/AttachmentManager.java | 12 ++--- .../notifications/NotificationCenter.java | 2 + src/main/res/values/strings.xml | 2 + 5 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/DcLocationManager.java b/src/main/java/org/thoughtcrime/securesms/geolocation/DcLocationManager.java index 1876caa2c..a9b1bbcf7 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/DcLocationManager.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/DcLocationManager.java @@ -5,9 +5,12 @@ import android.content.Intent; import android.content.ServiceConnection; import android.location.Location; +import android.os.Build; import android.os.IBinder; import android.util.Log; +import androidx.core.content.ContextCompat; + import org.thoughtcrime.securesms.connect.DcHelper; import java.util.LinkedList; @@ -23,11 +26,13 @@ public class DcLocationManager implements Observer { private final Context context; private DcLocation dcLocation = DcLocation.getInstance(); private final LinkedList pendingShareLastLocation = new LinkedList<>(); + private boolean serviceBound = false; private final ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d(TAG, "background service connected"); serviceBinder = (LocationBackgroundService.LocationBackgroundServiceBinder) service; + serviceBound = true; while (!pendingShareLastLocation.isEmpty()) { shareLastLocation(pendingShareLastLocation.pop()); } @@ -37,6 +42,7 @@ public void onServiceConnected(ComponentName name, IBinder service) { public void onServiceDisconnected(ComponentName name) { Log.d(TAG, "background service disconnected"); serviceBinder = null; + serviceBound = false; } }; @@ -49,19 +55,29 @@ public DcLocationManager(Context context) { } public void startLocationEngine() { - if (serviceBinder == null) { + if (serviceBinder == null && !serviceBound) { Intent intent = new Intent(context.getApplicationContext(), LocationBackgroundService.class); + // Start as foreground service + ContextCompat.startForegroundService(context, intent); + // Then bind to it context.bindService(intent, serviceConnection, BIND_AUTO_CREATE); } } public void stopLocationEngine() { - if (serviceBinder == null) { + if (serviceBinder == null && !serviceBound) { return; } - context.unbindService(serviceConnection); - serviceBinder.stop(); + try { + context.unbindService(serviceConnection); + if (serviceBinder != null) { + serviceBinder.stop(); + } + } catch (IllegalArgumentException e) { + Log.w(TAG, "Service not registered", e); + } serviceBinder = null; + serviceBound = false; } public void stopSharingLocation(int chatId) { diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java index 4b17f1d27..6d61b8927 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java @@ -1,5 +1,9 @@ package org.thoughtcrime.securesms.geolocation; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; @@ -8,11 +12,18 @@ import android.location.LocationListener; import android.location.LocationManager; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; + +import org.thoughtcrime.securesms.ConversationListActivity; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.notifications.NotificationCenter; +import org.thoughtcrime.securesms.util.IntentUtils; public class LocationBackgroundService extends Service { @@ -37,6 +48,14 @@ public IBinder onBind(Intent intent) { @Override public void onCreate() { + super.onCreate(); + + // Create notification channel if needed + createNotificationChannel(); + + // Start foreground service with notification + startForeground(NotificationCenter.ID_LOCATION, createNotification()); + locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE); if (locationManager == null) { Log.e(TAG, "Unable to initialize location service"); @@ -77,6 +96,40 @@ public void onDestroy() { } } + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + NotificationCenter.CH_LOCATION, + getString(R.string.location), + NotificationManager.IMPORTANCE_LOW + ); + channel.setDescription("Location sharing notification"); + NotificationManager notificationManager = getSystemService(NotificationManager.class); + if (notificationManager != null) { + notificationManager.createNotificationChannel(channel); + } + } + } + + private Notification createNotification() { + Intent intent = new Intent(this, ConversationListActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity( + this, + 0, + intent, + IntentUtils.FLAG_IMMUTABLE + ); + + return new NotificationCompat.Builder(this, NotificationCenter.CH_LOCATION) + .setContentTitle(getString(R.string.location_sharing_notification_title)) + .setContentText(getString(R.string.location_sharing_notification_text)) + .setSmallIcon(R.drawable.ic_location_on_white_24dp) + .setContentIntent(pendingIntent) + .setOngoing(true) + .setPriority(NotificationCompat.PRIORITY_LOW) + .build(); + } + private void requestLocationUpdate(String provider) { try { locationManager.requestLocationUpdates( diff --git a/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index c0dde8289..5deb8bad1 100644 --- a/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -484,7 +484,7 @@ public static void selectLocation(Activity activity, int chatId) { // for rationale dialog requirements Permissions.PermissionsBuilder permissionsBuilder = Permissions.with(activity) .ifNecessary() - .withRationaleDialog("To share your live location with chat members, allow ArcaneChat to use your location data.\n\nTo make live location work gaplessly, location data is used even when the app is closed or not in use.", R.drawable.ic_location_on_white_24dp) + .withRationaleDialog("To share your live location with chat members, allow ArcaneChat to use your location data.", R.drawable.ic_location_on_white_24dp) .withPermanentDenialDialog(activity.getString(R.string.perm_explain_access_to_location_denied)) .onAllGranted(() -> { ShareLocationDialog.show(activity, durationInSeconds -> { @@ -494,13 +494,9 @@ public static void selectLocation(Activity activity, int chatId) { dcLocationManager.shareLocation(durationInSeconds, chatId); } }); - }); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { - permissionsBuilder.request(Manifest.permission.ACCESS_BACKGROUND_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION); - } else { - permissionsBuilder.request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION); - } - permissionsBuilder.execute(); + }) + .request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION) + .execute(); } private @Nullable Uri getSlideUri() { diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java index 09e94c250..170abb27f 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCenter.java @@ -218,6 +218,7 @@ public PendingIntent getDeclineCallIntent(ChatData chatData, int callId) { public static final int ID_MSG_SUMMARY = 2; public static final int ID_GENERIC = 3; public static final int ID_FETCH = 4; + public static final int ID_LOCATION = 5; public static final int ID_MSG_OFFSET = 0; // msgId is added - as msgId start at 10, there are no conflicts with lower numbers @@ -243,6 +244,7 @@ public PendingIntent getDeclineCallIntent(ChatData chatData, int callId) { public static final String CH_MSG_VERSION = "5"; public static final String CH_PERMANENT = "dc_fg_notification_ch"; public static final String CH_GENERIC = "ch_generic"; + public static final String CH_LOCATION = "ch_location"; public static final String CH_CALLS_PREFIX = "call_chan"; private boolean notificationChannelsSupported() { diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 44cb825db..f02b37678 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -358,6 +358,8 @@ Copy JSON Replace Draft Share location with all group members + Sharing location + Location is being shared with chat members Device Messages Locally generated messages Messages in this chat are generated on your device to inform about app updates and problems during usage. From c9af561868e44498583c8fc7f4c032b1e93559d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:10:28 +0000 Subject: [PATCH 03/14] Add FOREGROUND_SERVICE_LOCATION permission Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index c6a3ba9ba..daa33162c 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -39,6 +39,7 @@ + From 8675f816decbeec60671be2a2de0844c8383cdff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:12:34 +0000 Subject: [PATCH 04/14] Fix code review feedback: improve error handling and service logic Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- .../securesms/geolocation/DcLocationManager.java | 2 +- .../securesms/geolocation/LocationBackgroundService.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/DcLocationManager.java b/src/main/java/org/thoughtcrime/securesms/geolocation/DcLocationManager.java index a9b1bbcf7..f253014f6 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/DcLocationManager.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/DcLocationManager.java @@ -65,7 +65,7 @@ public void startLocationEngine() { } public void stopLocationEngine() { - if (serviceBinder == null && !serviceBound) { + if (serviceBinder == null || !serviceBound) { return; } try { diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java index 6d61b8927..4162dda5a 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java @@ -53,15 +53,16 @@ public void onCreate() { // Create notification channel if needed createNotificationChannel(); - // Start foreground service with notification - startForeground(NotificationCenter.ID_LOCATION, createNotification()); - locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE); if (locationManager == null) { Log.e(TAG, "Unable to initialize location service"); + stopSelf(); return; } + // Start foreground service with notification after successful initialization + startForeground(NotificationCenter.ID_LOCATION, createNotification()); + locationListener = new ServiceLocationListener(); Location lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (lastLocation != null) { From b8d6c0b4815f55a690141c751e4ba8207f132f6d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:13:51 +0000 Subject: [PATCH 05/14] Fix service binding logic and foreground service lifecycle Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- .../securesms/geolocation/DcLocationManager.java | 2 +- .../securesms/geolocation/LocationBackgroundService.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/DcLocationManager.java b/src/main/java/org/thoughtcrime/securesms/geolocation/DcLocationManager.java index f253014f6..a85696ad5 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/DcLocationManager.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/DcLocationManager.java @@ -55,7 +55,7 @@ public DcLocationManager(Context context) { } public void startLocationEngine() { - if (serviceBinder == null && !serviceBound) { + if (serviceBinder == null || !serviceBound) { Intent intent = new Intent(context.getApplicationContext(), LocationBackgroundService.class); // Start as foreground service ContextCompat.startForegroundService(context, intent); diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java index 4162dda5a..cb0f57dd9 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java @@ -53,16 +53,18 @@ public void onCreate() { // Create notification channel if needed createNotificationChannel(); + // Start foreground service with notification - required for foreground services + startForeground(NotificationCenter.ID_LOCATION, createNotification()); + locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE); if (locationManager == null) { Log.e(TAG, "Unable to initialize location service"); + // Stop the service since we can't function without location manager + stopForeground(true); stopSelf(); return; } - // Start foreground service with notification after successful initialization - startForeground(NotificationCenter.ID_LOCATION, createNotification()); - locationListener = new ServiceLocationListener(); Location lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (lastLocation != null) { From 61fc7019b2452562dfb752e05386f286d8a761d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:15:09 +0000 Subject: [PATCH 06/14] Improve foreground service lifecycle handling Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- .../geolocation/LocationBackgroundService.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java index cb0f57dd9..e2afb76e5 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java @@ -81,6 +81,13 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); + + // Ensure foreground notification is shown even if onCreate hasn't been called yet + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createNotificationChannel(); + startForeground(NotificationCenter.ID_LOCATION, createNotification()); + } + return START_STICKY; } @@ -88,6 +95,9 @@ public int onStartCommand(Intent intent, int flags, int startId) { public void onDestroy() { super.onDestroy(); + // Stop foreground notification + stopForeground(true); + if (locationManager == null) { return; } From b3abc390592317f9520ec3b08ae3f19ada13ab60 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:17:30 +0000 Subject: [PATCH 07/14] Add FLAG_IMMUTABLE and improve foreground service initialization Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- .../LocationBackgroundService.java | 22 ++++++++++--------- .../securesms/util/IntentUtils.java | 8 +++++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java index e2afb76e5..9aa914fec 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java @@ -33,6 +33,7 @@ public class LocationBackgroundService extends Service { private static final int LOCATION_INTERVAL = 1000; private static final float LOCATION_DISTANCE = 25F; ServiceLocationListener locationListener; + private boolean isForeground = false; private final IBinder mBinder = new LocationBackgroundServiceBinder(); @@ -50,11 +51,7 @@ public IBinder onBind(Intent intent) { public void onCreate() { super.onCreate(); - // Create notification channel if needed - createNotificationChannel(); - - // Start foreground service with notification - required for foreground services - startForeground(NotificationCenter.ID_LOCATION, createNotification()); + initializeForegroundService(); locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE); if (locationManager == null) { @@ -82,11 +79,8 @@ public void onCreate() { public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); - // Ensure foreground notification is shown even if onCreate hasn't been called yet - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createNotificationChannel(); - startForeground(NotificationCenter.ID_LOCATION, createNotification()); - } + // Ensure foreground notification is shown (handles edge cases) + initializeForegroundService(); return START_STICKY; } @@ -109,6 +103,14 @@ public void onDestroy() { } } + private void initializeForegroundService() { + if (!isForeground) { + createNotificationChannel(); + startForeground(NotificationCenter.ID_LOCATION, createNotification()); + isForeground = true; + } + } + private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( diff --git a/src/main/java/org/thoughtcrime/securesms/util/IntentUtils.java b/src/main/java/org/thoughtcrime/securesms/util/IntentUtils.java index 70c4544cc..895eb0bbb 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/IntentUtils.java +++ b/src/main/java/org/thoughtcrime/securesms/util/IntentUtils.java @@ -39,4 +39,12 @@ public static int FLAG_MUTABLE() { return 0; } } + + public static int FLAG_IMMUTABLE() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return PendingIntent.FLAG_IMMUTABLE; + } else { + return 0; + } + } } From df50a6d60809df0a00d9f982d066134aac30f90c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:19:30 +0000 Subject: [PATCH 08/14] Fix method call, thread safety, and service initialization order Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- .../LocationBackgroundService.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java index 9aa914fec..9b76a011f 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java @@ -25,6 +25,8 @@ import org.thoughtcrime.securesms.notifications.NotificationCenter; import org.thoughtcrime.securesms.util.IntentUtils; +import java.util.concurrent.atomic.AtomicBoolean; + public class LocationBackgroundService extends Service { private static final int INITIAL_TIMEOUT = 1000 * 60 * 2; @@ -33,7 +35,7 @@ public class LocationBackgroundService extends Service { private static final int LOCATION_INTERVAL = 1000; private static final float LOCATION_DISTANCE = 25F; ServiceLocationListener locationListener; - private boolean isForeground = false; + private final AtomicBoolean isForeground = new AtomicBoolean(false); private final IBinder mBinder = new LocationBackgroundServiceBinder(); @@ -51,17 +53,22 @@ public IBinder onBind(Intent intent) { public void onCreate() { super.onCreate(); - initializeForegroundService(); - locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE); if (locationManager == null) { Log.e(TAG, "Unable to initialize location service"); - // Stop the service since we can't function without location manager + // Initialize foreground first, then stop + initializeForegroundService(); stopForeground(true); stopSelf(); return; } + // Initialize foreground service after successful location manager setup + initializeForegroundService(); + stopSelf(); + return; + } + locationListener = new ServiceLocationListener(); Location lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (lastLocation != null) { @@ -104,10 +111,9 @@ public void onDestroy() { } private void initializeForegroundService() { - if (!isForeground) { + if (isForeground.compareAndSet(false, true)) { createNotificationChannel(); startForeground(NotificationCenter.ID_LOCATION, createNotification()); - isForeground = true; } } @@ -132,7 +138,7 @@ private Notification createNotification() { this, 0, intent, - IntentUtils.FLAG_IMMUTABLE + IntentUtils.FLAG_IMMUTABLE() ); return new NotificationCompat.Builder(this, NotificationCenter.CH_LOCATION) From 6e48933a8f843580d0df2abdb4fe15487c53e2a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:21:00 +0000 Subject: [PATCH 09/14] Remove orphaned code from LocationBackgroundService Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- .../securesms/geolocation/LocationBackgroundService.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java index 9b76a011f..beb720a2f 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java @@ -65,9 +65,6 @@ public void onCreate() { // Initialize foreground service after successful location manager setup initializeForegroundService(); - stopSelf(); - return; - } locationListener = new ServiceLocationListener(); Location lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); From ca65424653681b77d062238804337a3445896c47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:22:58 +0000 Subject: [PATCH 10/14] Ensure foreground service requirements are met even on error Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- .../securesms/geolocation/LocationBackgroundService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java index beb720a2f..2a80eadd0 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java @@ -56,7 +56,7 @@ public void onCreate() { locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE); if (locationManager == null) { Log.e(TAG, "Unable to initialize location service"); - // Initialize foreground first, then stop + // Must start foreground to avoid crash, then stop immediately initializeForegroundService(); stopForeground(true); stopSelf(); From 7633338359e101127de93f24a8d96d81de527f53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:43:58 +0000 Subject: [PATCH 11/14] Fix PermissionsBuilder method chaining - request and execute must be separate calls Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- .../org/thoughtcrime/securesms/mms/AttachmentManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index 5deb8bad1..787d34d85 100644 --- a/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -494,9 +494,9 @@ public static void selectLocation(Activity activity, int chatId) { dcLocationManager.shareLocation(durationInSeconds, chatId); } }); - }) - .request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION) - .execute(); + }); + permissionsBuilder.request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION); + permissionsBuilder.execute(); } private @Nullable Uri getSlideUri() { From adffc4f6f85ace8ec052a5df31aad278f6c42ed3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 21:22:29 +0000 Subject: [PATCH 12/14] Fix foreground service for Android 14+ and add NETWORK_PROVIDER fallback Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- .../geolocation/LocationBackgroundService.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java index 2a80eadd0..d3ba37c40 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java @@ -8,6 +8,7 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.ServiceInfo; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; @@ -19,6 +20,7 @@ import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; +import androidx.core.app.ServiceCompat; import org.thoughtcrime.securesms.ConversationListActivity; import org.thoughtcrime.securesms.R; @@ -74,8 +76,9 @@ public void onCreate() { DcLocation.getInstance().updateLocation(lastLocation); } } - //requestLocationUpdate(LocationManager.NETWORK_PROVIDER); + // Request location updates from both GPS and network providers for better coverage requestLocationUpdate(LocationManager.GPS_PROVIDER); + requestLocationUpdate(LocationManager.NETWORK_PROVIDER); initialLocationUpdate(); } @@ -110,7 +113,15 @@ public void onDestroy() { private void initializeForegroundService() { if (isForeground.compareAndSet(false, true)) { createNotificationChannel(); - startForeground(NotificationCenter.ID_LOCATION, createNotification()); + Notification notification = createNotification(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + // Android 14+ requires foregroundServiceType in startForeground + ServiceCompat.startForeground(this, NotificationCenter.ID_LOCATION, notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION); + } else { + startForeground(NotificationCenter.ID_LOCATION, notification); + } + Log.d(TAG, "Foreground service started with notification"); } } From 9dcd75fe547797765c40563ab8f60ac7c7de54a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 21:47:02 +0000 Subject: [PATCH 13/14] Use LocationManagerCompat for proper location updates on modern Android Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- .../LocationBackgroundService.java | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java index d3ba37c40..51b479d96 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java @@ -21,12 +21,16 @@ import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import androidx.core.app.ServiceCompat; +import androidx.core.content.ContextCompat; +import androidx.core.location.LocationManagerCompat; +import androidx.core.location.LocationRequestCompat; import org.thoughtcrime.securesms.ConversationListActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.notifications.NotificationCenter; import org.thoughtcrime.securesms.util.IntentUtils; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; public class LocationBackgroundService extends Service { @@ -161,10 +165,28 @@ private Notification createNotification() { private void requestLocationUpdate(String provider) { try { - locationManager.requestLocationUpdates( - provider, LOCATION_INTERVAL, LOCATION_DISTANCE, - locationListener); - } catch (SecurityException | IllegalArgumentException ex) { + // Check if provider is available + if (!locationManager.isProviderEnabled(provider)) { + Log.w(TAG, String.format("Provider %s is not enabled", provider)); + return; + } + + // Use LocationManagerCompat for better compatibility with modern Android + LocationRequestCompat locationRequest = new LocationRequestCompat.Builder(LOCATION_INTERVAL) + .setMinUpdateDistanceMeters(LOCATION_DISTANCE) + .setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY) + .build(); + + Executor executor = ContextCompat.getMainExecutor(this); + LocationManagerCompat.requestLocationUpdates( + locationManager, + provider, + locationRequest, + executor, + locationListener + ); + Log.d(TAG, String.format("Requested location updates from %s provider", provider)); + } catch (SecurityException | IllegalArgumentException ex) { Log.e(TAG, String.format("Unable to request %s provider based location updates.", provider), ex); } } @@ -175,9 +197,16 @@ private void initialLocationUpdate() { if (gpsLocation != null && System.currentTimeMillis() - gpsLocation.getTime() < INITIAL_TIMEOUT) { locationListener.onLocationChanged(gpsLocation); } - + // Also try network provider for initial location + Location networkLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + if (networkLocation != null && System.currentTimeMillis() - networkLocation.getTime() < INITIAL_TIMEOUT) { + // Use network location if GPS location is not available or network location is newer + if (gpsLocation == null || networkLocation.getTime() > gpsLocation.getTime()) { + locationListener.onLocationChanged(networkLocation); + } + } } catch (NullPointerException | SecurityException e) { - e.printStackTrace(); + Log.e(TAG, "Error getting initial location", e); } } From 469259f46983a1cfed42b91077beae5ceec3d384 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 21:53:16 +0000 Subject: [PATCH 14/14] Fix LocationListenerCompat compilation error - use correct interface for LocationManagerCompat Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com> --- .../LocationBackgroundService.java | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java index 51b479d96..7a8bfb50b 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationBackgroundService.java @@ -10,11 +10,9 @@ import android.content.ServiceConnection; import android.content.pm.ServiceInfo; import android.location.Location; -import android.location.LocationListener; import android.location.LocationManager; import android.os.Binder; import android.os.Build; -import android.os.Bundle; import android.os.IBinder; import android.util.Log; @@ -22,6 +20,7 @@ import androidx.core.app.NotificationCompat; import androidx.core.app.ServiceCompat; import androidx.core.content.ContextCompat; +import androidx.core.location.LocationListenerCompat; import androidx.core.location.LocationManagerCompat; import androidx.core.location.LocationRequestCompat; @@ -103,12 +102,12 @@ public void onDestroy() { // Stop foreground notification stopForeground(true); - if (locationManager == null) { + if (locationManager == null || locationListener == null) { return; } try { - locationManager.removeUpdates(locationListener); + LocationManagerCompat.removeUpdates(locationManager, locationListener); } catch (Exception ex) { Log.i(TAG, "fail to remove location listeners, ignore", ex); } @@ -221,30 +220,22 @@ void stop() { } } - private class ServiceLocationListener implements LocationListener { + private class ServiceLocationListener implements LocationListenerCompat { @Override public void onLocationChanged(@NonNull Location location) { Log.d(TAG, "onLocationChanged: " + location); - if (location == null) { - return; - } DcLocation.getInstance().updateLocation(location); } @Override public void onProviderDisabled(@NonNull String provider) { - Log.e(TAG, "onProviderDisabled: " + provider); + Log.w(TAG, "onProviderDisabled: " + provider); } @Override public void onProviderEnabled(@NonNull String provider) { - Log.e(TAG, "onProviderEnabled: " + provider); - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - Log.e(TAG, "onStatusChanged: " + provider + " status: " + status); + Log.d(TAG, "onProviderEnabled: " + provider); } }