diff --git a/CHANGELOG.md b/CHANGELOG.md index db7d8b998..56fa7c7b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Add new webxdc.isAppSender and webxdc.isBroadcast APIs for mini-apps +* Fix: Location streaming now works correctly with multiple accounts ## v2.50.0 2026-05 diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/ActiveLocationChats.java b/src/main/java/org/thoughtcrime/securesms/geolocation/ActiveLocationChats.java deleted file mode 100644 index a99874222..000000000 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/ActiveLocationChats.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.thoughtcrime.securesms.geolocation; - -import android.content.Context; -import android.content.SharedPreferences; -import java.util.HashSet; -import java.util.Set; - -public final class ActiveLocationChats { - - private static final String PREFS_NAME = "location_streaming"; - private static final String KEY_ACTIVE = "active_chat_ids"; - - private ActiveLocationChats() {} - - private static SharedPreferences prefs(Context context) { - return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - } - - /** - * Add a chat. Uses commit() to guarantee the write reaches disk before the process can die to - * preserve the superset invariant. - */ - static void add(Context context, int chatId) { - Set current = new HashSet<>(getAll(context)); - current.add(String.valueOf(chatId)); - prefs(context).edit().putStringSet(KEY_ACTIVE, current).commit(); - } - - public static void remove(Context context, int chatId) { - Set current = new HashSet<>(getAll(context)); - current.remove(String.valueOf(chatId)); - prefs(context).edit().putStringSet(KEY_ACTIVE, current).apply(); - } - - static void clear(Context context) { - prefs(context).edit().remove(KEY_ACTIVE).apply(); - } - - static Set getAllIds(Context context) { - Set ids = new HashSet<>(); - for (String s : getAll(context)) { - try { - ids.add(Integer.parseInt(s)); - } catch (NumberFormatException ignored) { - } - } - return ids; - } - - private static Set getAll(Context context) { - return prefs(context).getStringSet(KEY_ACTIVE, new HashSet<>()); - } -} diff --git a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationStreamingService.java b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationStreamingService.java index 72ada35df..e2aa8bc15 100644 --- a/src/main/java/org/thoughtcrime/securesms/geolocation/LocationStreamingService.java +++ b/src/main/java/org/thoughtcrime/securesms/geolocation/LocationStreamingService.java @@ -17,13 +17,15 @@ import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import androidx.core.app.ServiceCompat; import androidx.core.content.ContextCompat; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; import org.thoughtcrime.securesms.ConversationListActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.connect.DcHelper; public class LocationStreamingService extends Service { - private static final String TAG = "LocationStreamingService"; + private static final String TAG = "LocationStreamingSvc"; private static final String ACTION_STOP = "org.thoughtcrime.securesms.geolocation.STOP_STREAMING"; private static final int NOTIFICATION_ID = 8801; private static final String CHANNEL_ID = "location_streaming"; @@ -36,38 +38,63 @@ public class LocationStreamingService extends Service { // static API /** Register a chat for location updates, then ensure the service is running. */ - public static void startSharing(Context context, int chatId, int durationSeconds) { - ActiveLocationChats.add(context, chatId); - DcHelper.getContext(context).sendLocationsToChat(chatId, durationSeconds); - ContextCompat.startForegroundService( - context, new Intent(context, LocationStreamingService.class)); - } - - /** Unregister a chat. If no chats remain, stop the service. */ - public static void stopSharing(Context context, int chatId) { - ActiveLocationChats.remove(context, chatId); - DcHelper.getContext(context).sendLocationsToChat(chatId, 0); - if (!DcHelper.getContext(context).isSendingLocationsToChat(0)) { - context.stopService(new Intent(context, LocationStreamingService.class)); - } - } - - public static void ensureRunning(Context context) { - if (!hasLocationPermission(context)) { - for (int chatId : ActiveLocationChats.getAllIds(context)) { - DcHelper.getContext(context).sendLocationsToChat(chatId, 0); - } - ActiveLocationChats.clear(context); + public static void startSharing(Context context, int accountId, int chatId, int durationSeconds) { + try { + DcHelper.getRpc(context).sendLocationsToChat(accountId, chatId, durationSeconds); + } catch (RpcException e) { + Log.e(TAG, "Failed to start location streaming", e); return; } ContextCompat.startForegroundService( context, new Intent(context, LocationStreamingService.class)); } + /** Unregister a chat. If no chats remain, stop the service. */ + public static void stopSharing(Context context, int accountId, int chatId) { + try { + DcHelper.getRpc(context).sendLocationsToChat(accountId, chatId, 0); + } catch (RpcException e) { + Log.e(TAG, "Failed to stop location streaming", e); + } + if (!isAnySharingActive(context)) { + context.stopService(new Intent(context, LocationStreamingService.class)); + } + } + + public static void ensureRunning(Context context) { + if (!hasLocationPermission(context)) { + try { + DcHelper.getRpc(context).stopSendingLocations(); + } catch (RpcException e) { + Log.e(TAG, "Failed to stop location streaming", e); + } + return; + } + if (isAnySharingActive(context)) { + ContextCompat.startForegroundService( + context, new Intent(context, LocationStreamingService.class)); + } + } + public static boolean isRunning() { return running; } + private static boolean isAnySharingActive(Context context) { + try { + Rpc rpc = DcHelper.getRpc(context); + for (int accountId : rpc.getAllAccountIds()) { + if (rpc.isSendingLocations(accountId)) { + return true; + } + } + } catch (RpcException e) { + Log.e(TAG, "Failed to check location streaming state", e); + return true; + } + return false; + } + // lifecycle @Override @@ -86,7 +113,7 @@ public class LocationStreamingService extends Service { @Override public int onStartCommand(@Nullable Intent intent, int flags, int startId) { if (intent != null && ACTION_STOP.equals(intent.getAction())) { - stopAllSharing(); + stopAllSharing(this); stopSelf(); return START_NOT_STICKY; } @@ -100,11 +127,12 @@ public class LocationStreamingService extends Service { return START_STICKY; } - private void stopAllSharing() { - for (int chatId : ActiveLocationChats.getAllIds(this)) { - DcHelper.getContext(this).sendLocationsToChat(chatId, 0); + private static void stopAllSharing(Context context) { + try { + DcHelper.getRpc(context).stopSendingLocations(); + } catch (RpcException e) { + Log.e(TAG, "Failed to stop sending locations", e); } - ActiveLocationChats.clear(this); } @Nullable @@ -150,16 +178,21 @@ public class LocationStreamingService extends Service { private void publishAndWrite(Location location) { LocationData.getInstance().post(location); - boolean keepGoing = - DcHelper.getContext(this) - .setLocation( - (float) location.getLatitude(), - (float) location.getLongitude(), - location.getAccuracy()); + boolean keepGoing; + try { + keepGoing = + DcHelper.getRpc(this) + .setLocation( + (float) location.getLatitude(), + (float) location.getLongitude(), + location.getAccuracy()); + } catch (RpcException e) { + Log.e(TAG, "Failed to set location", e); + return; + } Log.d(TAG, "keepGoing: " + keepGoing); if (!keepGoing) { - stopAllSharing(); stopSelf(); } } diff --git a/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index 4276beb72..feecb0c6e 100644 --- a/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -36,6 +36,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import chat.delta.rpc.Rpc; import chat.delta.rpc.RpcException; import chat.delta.util.ListenableFuture; import chat.delta.util.SettableFuture; @@ -63,7 +64,6 @@ import org.thoughtcrime.securesms.components.audioplay.AudioPlaybackViewModel; import org.thoughtcrime.securesms.components.audioplay.AudioView; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.database.AttachmentDatabase; -import org.thoughtcrime.securesms.geolocation.ActiveLocationChats; import org.thoughtcrime.securesms.geolocation.LocationStreamingService; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; @@ -491,16 +491,29 @@ public class AttachmentManager { public static void selectLocation(Activity activity, int chatId) { Context appContext = activity.getApplicationContext(); + Rpc rpc = DcHelper.getRpc(appContext); + int accountId = DcHelper.getContext(appContext).getAccountId(); - if (DcHelper.getContext(appContext).isSendingLocationsToChat(chatId)) { + boolean currentlySharing; + try { + currentlySharing = rpc.isSendingLocationsToChat(accountId, chatId); + } catch (RpcException e) { + Log.e(TAG, "Failed to check location streaming state", e); + return; + } + + if (currentlySharing) { if (LocationStreamingService.isRunning()) { - LocationStreamingService.stopSharing(appContext, chatId); + LocationStreamingService.stopSharing(appContext, accountId, chatId); return; } // Stale — service is dead but chat layer still thinks it's sharing. - // Clean up this chat and fall through to the fresh start flow. - ActiveLocationChats.remove(appContext, chatId); - DcHelper.getContext(appContext).sendLocationsToChat(chatId, 0); + // Clean up and fall through to the fresh start flow. + try { + rpc.sendLocationsToChat(accountId, chatId, 0); + } catch (RpcException e) { + Log.e(TAG, "Failed to stop stale location streaming", e); + } } Permissions.with(activity) @@ -514,7 +527,8 @@ public class AttachmentManager { ShareLocationDialog.show( activity, durationInSeconds -> - LocationStreamingService.startSharing(appContext, chatId, durationInSeconds)); + LocationStreamingService.startSharing( + appContext, accountId, chatId, durationInSeconds)); }) .request( android.Manifest.permission.ACCESS_FINE_LOCATION,