ask for notification permission in start chat screen

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java | 753 
1 file changed, 431 insertions(+), 322 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java 🔗

@@ -34,7 +34,6 @@ import android.widget.AutoCompleteTextView;
 import android.widget.CheckBox;
 import android.widget.EditText;
 import android.widget.ListView;
-import android.widget.Spinner;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -45,7 +44,7 @@ import androidx.annotation.StringRes;
 import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.app.AlertDialog;
 import androidx.appcompat.widget.PopupMenu;
-import androidx.core.content.ContextCompat;
+import androidx.core.app.ActivityCompat;
 import androidx.databinding.DataBindingUtil;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
@@ -57,15 +56,11 @@ import androidx.viewpager.widget.ViewPager;
 import com.google.android.material.color.MaterialColors;
 import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 import com.google.android.material.textfield.TextInputLayout;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.leinardi.android.speeddial.SpeedDialActionItem;
 import com.leinardi.android.speeddial.SpeedDialView;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
 import eu.siacs.conversations.BuildConfig;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
@@ -93,9 +88,22 @@ import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 import eu.siacs.conversations.xmpp.XmppConnection;
 
-public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreatePrivateGroupChatDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener, SwipeRefreshLayout.OnRefreshListener, CreatePublicChannelDialog.CreatePublicChannelDialogListener {
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class StartConversationActivity extends XmppActivity
+        implements XmppConnectionService.OnConversationUpdate,
+                OnRosterUpdate,
+                OnUpdateBlocklist,
+                CreatePrivateGroupChatDialog.CreateConferenceDialogListener,
+                JoinConferenceDialog.JoinConferenceDialogListener,
+                SwipeRefreshLayout.OnRefreshListener,
+                CreatePublicChannelDialog.CreatePublicChannelDialogListener {
 
-    private static final String PREF_KEY_CONTACT_INTEGRATION_CONSENT = "contact_list_integration_consent";
+    private static final String PREF_KEY_CONTACT_INTEGRATION_CONSENT =
+            "contact_list_integration_consent";
 
     public static final String EXTRA_INVITE_URI = "eu.siacs.conversations.invite_uri";
 
@@ -117,125 +125,137 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
     private final AtomicBoolean mOpenedFab = new AtomicBoolean(false);
     private boolean mHideOfflineContacts = false;
     private boolean createdByViewIntent = false;
-    private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
-
-        @Override
-        public boolean onMenuItemActionExpand(MenuItem item) {
-            mSearchEditText.post(() -> {
-                updateSearchViewHint();
-                mSearchEditText.requestFocus();
-                if (oneShotKeyboardSuppress.compareAndSet(true, false)) {
-                    return;
-                }
-                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
-                if (imm != null) {
-                    imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
+    private final MenuItem.OnActionExpandListener mOnActionExpandListener =
+            new MenuItem.OnActionExpandListener() {
+
+                @Override
+                public boolean onMenuItemActionExpand(@NonNull final MenuItem item) {
+                    mSearchEditText.post(
+                            () -> {
+                                updateSearchViewHint();
+                                mSearchEditText.requestFocus();
+                                if (oneShotKeyboardSuppress.compareAndSet(true, false)) {
+                                    return;
+                                }
+                                InputMethodManager imm =
+                                        (InputMethodManager)
+                                                getSystemService(Context.INPUT_METHOD_SERVICE);
+                                if (imm != null) {
+                                    imm.showSoftInput(
+                                            mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
+                                }
+                            });
+                    if (binding.speedDial.isOpen()) {
+                        binding.speedDial.close();
+                    }
+                    return true;
                 }
-            });
-            if (binding.speedDial.isOpen()) {
-                binding.speedDial.close();
-            }
-            return true;
-        }
 
-        @Override
-        public boolean onMenuItemActionCollapse(MenuItem item) {
-            SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this);
-            mSearchEditText.setText("");
-            filter(null);
-            return true;
-        }
-    };
-    private final TextWatcher mSearchTextWatcher = new TextWatcher() {
+                @Override
+                public boolean onMenuItemActionCollapse(@NonNull final MenuItem item) {
+                    SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this);
+                    mSearchEditText.setText("");
+                    filter(null);
+                    return true;
+                }
+            };
+    private final TextWatcher mSearchTextWatcher =
+            new TextWatcher() {
 
-        @Override
-        public void afterTextChanged(Editable editable) {
-            filter(editable.toString());
-        }
+                @Override
+                public void afterTextChanged(Editable editable) {
+                    filter(editable.toString());
+                }
 
-        @Override
-        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        }
+                @Override
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
 
-        @Override
-        public void onTextChanged(CharSequence s, int start, int before, int count) {
-        }
-    };
+                @Override
+                public void onTextChanged(CharSequence s, int start, int before, int count) {}
+            };
     private MenuItem mMenuSearchView;
-    private final ListItemAdapter.OnTagClickedListener mOnTagClickedListener = new ListItemAdapter.OnTagClickedListener() {
-        @Override
-        public void onTagClicked(String tag) {
-            if (mMenuSearchView != null) {
-                mMenuSearchView.expandActionView();
-                mSearchEditText.setText("");
-                mSearchEditText.append(tag);
-                filter(tag);
-            }
-        }
-    };
+    private final ListItemAdapter.OnTagClickedListener mOnTagClickedListener =
+            new ListItemAdapter.OnTagClickedListener() {
+                @Override
+                public void onTagClicked(String tag) {
+                    if (mMenuSearchView != null) {
+                        mMenuSearchView.expandActionView();
+                        mSearchEditText.setText("");
+                        mSearchEditText.append(tag);
+                        filter(tag);
+                    }
+                }
+            };
     private Pair<Integer, Intent> mPostponedActivityResult;
     private Toast mToast;
-    private final UiCallback<Conversation> mAdhocConferenceCallback = new UiCallback<Conversation>() {
-        @Override
-        public void success(final Conversation conversation) {
-            runOnUiThread(() -> {
-                hideToast();
-                switchToConversation(conversation);
-            });
-        }
-
-        @Override
-        public void error(final int errorCode, Conversation object) {
-            runOnUiThread(() -> replaceToast(getString(errorCode)));
-        }
+    private final UiCallback<Conversation> mAdhocConferenceCallback =
+            new UiCallback<>() {
+                @Override
+                public void success(final Conversation conversation) {
+                    runOnUiThread(
+                            () -> {
+                                hideToast();
+                                switchToConversation(conversation);
+                            });
+                }
 
-        @Override
-        public void userInputRequired(PendingIntent pi, Conversation object) {
+                @Override
+                public void error(final int errorCode, Conversation object) {
+                    runOnUiThread(() -> replaceToast(getString(errorCode)));
+                }
 
-        }
-    };
+                @Override
+                public void userInputRequired(PendingIntent pi, Conversation object) {}
+            };
     private ActivityStartConversationBinding binding;
-    private final TextView.OnEditorActionListener mSearchDone = new TextView.OnEditorActionListener() {
-        @Override
-        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-            int pos = binding.startConversationViewPager.getCurrentItem();
-            if (pos == 0) {
-                if (contacts.size() == 1) {
-                    openConversationForContact((Contact) contacts.get(0));
-                    return true;
-                } else if (contacts.size() == 0 && conferences.size() == 1) {
-                    openConversationsForBookmark((Bookmark) conferences.get(0));
-                    return true;
-                }
-            } else {
-                if (conferences.size() == 1) {
-                    openConversationsForBookmark((Bookmark) conferences.get(0));
-                    return true;
-                } else if (conferences.size() == 0 && contacts.size() == 1) {
-                    openConversationForContact((Contact) contacts.get(0));
+    private final TextView.OnEditorActionListener mSearchDone =
+            new TextView.OnEditorActionListener() {
+                @Override
+                public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                    int pos = binding.startConversationViewPager.getCurrentItem();
+                    if (pos == 0) {
+                        if (contacts.size() == 1) {
+                            openConversationForContact((Contact) contacts.get(0));
+                            return true;
+                        } else if (contacts.isEmpty() && conferences.size() == 1) {
+                            openConversationsForBookmark((Bookmark) conferences.get(0));
+                            return true;
+                        }
+                    } else {
+                        if (conferences.size() == 1) {
+                            openConversationsForBookmark((Bookmark) conferences.get(0));
+                            return true;
+                        } else if (conferences.isEmpty() && contacts.size() == 1) {
+                            openConversationForContact((Contact) contacts.get(0));
+                            return true;
+                        }
+                    }
+                    SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this);
+                    mListPagerAdapter.requestFocus(pos);
                     return true;
                 }
-            }
-            SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this);
-            mListPagerAdapter.requestFocus(pos);
-            return true;
-        }
-    };
+            };
 
-    public static void populateAccountSpinner(final Context context, final List<String> accounts, final AutoCompleteTextView spinner) {
+    public static void populateAccountSpinner(
+            final Context context,
+            final List<String> accounts,
+            final AutoCompleteTextView spinner) {
         if (accounts.isEmpty()) {
-            ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
-                    R.layout.item_autocomplete,
-                    Collections.singletonList(context.getString(R.string.no_accounts)));
+            ArrayAdapter<String> adapter =
+                    new ArrayAdapter<>(
+                            context,
+                            R.layout.item_autocomplete,
+                            Collections.singletonList(context.getString(R.string.no_accounts)));
             adapter.setDropDownViewResource(R.layout.item_autocomplete);
             spinner.setAdapter(adapter);
             spinner.setEnabled(false);
         } else {
-            final ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.item_autocomplete, accounts);
+            final ArrayAdapter<String> adapter =
+                    new ArrayAdapter<>(context, R.layout.item_autocomplete, accounts);
             adapter.setDropDownViewResource(R.layout.item_autocomplete);
             spinner.setAdapter(adapter);
             spinner.setEnabled(true);
-            spinner.setText(Iterables.getFirst(accounts,null),false);
+            spinner.setText(Iterables.getFirst(accounts, null), false);
         }
     }
 
@@ -252,7 +272,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
     }
 
     private static boolean isViewIntent(final Intent i) {
-        return i != null && (Intent.ACTION_VIEW.equals(i.getAction()) || Intent.ACTION_SENDTO.equals(i.getAction()) || i.hasExtra(EXTRA_INVITE_URI));
+        return i != null
+                && (Intent.ACTION_VIEW.equals(i.getAction())
+                        || Intent.ACTION_SENDTO.equals(i.getAction())
+                        || i.hasExtra(EXTRA_INVITE_URI));
     }
 
     protected void hideToast() {
@@ -282,12 +305,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 
         inflateFab(binding.speedDial, R.menu.start_conversation_fab_submenu);
         binding.tabLayout.setupWithViewPager(binding.startConversationViewPager);
-        binding.startConversationViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
-            @Override
-            public void onPageSelected(int position) {
-                updateSearchViewHint();
-            }
-        });
+        binding.startConversationViewPager.addOnPageChangeListener(
+                new ViewPager.SimpleOnPageChangeListener() {
+                    @Override
+                    public void onPageSelected(int position) {
+                        updateSearchViewHint();
+                    }
+                });
         mListPagerAdapter = new ListPagerAdapter(getSupportFragmentManager());
         binding.startConversationViewPager.setAdapter(mListPagerAdapter);
 
@@ -297,9 +321,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 
         final SharedPreferences preferences = getPreferences();
 
-        this.mHideOfflineContacts = QuickConversationsService.isConversations() && preferences.getBoolean("hide_offline", false);
+        this.mHideOfflineContacts =
+                QuickConversationsService.isConversations()
+                        && preferences.getBoolean("hide_offline", false);
 
-        final boolean startSearching = preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching));
+        final boolean startSearching =
+                preferences.getBoolean(
+                        "start_searching", getResources().getBoolean(R.bool.start_searching));
 
         final Intent intent;
         if (savedInstanceState == null) {
@@ -320,39 +348,45 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
         } else if (startSearching && mInitialSearchValue.peek() == null) {
             mInitialSearchValue.push("");
         }
-        mRequestedContactsPermission.set(savedInstanceState != null && savedInstanceState.getBoolean("requested_contacts_permission", false));
-        mOpenedFab.set(savedInstanceState != null && savedInstanceState.getBoolean("opened_fab", false));
-        binding.speedDial.setOnActionSelectedListener(actionItem -> {
-            final String searchString = mSearchEditText != null ? mSearchEditText.getText().toString() : null;
-            final String prefilled;
-            if (isValidJid(searchString)) {
-                prefilled = Jid.ofEscaped(searchString).toEscapedString();
-            } else {
-                prefilled = null;
-            }
-            switch (actionItem.getId()) {
-                case R.id.discover_public_channels:
-                    if (QuickConversationsService.isPlayStoreFlavor()) {
-                        throw new IllegalStateException("Channel discovery is not available on Google Play flavor");
+        mRequestedContactsPermission.set(
+                savedInstanceState != null
+                        && savedInstanceState.getBoolean("requested_contacts_permission", false));
+        mOpenedFab.set(
+                savedInstanceState != null && savedInstanceState.getBoolean("opened_fab", false));
+        binding.speedDial.setOnActionSelectedListener(
+                actionItem -> {
+                    final String searchString =
+                            mSearchEditText != null ? mSearchEditText.getText().toString() : null;
+                    final String prefilled;
+                    if (isValidJid(searchString)) {
+                        prefilled = Jid.ofEscaped(searchString).toEscapedString();
                     } else {
-                        startActivity(new Intent(this, ChannelDiscoveryActivity.class));
+                        prefilled = null;
                     }
-                    break;
-                case R.id.join_public_channel:
-                    showJoinConferenceDialog(prefilled);
-                    break;
-                case R.id.create_private_group_chat:
-                    showCreatePrivateGroupChatDialog();
-                    break;
-                case R.id.create_public_channel:
-                    showPublicChannelDialog();
-                    break;
-                case R.id.create_contact:
-                    showCreateContactDialog(prefilled, null);
-                    break;
-            }
-            return false;
-        });
+                    switch (actionItem.getId()) {
+                        case R.id.discover_public_channels:
+                            if (QuickConversationsService.isPlayStoreFlavor()) {
+                                throw new IllegalStateException(
+                                        "Channel discovery is not available on Google Play flavor");
+                            } else {
+                                startActivity(new Intent(this, ChannelDiscoveryActivity.class));
+                            }
+                            break;
+                        case R.id.join_public_channel:
+                            showJoinConferenceDialog(prefilled);
+                            break;
+                        case R.id.create_private_group_chat:
+                            showCreatePrivateGroupChatDialog();
+                            break;
+                        case R.id.create_public_channel:
+                            showPublicChannelDialog();
+                            break;
+                        case R.id.create_contact:
+                            showCreateContactDialog(prefilled, null);
+                            break;
+                    }
+                    return false;
+                });
     }
 
     private void inflateFab(final SpeedDialView speedDialView, final @MenuRes int menuRes) {
@@ -362,14 +396,26 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
         final Menu menu = popupMenu.getMenu();
         for (int i = 0; i < menu.size(); i++) {
             final MenuItem menuItem = menu.getItem(i);
-            if (QuickConversationsService.isPlayStoreFlavor() && menuItem.getItemId() == R.id.discover_public_channels) {
+            if (QuickConversationsService.isPlayStoreFlavor()
+                    && menuItem.getItemId() == R.id.discover_public_channels) {
                 continue;
             }
-            final SpeedDialActionItem actionItem = new SpeedDialActionItem.Builder(menuItem.getItemId(), menuItem.getIcon())
-                    .setLabel(menuItem.getTitle() != null ? menuItem.getTitle().toString() : null)
-                    .setFabImageTintColor(MaterialColors.getColor(speedDialView, com.google.android.material.R.attr.colorOnSurface))
-                    .setFabBackgroundColor(MaterialColors.getColor(speedDialView, com.google.android.material.R.attr.colorSurfaceContainerHighest))
-                    .create();
+            final SpeedDialActionItem actionItem =
+                    new SpeedDialActionItem.Builder(menuItem.getItemId(), menuItem.getIcon())
+                            .setLabel(
+                                    menuItem.getTitle() != null
+                                            ? menuItem.getTitle().toString()
+                                            : null)
+                            .setFabImageTintColor(
+                                    MaterialColors.getColor(
+                                            speedDialView,
+                                            com.google.android.material.R.attr.colorOnSurface))
+                            .setFabBackgroundColor(
+                                    MaterialColors.getColor(
+                                            speedDialView,
+                                            com.google.android.material.R.attr
+                                                    .colorSurfaceContainerHighest))
+                            .create();
             speedDialView.addActionItem(actionItem);
         }
     }
@@ -386,12 +432,16 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
     @Override
     public void onSaveInstanceState(Bundle savedInstanceState) {
         Intent pendingIntent = pendingViewIntent.peek();
-        savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent());
-        savedInstanceState.putBoolean("requested_contacts_permission", mRequestedContactsPermission.get());
+        savedInstanceState.putParcelable(
+                "intent", pendingIntent != null ? pendingIntent : getIntent());
+        savedInstanceState.putBoolean(
+                "requested_contacts_permission", mRequestedContactsPermission.get());
         savedInstanceState.putBoolean("opened_fab", mOpenedFab.get());
         savedInstanceState.putBoolean("created_by_view_intent", createdByViewIntent);
         if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
-            savedInstanceState.putString("search", mSearchEditText != null ? mSearchEditText.getText().toString() : null);
+            savedInstanceState.putString(
+                    "search",
+                    mSearchEditText != null ? mSearchEditText.getText().toString() : null);
         }
         super.onSaveInstanceState(savedInstanceState);
     }
@@ -399,11 +449,24 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
     @Override
     public void onStart() {
         super.onStart();
-        if (pendingViewIntent.peek() == null) {
-            askForContactsPermissions();
-        }
         mConferenceAdapter.refreshSettings();
         mContactsAdapter.refreshSettings();
+        if (pendingViewIntent.peek() == null) {
+            if (askForContactsPermissions()) {
+                return;
+            }
+            requestNotificationPermissionIfNeeded();
+        }
+    }
+
+    private void requestNotificationPermissionIfNeeded() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                && ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
+                        != PackageManager.PERMISSION_GRANTED) {
+            requestPermissions(
+                    new String[] {Manifest.permission.POST_NOTIFICATIONS},
+                    REQUEST_POST_NOTIFICATION);
+        }
     }
 
     @Override
@@ -423,7 +486,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
     }
 
     protected void openConversationForContact(Contact contact) {
-        Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
+        Conversation conversation =
+                xmppConnectionService.findOrCreateConversation(
+                        contact.getAccount(), contact.getJid(), false, true);
         SoftKeyboardUtils.hideSoftKeyboard(this);
         switchToConversation(conversation);
     }
@@ -448,9 +513,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
         shareIntent.putExtra(Intent.EXTRA_TEXT, "xmpp:" + address + "?join");
         shareIntent.setType("text/plain");
         try {
-            context.startActivity(Intent.createChooser(shareIntent, context.getText(R.string.share_uri_with)));
+            context.startActivity(
+                    Intent.createChooser(shareIntent, context.getText(R.string.share_uri_with)));
         } catch (ActivityNotFoundException e) {
-            Toast.makeText(context, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
+            Toast.makeText(context, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT)
+                    .show();
         }
     }
 
@@ -460,7 +527,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
             Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
             return;
         }
-        final Conversation conversation = xmppConnectionService.findOrCreateConversation(bookmark.getAccount(), jid, true, true, true);
+        final Conversation conversation =
+                xmppConnectionService.findOrCreateConversation(
+                        bookmark.getAccount(), jid, true, true, true);
         bookmark.setConversation(conversation);
         if (!bookmark.autojoin()) {
             bookmark.setAutojoin(true);
@@ -493,11 +562,15 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
         final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
         builder.setNegativeButton(R.string.cancel, null);
         builder.setTitle(R.string.action_delete_contact);
-        builder.setMessage(JidDialog.style(this, R.string.remove_contact_text, contact.getJid().toEscapedString()));
-        builder.setPositiveButton(R.string.delete, (dialog, which) -> {
-            xmppConnectionService.deleteContactOnServer(contact);
-            filter(mSearchEditText.getText().toString());
-        });
+        builder.setMessage(
+                JidDialog.style(
+                        this, R.string.remove_contact_text, contact.getJid().toEscapedString()));
+        builder.setPositiveButton(
+                R.string.delete,
+                (dialog, which) -> {
+                    xmppConnectionService.deleteContactOnServer(contact);
+                    filter(mSearchEditText.getText().toString());
+                });
         builder.create().show();
     }
 
@@ -510,21 +583,28 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
         builder.setNegativeButton(R.string.cancel, null);
         builder.setTitle(R.string.delete_bookmark);
         if (hasConversation) {
-            builder.setMessage(JidDialog.style(this, R.string.remove_bookmark_and_close, bookmark.getJid().toEscapedString()));
+            builder.setMessage(
+                    JidDialog.style(
+                            this,
+                            R.string.remove_bookmark_and_close,
+                            bookmark.getJid().toEscapedString()));
         } else {
-            builder.setMessage(JidDialog.style(this, R.string.remove_bookmark, bookmark.getJid().toEscapedString()));
-        }
-        builder.setPositiveButton(hasConversation ? R.string.delete_and_close : R.string.delete, (dialog, which) -> {
-            bookmark.setConversation(null);
-            final Account account = bookmark.getAccount();
-            xmppConnectionService.deleteBookmark(account, bookmark);
-            if (conversation != null) {
-                xmppConnectionService.archiveConversation(conversation);
-            }
-            filter(mSearchEditText.getText().toString());
-        });
+            builder.setMessage(
+                    JidDialog.style(
+                            this, R.string.remove_bookmark, bookmark.getJid().toEscapedString()));
+        }
+        builder.setPositiveButton(
+                hasConversation ? R.string.delete_and_close : R.string.delete,
+                (dialog, which) -> {
+                    bookmark.setConversation(null);
+                    final Account account = bookmark.getAccount();
+                    xmppConnectionService.deleteBookmark(account, bookmark);
+                    if (conversation != null) {
+                        xmppConnectionService.archiveConversation(conversation);
+                    }
+                    filter(mSearchEditText.getText().toString());
+                });
         builder.create().show();
-
     }
 
     @SuppressLint("InflateParams")
@@ -535,45 +615,52 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
             ft.remove(prev);
         }
         ft.addToBackStack(null);
-        EnterJidDialog dialog = EnterJidDialog.newInstance(
-                mActivatedAccounts,
-                getString(R.string.add_contact),
-                getString(R.string.add),
-                prefilledJid,
-                invite == null ? null : invite.account,
-                invite == null || !invite.hasFingerprints(),
-                true
-        );
-
-        dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
-            if (!xmppConnectionServiceBound) {
-                return false;
-            }
+        EnterJidDialog dialog =
+                EnterJidDialog.newInstance(
+                        mActivatedAccounts,
+                        getString(R.string.add_contact),
+                        getString(R.string.add),
+                        prefilledJid,
+                        invite == null ? null : invite.account,
+                        invite == null || !invite.hasFingerprints(),
+                        true);
+
+        dialog.setOnEnterJidDialogPositiveListener(
+                (accountJid, contactJid) -> {
+                    if (!xmppConnectionServiceBound) {
+                        return false;
+                    }
 
-            final Account account = xmppConnectionService.findAccountByJid(accountJid);
-            if (account == null) {
-                return true;
-            }
+                    final Account account = xmppConnectionService.findAccountByJid(accountJid);
+                    if (account == null) {
+                        return true;
+                    }
 
-            final Contact contact = account.getRoster().getContact(contactJid);
-            if (invite != null && invite.getName() != null) {
-                contact.setServerName(invite.getName());
-            }
-            if (contact.isSelf()) {
-                switchToConversation(contact);
-                return true;
-            } else if (contact.showInRoster()) {
-                throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists));
-            } else {
-                final String preAuth = invite == null ? null : invite.getParameter(XmppUri.PARAMETER_PRE_AUTH);
-                xmppConnectionService.createContact(contact, true, preAuth);
-                if (invite != null && invite.hasFingerprints()) {
-                    xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
-                }
-                switchToConversationDoNotAppend(contact, invite == null ? null : invite.getBody());
-                return true;
-            }
-        });
+                    final Contact contact = account.getRoster().getContact(contactJid);
+                    if (invite != null && invite.getName() != null) {
+                        contact.setServerName(invite.getName());
+                    }
+                    if (contact.isSelf()) {
+                        switchToConversation(contact);
+                        return true;
+                    } else if (contact.showInRoster()) {
+                        throw new EnterJidDialog.JidError(
+                                getString(R.string.contact_already_exists));
+                    } else {
+                        final String preAuth =
+                                invite == null
+                                        ? null
+                                        : invite.getParameter(XmppUri.PARAMETER_PRE_AUTH);
+                        xmppConnectionService.createContact(contact, true, preAuth);
+                        if (invite != null && invite.hasFingerprints()) {
+                            xmppConnectionService.verifyFingerprints(
+                                    contact, invite.getFingerprints());
+                        }
+                        switchToConversationDoNotAppend(
+                                contact, invite == null ? null : invite.getBody());
+                        return true;
+                    }
+                });
         dialog.show(ft, FRAGMENT_TAG_DIALOG);
     }
 
@@ -585,7 +672,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
             ft.remove(prev);
         }
         ft.addToBackStack(null);
-        JoinConferenceDialog joinConferenceFragment = JoinConferenceDialog.newInstance(prefilledJid, mActivatedAccounts);
+        JoinConferenceDialog joinConferenceFragment =
+                JoinConferenceDialog.newInstance(prefilledJid, mActivatedAccounts);
         joinConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
     }
 
@@ -596,7 +684,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
             ft.remove(prev);
         }
         ft.addToBackStack(null);
-        CreatePrivateGroupChatDialog createConferenceFragment = CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts);
+        CreatePrivateGroupChatDialog createConferenceFragment =
+                CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts);
         createConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
     }
 
@@ -607,11 +696,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
             ft.remove(prev);
         }
         ft.addToBackStack(null);
-        CreatePublicChannelDialog dialog = CreatePublicChannelDialog.newInstance(mActivatedAccounts);
+        CreatePublicChannelDialog dialog =
+                CreatePublicChannelDialog.newInstance(mActivatedAccounts);
         dialog.show(ft, FRAGMENT_TAG_DIALOG);
     }
 
-    public static Account getSelectedAccount(final Context context, final AutoCompleteTextView spinner) {
+    public static Account getSelectedAccount(
+            final Context context, final AutoCompleteTextView spinner) {
         if (spinner == null || !spinner.isEnabled()) {
             return null;
         }
@@ -633,12 +724,16 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
     }
 
     protected void switchToConversation(Contact contact) {
-        Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
+        Conversation conversation =
+                xmppConnectionService.findOrCreateConversation(
+                        contact.getAccount(), contact.getJid(), false, true);
         switchToConversation(conversation);
     }
 
     protected void switchToConversationDoNotAppend(Contact contact, String body) {
-        Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
+        Conversation conversation =
+                xmppConnectionService.findOrCreateConversation(
+                        contact.getAccount(), contact.getJid(), false, true);
         switchToConversationDoNotAppend(conversation, body);
     }
 
@@ -752,11 +847,15 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
                 this.mPostponedActivityResult = null;
                 if (requestCode == REQUEST_CREATE_CONFERENCE) {
                     Account account = extractAccount(intent);
-                    final String name = intent.getStringExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME);
+                    final String name =
+                            intent.getStringExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME);
                     final List<Jid> jids = ChooseContactActivity.extractJabberIds(intent);
                     if (account != null && jids.size() > 0) {
-                        if (xmppConnectionService.createAdhocConference(account, name, jids, mAdhocConferenceCallback)) {
-                            mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
+                        if (xmppConnectionService.createAdhocConference(
+                                account, name, jids, mAdhocConferenceCallback)) {
+                            mToast =
+                                    Toast.makeText(
+                                            this, R.string.creating_conference, Toast.LENGTH_LONG);
                             mToast.show();
                         }
                     }
@@ -768,104 +867,109 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
         super.onActivityResult(requestCode, requestCode, intent);
     }
 
-    private void askForContactsPermissions() {
-        if (QuickConversationsService.isContactListIntegration(this)) {
-            if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
-                    != PackageManager.PERMISSION_GRANTED) {
-                if (mRequestedContactsPermission.compareAndSet(false, true)) {
-                    final String consent =
-                            PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
-                                    .getString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, null);
-                    final boolean requiresConsent =
-                            (QuickConversationsService.isQuicksy()
-                                            || QuickConversationsService.isPlayStoreFlavor())
-                                    && !"agreed".equals(consent);
-                    if (requiresConsent && "declined".equals(consent)) {
-                        Log.d(Config.LOGTAG,"not asking for contacts permission because consent has been declined");
-                        return;
-                    }
-                    if (requiresConsent
-                            || shouldShowRequestPermissionRationale(
-                                    Manifest.permission.READ_CONTACTS)) {
-                        final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
-                        final AtomicBoolean requestPermission = new AtomicBoolean(false);
-                        if (QuickConversationsService.isQuicksy()) {
-                            builder.setTitle(R.string.quicksy_wants_your_consent);
-                            builder.setMessage(
-                                    Html.fromHtml(
-                                            getString(R.string.sync_with_contacts_quicksy_static)));
-                        } else {
-                            builder.setTitle(R.string.sync_with_contacts);
-                            builder.setMessage(
-                                    getString(
-                                            R.string.sync_with_contacts_long,
-                                            getString(R.string.app_name)));
-                        }
-                        @StringRes int confirmButtonText;
-                        if (requiresConsent) {
-                            confirmButtonText = R.string.agree_and_continue;
-                        } else {
-                            confirmButtonText = R.string.next;
-                        }
-                        builder.setPositiveButton(
-                                confirmButtonText,
-                                (dialog, which) -> {
-                                    if (requiresConsent) {
-                                        PreferenceManager.getDefaultSharedPreferences(
-                                                        getApplicationContext())
-                                                .edit()
-                                                .putString(
-                                                        PREF_KEY_CONTACT_INTEGRATION_CONSENT, "agreed")
-                                                .apply();
-                                    }
-                                    if (requestPermission.compareAndSet(false, true)) {
-                                        requestPermissions(
-                                                new String[] {Manifest.permission.READ_CONTACTS},
-                                                REQUEST_SYNC_CONTACTS);
-                                    }
-                                });
-                        if (requiresConsent) {
-                            builder.setNegativeButton(R.string.decline, (dialog, which) -> PreferenceManager.getDefaultSharedPreferences(
-                                            getApplicationContext())
-                                    .edit()
-                                    .putString(
-                                            PREF_KEY_CONTACT_INTEGRATION_CONSENT, "declined")
-                                    .apply());
-                        } else {
-                            builder.setOnDismissListener(
-                                    dialog -> {
-                                        if (requestPermission.compareAndSet(false, true)) {
-                                            requestPermissions(
-                                                    new String[] {
-                                                        Manifest.permission.READ_CONTACTS
-                                                    },
-                                                    REQUEST_SYNC_CONTACTS);
-                                        }
-                                    });
-                        }
-                        builder.setCancelable(requiresConsent);
-                        final AlertDialog dialog = builder.create();
-                        dialog.setCanceledOnTouchOutside(requiresConsent);
-                        dialog.setOnShowListener(
-                                dialogInterface -> {
-                                    final TextView tv = dialog.findViewById(android.R.id.message);
-                                    if (tv != null) {
-                                        tv.setMovementMethod(LinkMovementMethod.getInstance());
-                                    }
-                                });
-                        dialog.show();
-                    } else {
-                        requestPermissions(
-                                new String[] {Manifest.permission.READ_CONTACTS},
-                                REQUEST_SYNC_CONTACTS);
-                    }
+    private boolean askForContactsPermissions() {
+        if (!QuickConversationsService.isContactListIntegration(this)) {
+            return false;
+        }
+        if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
+                == PackageManager.PERMISSION_GRANTED) {
+            return false;
+        }
+        if (mRequestedContactsPermission.compareAndSet(false, true)) {
+            final ImmutableList.Builder<String> permissionBuilder = new ImmutableList.Builder<>();
+            permissionBuilder.add(Manifest.permission.READ_CONTACTS);
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                permissionBuilder.add(Manifest.permission.POST_NOTIFICATIONS);
+            }
+            final String[] permission = permissionBuilder.build().toArray(new String[0]);
+            final String consent =
+                    PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
+                            .getString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, null);
+            final boolean requiresConsent =
+                    (QuickConversationsService.isQuicksy()
+                                    || QuickConversationsService.isPlayStoreFlavor())
+                            && !"agreed".equals(consent);
+            if (requiresConsent && "declined".equals(consent)) {
+                Log.d(
+                        Config.LOGTAG,
+                        "not asking for contacts permission because consent has been declined");
+                return false;
+            }
+            if (requiresConsent
+                    || shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
+                final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
+                final AtomicBoolean requestPermission = new AtomicBoolean(false);
+                if (QuickConversationsService.isQuicksy()) {
+                    builder.setTitle(R.string.quicksy_wants_your_consent);
+                    builder.setMessage(
+                            Html.fromHtml(getString(R.string.sync_with_contacts_quicksy_static)));
+                } else {
+                    builder.setTitle(R.string.sync_with_contacts);
+                    builder.setMessage(
+                            getString(
+                                    R.string.sync_with_contacts_long,
+                                    getString(R.string.app_name)));
+                }
+                @StringRes int confirmButtonText;
+                if (requiresConsent) {
+                    confirmButtonText = R.string.agree_and_continue;
+                } else {
+                    confirmButtonText = R.string.next;
                 }
+                builder.setPositiveButton(
+                        confirmButtonText,
+                        (dialog, which) -> {
+                            if (requiresConsent) {
+                                PreferenceManager.getDefaultSharedPreferences(
+                                                getApplicationContext())
+                                        .edit()
+                                        .putString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, "agreed")
+                                        .apply();
+                            }
+                            if (requestPermission.compareAndSet(false, true)) {
+                                requestPermissions(permission, REQUEST_SYNC_CONTACTS);
+                            }
+                        });
+                if (requiresConsent) {
+                    builder.setNegativeButton(
+                            R.string.decline,
+                            (dialog, which) ->
+                                    PreferenceManager.getDefaultSharedPreferences(
+                                                    getApplicationContext())
+                                            .edit()
+                                            .putString(
+                                                    PREF_KEY_CONTACT_INTEGRATION_CONSENT,
+                                                    "declined")
+                                            .apply());
+                } else {
+                    builder.setOnDismissListener(
+                            dialog -> {
+                                if (requestPermission.compareAndSet(false, true)) {
+                                    requestPermissions(permission, REQUEST_SYNC_CONTACTS);
+                                }
+                            });
+                }
+                builder.setCancelable(requiresConsent);
+                final AlertDialog dialog = builder.create();
+                dialog.setCanceledOnTouchOutside(requiresConsent);
+                dialog.setOnShowListener(
+                        dialogInterface -> {
+                            final TextView tv = dialog.findViewById(android.R.id.message);
+                            if (tv != null) {
+                                tv.setMovementMethod(LinkMovementMethod.getInstance());
+                            }
+                        });
+                dialog.show();
+            } else {
+                requestPermissions(permission, REQUEST_SYNC_CONTACTS);
             }
         }
+        return true;
     }
 
     @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+    public void onRequestPermissionsResult(
+            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
         if (grantResults.length > 0)
             if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
@@ -885,10 +989,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
         if (actionBar == null) {
             return;
         }
-        boolean openConversations = !createdByViewIntent && !xmppConnectionService.isConversationsListEmpty(null);
+        boolean openConversations =
+                !createdByViewIntent && !xmppConnectionService.isConversationsListEmpty(null);
         actionBar.setDisplayHomeAsUpEnabled(openConversations);
         actionBar.setDisplayHomeAsUpEnabled(openConversations);
-
     }
 
     @Override
@@ -900,7 +1004,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
             xmppConnectionService.getQuickConversationsService().considerSyncBackground(false);
         }
         if (mPostponedActivityResult != null) {
-            onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
+            onActivityResult(
+                    mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
             this.mPostponedActivityResult = null;
         }
         this.mActivatedAccounts.clear();
@@ -924,7 +1029,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
         if (QuickConversationsService.isQuicksy()) {
             setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing());
         }
-        if (QuickConversationsService.isConversations() && AccountUtils.hasEnabledAccounts(xmppConnectionService) && this.contacts.size() == 0 && this.conferences.size() == 0 && mOpenedFab.compareAndSet(false, true)) {
+        if (QuickConversationsService.isConversations()
+                && AccountUtils.hasEnabledAccounts(xmppConnectionService)
+                && this.contacts.size() == 0
+                && this.conferences.size() == 0
+                && mOpenedFab.compareAndSet(false, true)) {
             binding.speedDial.open();
         }
     }