diff --git a/CHANGELOG.md b/CHANGELOG.md index f18047039..b89e96a7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * Fix: Update menu when using "select all" in contact selection * Fix: Avoid empty profiles after using "add as second device" from welcome screen * Fix multi-device seen messages synchronization when using multiple relays +* Avoid crash when the app is minimized with profile switcher or reactions dialogs open * Update to core 2.39.0 ## v2.35.0 diff --git a/jni/dc_wrapper.c b/jni/dc_wrapper.c index b51257bdb..eccca6f8a 100644 --- a/jni/dc_wrapper.c +++ b/jni/dc_wrapper.c @@ -153,6 +153,42 @@ static uint32_t* jintArray2uint32Pointer(JNIEnv* env, jintArray ja, int* ret_icn } +/************************************************************ + * DcEventChannel + ************************************************************/ + +static dc_event_channel_t* get_dc_event_channel(JNIEnv *env, jobject obj) +{ + static jfieldID fid = 0; + if (fid==0) { + jclass cls = (*env)->GetObjectClass(env, obj); + fid = (*env)->GetFieldID(env, cls, "eventChannelCPtr", "J" /*Signature, J=long*/); + } + if (fid) { + return (dc_event_channel_t*)(*env)->GetLongField(env, obj, fid); + } + return NULL; +} + + +JNIEXPORT jlong Java_com_b44t_messenger_DcEventChannel_createEventChannelCPtr(JNIEnv *env, jobject obj) +{ + return (jlong)dc_event_channel_new(); +} + + +JNIEXPORT void Java_com_b44t_messenger_DcEventChannel_unrefEventChannelCPtr(JNIEnv *env, jobject obj) +{ + dc_event_channel_unref(get_dc_event_channel(env, obj)); +} + + +JNIEXPORT jlong Java_com_b44t_messenger_DcEventChannel_getEventEmitterCPtr(JNIEnv *env, jobject obj) +{ + return (jlong)dc_event_channel_get_event_emitter(get_dc_event_channel(env, obj)); +} + + /******************************************************************************* * DcAccounts ******************************************************************************/ @@ -172,11 +208,11 @@ static dc_accounts_t* get_dc_accounts(JNIEnv *env, jobject obj) } -JNIEXPORT jlong Java_com_b44t_messenger_DcAccounts_createAccountsCPtr(JNIEnv *env, jobject obj, jstring dir) +JNIEXPORT jlong Java_com_b44t_messenger_DcAccounts_createAccountsCPtr(JNIEnv *env, jobject obj, jstring dir, jobject chanObj) { CHAR_REF(dir); int writable = 1; - jlong accountsCPtr = (jlong)dc_accounts_new(dirPtr, writable); + jlong accountsCPtr = (jlong)dc_accounts_new_with_event_channel(dirPtr, writable, get_dc_event_channel(env, chanObj)); CHAR_UNREF(dir); return accountsCPtr; } diff --git a/src/main/java/com/b44t/messenger/DcAccounts.java b/src/main/java/com/b44t/messenger/DcAccounts.java index 669048b12..8a08984d3 100644 --- a/src/main/java/com/b44t/messenger/DcAccounts.java +++ b/src/main/java/com/b44t/messenger/DcAccounts.java @@ -2,8 +2,8 @@ package com.b44t.messenger; public class DcAccounts { - public DcAccounts(String dir) { - accountsCPtr = createAccountsCPtr(dir); + public DcAccounts(String dir, DcEventChannel channel) { + accountsCPtr = createAccountsCPtr(dir, channel); if (accountsCPtr == 0) throw new RuntimeException("createAccountsCPtr() returned null pointer"); } @@ -38,7 +38,7 @@ public class DcAccounts { // working with raw c-data private long accountsCPtr; // CAVE: the name is referenced in the JNI - private native long createAccountsCPtr (String dir); + private native long createAccountsCPtr (String dir, DcEventChannel channel); private native void unrefAccountsCPtr (); private native long getEventEmitterCPtr (); private native long getJsonrpcInstanceCPtr (); diff --git a/src/main/java/com/b44t/messenger/DcEventChannel.java b/src/main/java/com/b44t/messenger/DcEventChannel.java new file mode 100644 index 000000000..2c3ece1dd --- /dev/null +++ b/src/main/java/com/b44t/messenger/DcEventChannel.java @@ -0,0 +1,27 @@ +package com.b44t.messenger; + +public class DcEventChannel { + + public DcEventChannel() { + eventChannelCPtr = createEventChannelCPtr(); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + if (eventChannelCPtr != 0) { + unrefEventChannelCPtr(); + eventChannelCPtr = 0; + } + } + + public DcEventEmitter getEventEmitter() { + return new DcEventEmitter(getEventEmitterCPtr()); + } + + // working with raw c-data + private long eventChannelCPtr; // CAVE: the name is referenced in the JNI + private native long createEventChannelCPtr (); + private native void unrefEventChannelCPtr (); + private native long getEventEmitterCPtr (); +} diff --git a/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 6476842a3..37ade8c06 100644 --- a/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -23,6 +23,7 @@ import androidx.work.WorkManager; import com.b44t.messenger.DcAccounts; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEvent; +import com.b44t.messenger.DcEventChannel; import com.b44t.messenger.DcEventEmitter; import com.b44t.messenger.FFITransport; @@ -173,7 +174,29 @@ public class ApplicationContext extends MultiDexApplication { Util.runOnBackground(() -> { synchronized (initLock) { try { - dcAccounts = new DcAccounts(new File(getFilesDir(), "accounts").getAbsolutePath()); + DcEventChannel eventChannel = new DcEventChannel(); + DcEventEmitter emitter = eventChannel.getEventEmitter(); + eventCenter = new DcEventCenter(this); + + new Thread(() -> { + Log.i(TAG, "Starting event loop"); + while (true) { + DcEvent event = emitter.getNextEvent(); + if (event == null) { + break; + } + if (isInitialized) { + eventCenter.handleEvent(event); + } else { + // not fully initialized, only handle logging events, + // ex. account migrations during DcAccounts initialization + eventCenter.handleLogging(event); + } + } + Log.i("DeltaChat", "shutting down event handler"); + }, "eventThread").start(); + + dcAccounts = new DcAccounts(new File(getFilesDir(), "accounts").getAbsolutePath(), eventChannel); Log.i(TAG, "DcAccounts created"); rpc = new Rpc(new FFITransport(dcAccounts.getJsonrpcInstance())); Log.i(TAG, "Rpc created"); @@ -213,28 +236,12 @@ public class ApplicationContext extends MultiDexApplication { } dcContext = dcAccounts.getSelectedAccount(); notificationCenter = new NotificationCenter(this); - eventCenter = new DcEventCenter(this); dcLocationManager = new DcLocationManager(this, dcContext); - // Mark as initialized before starting threads that depend on it isInitialized = true; initLock.notifyAll(); Log.i(TAG, "DcAccounts initialization complete"); - new Thread(() -> { - Log.i(TAG, "Starting event loop"); - DcEventEmitter emitter = dcAccounts.getEventEmitter(); - Log.i(TAG, "DcEventEmitter obtained"); - while (true) { - DcEvent event = emitter.getNextEvent(); - if (event==null) { - break; - } - eventCenter.handleEvent(event); - } - Log.i("DeltaChat", "shutting down event handler"); - }, "eventThread").start(); - // set translations before starting I/O to avoid sending untranslated MDNs (issue #2288) DcHelper.setStockTranslations(this); diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java b/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java index 3ddc5bc90..ff834a0d1 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java @@ -875,7 +875,7 @@ public class ConversationFragment extends MessageSelectorFragment @Override public void onReactionClicked(DcMsg messageRecord) { - ReactionsDetailsFragment dialog = new ReactionsDetailsFragment(messageRecord.getId()); + ReactionsDetailsFragment dialog = ReactionsDetailsFragment.newInstance(messageRecord.getId()); dialog.show(getActivity().getSupportFragmentManager(), null); } } diff --git a/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java b/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java index 7e4e2adff..1506980cf 100644 --- a/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java @@ -47,20 +47,23 @@ import chat.delta.rpc.RpcException; public class AccountSelectionListFragment extends DialogFragment implements DcEventCenter.DcEventDelegate { private static final String TAG = AccountSelectionListFragment.class.getSimpleName(); - private final ConversationListActivity activity; + private static final String ARG_SELECT_ONLY = "select_only"; private RecyclerView recyclerView; private AccountSelectionListAdapter adapter; - private final boolean selectOnly; + private boolean selectOnly; - public AccountSelectionListFragment(ConversationListActivity activity, boolean selectOnly) { - super(); - this.activity = activity; - this.selectOnly = selectOnly; + public static AccountSelectionListFragment newInstance(boolean selectOnly) { + AccountSelectionListFragment fragment = new AccountSelectionListFragment(); + Bundle args = new Bundle(); + args.putBoolean(ARG_SELECT_ONLY, selectOnly); + fragment.setArguments(args); + return fragment; } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + selectOnly = getArguments() != null && getArguments().getBoolean(ARG_SELECT_ONLY, false); AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()) .setTitle(R.string.switch_account) .setNegativeButton(R.string.cancel, null); @@ -179,6 +182,7 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv } private void onSetTag(int accountId) { + ConversationListActivity activity = (ConversationListActivity)requireActivity(); AccountSelectionListFragment.this.dismiss(); DcContext dcContext = DcHelper.getAccounts(activity).getAccount(accountId); @@ -202,6 +206,7 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv private void onDeleteProfile(int accountId) { AccountSelectionListFragment.this.dismiss(); + ConversationListActivity activity = (ConversationListActivity)requireActivity(); DcAccounts accounts = DcHelper.getAccounts(activity); Rpc rpc = DcHelper.getRpc(activity); @@ -254,6 +259,7 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv @Override public void onItemClick(AccountSelectionListItem contact) { AccountSelectionListFragment.this.dismiss(); + ConversationListActivity activity = (ConversationListActivity)requireActivity(); int accountId = contact.getAccountId(); if (accountId == DC_CONTACT_ID_ADD_ACCOUNT) { AccountManager.getInstance().switchAccountAndStartActivity(activity, 0); diff --git a/src/main/java/org/thoughtcrime/securesms/connect/AccountManager.java b/src/main/java/org/thoughtcrime/securesms/connect/AccountManager.java index 2bdebe7fe..de8901b52 100644 --- a/src/main/java/org/thoughtcrime/securesms/connect/AccountManager.java +++ b/src/main/java/org/thoughtcrime/securesms/connect/AccountManager.java @@ -138,7 +138,7 @@ public class AccountManager { // ui public void showSwitchAccountMenu(ConversationListActivity activity, boolean selectOnly) { - AccountSelectionListFragment dialog = new AccountSelectionListFragment(activity, selectOnly); + AccountSelectionListFragment dialog = AccountSelectionListFragment.newInstance(selectOnly); dialog.show(((FragmentActivity) activity).getSupportFragmentManager(), null); } diff --git a/src/main/java/org/thoughtcrime/securesms/connect/DcEventCenter.java b/src/main/java/org/thoughtcrime/securesms/connect/DcEventCenter.java index 560f67888..f766a2e53 100644 --- a/src/main/java/org/thoughtcrime/securesms/connect/DcEventCenter.java +++ b/src/main/java/org/thoughtcrime/securesms/connect/DcEventCenter.java @@ -164,6 +164,23 @@ public class DcEventCenter { }); } + public void handleLogging(@NonNull DcEvent event) { + final String logPrefix = "[accId="+event.getAccountId()+ "] "; + switch (event.getId()) { + case DcContext.DC_EVENT_INFO: + Log.i("DeltaChat", logPrefix + event.getData2Str()); + break; + + case DcContext.DC_EVENT_WARNING: + Log.w("DeltaChat", logPrefix + event.getData2Str()); + break; + + case DcContext.DC_EVENT_ERROR: + Log.e("DeltaChat", logPrefix + event.getData2Str()); + break; + } + } + public long handleEvent(@NonNull DcEvent event) { int accountId = event.getAccountId(); int id = event.getId(); @@ -205,20 +222,7 @@ public class DcEventCenter { return 0; } - final String logPrefix = "[accId="+accountId + "] "; - switch (id) { - case DcContext.DC_EVENT_INFO: - Log.i("DeltaChat", logPrefix + event.getData2Str()); - break; - - case DcContext.DC_EVENT_WARNING: - Log.w("DeltaChat", logPrefix + event.getData2Str()); - break; - - case DcContext.DC_EVENT_ERROR: - Log.e("DeltaChat", logPrefix + event.getData2Str()); - break; - } + handleLogging(event); if (accountId != context.getDcContext().getAccountId()) { return 0; diff --git a/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDetailsFragment.java b/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDetailsFragment.java index 91edc2e18..1288e9afc 100644 --- a/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDetailsFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDetailsFragment.java @@ -36,19 +36,24 @@ import chat.delta.rpc.types.Reactions; public class ReactionsDetailsFragment extends DialogFragment implements DcEventCenter.DcEventDelegate { private static final String TAG = ReactionsDetailsFragment.class.getSimpleName(); + private static final String ARG_MSG_ID = "msg_id"; private RecyclerView recyclerView; private ReactionRecipientsAdapter adapter; - private final int msgId; + private int msgId; - public ReactionsDetailsFragment(int msgId) { - super(); - this.msgId = msgId; + public static ReactionsDetailsFragment newInstance(int msgId) { + ReactionsDetailsFragment fragment = new ReactionsDetailsFragment(); + Bundle args = new Bundle(); + args.putInt(ARG_MSG_ID, msgId); + fragment.setArguments(args); + return fragment; } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + msgId = getArguments() != null? getArguments().getInt(ARG_MSG_ID, 0) : 0; adapter = new ReactionRecipientsAdapter(requireActivity(), GlideApp.with(requireActivity()), new ListClickListener()); LayoutInflater inflater = requireActivity().getLayoutInflater();