add 'log out' button to foreground notifcation

this temporarily disconnects all accounts until the user opens the app again.

essentially this akin to an 'Exit' button

Users previously had the option to 'disable' accounts but this provides a
faster way to "free up resources" until the next time the app is opened.
This commit is contained in:
Daniel Gultsch 2023-10-18 14:02:10 +02:00
parent 418d6b09a0
commit f042efd550
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
14 changed files with 131 additions and 88 deletions

View file

@ -359,6 +359,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
private void enableAccount(Account account) {
account.setOption(Account.OPTION_DISABLED, false);
account.setOption(Account.OPTION_SOFT_DISABLED, false);
final XmppConnection connection = account.getXmppConnection();
if (connection != null) {
connection.resetEverything();

View file

@ -70,6 +70,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
public static final int OPTION_UNVERIFIED = 8;
public static final int OPTION_FIXED_USERNAME = 9;
public static final int OPTION_QUICKSTART_AVAILABLE = 10;
public static final int OPTION_SOFT_DISABLED = 11;
private static final String KEY_PGP_SIGNATURE = "pgp_signature";
private static final String KEY_PGP_ID = "pgp_id";
@ -248,6 +249,10 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
return !isOptionSet(Account.OPTION_DISABLED);
}
public boolean isConnectionEnabled() {
return !isOptionSet(Account.OPTION_DISABLED) && !isOptionSet(Account.OPTION_SOFT_DISABLED);
}
public boolean isOptionSet(final int option) {
return ((options & (1 << option)) != 0);
}
@ -322,6 +327,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
public State getStatus() {
if (isOptionSet(OPTION_DISABLED)) {
return State.DISABLED;
} else if (isOptionSet(OPTION_SOFT_DISABLED)) {
return State.LOGGED_OUT;
} else {
return this.status;
}
@ -765,6 +772,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
public enum State {
DISABLED(false, false),
LOGGED_OUT(false,false),
OFFLINE(false),
CONNECTING(false),
ONLINE(false),
@ -824,6 +832,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
switch (this) {
case DISABLED:
return R.string.account_status_disabled;
case LOGGED_OUT:
return R.string.account_state_logged_out;
case ONLINE:
return R.string.account_status_online;
case CONNECTING:

View file

@ -22,6 +22,7 @@ import android.os.Build;
import android.os.SystemClock;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.DisplayMetrics;
@ -40,6 +41,7 @@ import androidx.core.graphics.drawable.IconCompat;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.io.File;
@ -1661,12 +1663,25 @@ public class NotificationService {
}
private PendingIntent createCallAction(String sessionId, final String action, int requestCode) {
final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
return pendingServiceIntent(mXmppConnectionService, action, requestCode, ImmutableMap.of(RtpSessionActivity.EXTRA_SESSION_ID, sessionId));
}
private PendingIntent createSnoozeIntent(final Conversation conversation) {
return pendingServiceIntent(mXmppConnectionService, XmppConnectionService.ACTION_SNOOZE, generateRequestCode(conversation,22),ImmutableMap.of("uuid",conversation.getUuid()));
}
private static PendingIntent pendingServiceIntent(final Context context, final String action, final int requestCode) {
return pendingServiceIntent(context, action, requestCode, ImmutableMap.of());
}
private static PendingIntent pendingServiceIntent(final Context context, final String action, final int requestCode, final Map<String,String> extras) {
final Intent intent = new Intent(context, XmppConnectionService.class);
intent.setAction(action);
intent.setPackage(mXmppConnectionService.getPackageName());
intent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, sessionId);
for(final Map.Entry<String,String> entry : extras.entrySet()) {
intent.putExtra(entry.getKey(), entry.getValue());
}
return PendingIntent.getService(
mXmppConnectionService,
context,
requestCode,
intent,
s()
@ -1674,44 +1689,6 @@ public class NotificationService {
: PendingIntent.FLAG_UPDATE_CURRENT);
}
private PendingIntent createSnoozeIntent(Conversation conversation) {
final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
intent.setAction(XmppConnectionService.ACTION_SNOOZE);
intent.putExtra("uuid", conversation.getUuid());
intent.setPackage(mXmppConnectionService.getPackageName());
return PendingIntent.getService(
mXmppConnectionService,
generateRequestCode(conversation, 22),
intent,
s()
? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
: PendingIntent.FLAG_UPDATE_CURRENT);
}
private PendingIntent createTryAgainIntent() {
final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
intent.setAction(XmppConnectionService.ACTION_TRY_AGAIN);
return PendingIntent.getService(
mXmppConnectionService,
45,
intent,
s()
? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
: PendingIntent.FLAG_UPDATE_CURRENT);
}
private PendingIntent createDismissErrorIntent() {
final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
intent.setAction(XmppConnectionService.ACTION_DISMISS_ERROR_NOTIFICATIONS);
return PendingIntent.getService(
mXmppConnectionService,
69,
intent,
s()
? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
: PendingIntent.FLAG_UPDATE_CURRENT);
}
private boolean wasHighlightedOrPrivate(final Message message) {
if (message.getConversation() instanceof Conversation) {
Conversation conversation = (Conversation) message.getConversation();
@ -1756,17 +1733,15 @@ public class NotificationService {
final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.app_name));
final List<Account> accounts = mXmppConnectionService.getAccounts();
int enabled = 0;
int connected = 0;
if (accounts != null) {
for (Account account : accounts) {
if (account.isOnlineAndConnected()) {
connected++;
enabled++;
} else if (account.isEnabled()) {
enabled++;
}
}
final int enabled;
final int connected;
if (accounts == null) {
enabled = 0;
connected = 0;
} else {
enabled = Iterables.size(Iterables.filter(accounts, Account::isEnabled));
connected =
Iterables.size(Iterables.filter(accounts, Account::isOnlineAndConnected));
}
mBuilder.setContentText(
mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled));
@ -1784,11 +1759,36 @@ public class NotificationService {
if (Compatibility.runsTwentySix()) {
mBuilder.setChannelId("foreground");
mBuilder.addAction(
R.drawable.ic_logout_white_24dp,
mXmppConnectionService.getString(R.string.log_out),
pendingServiceIntent(
mXmppConnectionService,
XmppConnectionService.ACTION_TEMPORARILY_DISABLE,
87));
mBuilder.addAction(
R.drawable.ic_notifications_off_white_24dp,
mXmppConnectionService.getString(R.string.hide_notification),
pendingNotificationSettingsIntent(mXmppConnectionService));
}
return mBuilder.build();
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static PendingIntent pendingNotificationSettingsIntent(final Context context) {
final Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, "foreground");
return PendingIntent.getActivity(
context,
89,
intent,
s()
? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
: PendingIntent.FLAG_UPDATE_CURRENT);
}
private PendingIntent createOpenConversationsIntent() {
try {
return PendingIntent.getActivity(
@ -1839,7 +1839,7 @@ public class NotificationService {
mBuilder.addAction(
R.drawable.ic_autorenew_white_24dp,
mXmppConnectionService.getString(R.string.try_again),
createTryAgainIntent());
pendingServiceIntent(mXmppConnectionService, XmppConnectionService.ACTION_TRY_AGAIN, 45));
if (torNotAvailable) {
if (TorServiceUtils.isOrbotInstalled(mXmppConnectionService)) {
mBuilder.addAction(
@ -1867,7 +1867,7 @@ public class NotificationService {
: PendingIntent.FLAG_UPDATE_CURRENT));
}
}
mBuilder.setDeleteIntent(createDismissErrorIntent());
mBuilder.setDeleteIntent(pendingServiceIntent(mXmppConnectionService,XmppConnectionService.ACTION_DISMISS_ERROR_NOTIFICATIONS, 69));
mBuilder.setVisibility(Notification.VISIBILITY_PRIVATE);
mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
mBuilder.setLocalOnly(true);

View file

@ -184,6 +184,8 @@ public class XmppConnectionService extends Service {
public static final String ACTION_CLEAR_MISSED_CALL_NOTIFICATION = "clear_missed_call_notification";
public static final String ACTION_DISMISS_ERROR_NOTIFICATIONS = "dismiss_error";
public static final String ACTION_TRY_AGAIN = "try_again";
public static final String ACTION_TEMPORARILY_DISABLE = "temporarily_disable";
public static final String ACTION_IDLE_PING = "idle_ping";
public static final String ACTION_FCM_TOKEN_REFRESH = "fcm_token_refresh";
public static final String ACTION_FCM_MESSAGE_RECEIVED = "fcm_message_received";
@ -452,9 +454,9 @@ public class XmppConnectionService extends Service {
joinMuc(conversation);
}
scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode());
} else if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED) {
} else if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED || account.getStatus() == Account.State.LOGGED_OUT) {
resetSendingToWaiting(account);
if (account.isEnabled() && isInLowPingTimeoutMode(account)) {
if (account.isConnectionEnabled() && isInLowPingTimeoutMode(account)) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": went into offline state during low ping mode. reconnecting now");
reconnectAccount(account, true, false);
} else {
@ -846,6 +848,9 @@ public class XmppConnectionService extends Service {
Log.d(Config.LOGTAG, "received uri permission for " + uri);
}
return START_STICKY;
case ACTION_TEMPORARILY_DISABLE:
toggleSoftDisabled(true);
return START_NOT_STICKY;
}
}
synchronized (this) {
@ -961,6 +966,16 @@ public class XmppConnectionService extends Service {
return pingNow;
}
private void toggleSoftDisabled(final boolean softDisabled) {
for(final Account account : this.accounts) {
if (account.isEnabled()) {
if (account.setOption(Account.OPTION_SOFT_DISABLED, softDisabled)) {
updateAccount(account);
}
}
}
}
public boolean processUnifiedPushMessage(final Account account, final Jid transport, final Element push) {
return unifiedPushBroker.processPushMessage(account, transport, push);
}
@ -1449,7 +1464,7 @@ public class XmppConnectionService extends Service {
private void logoutAndSave(boolean stop) {
int activeAccounts = 0;
for (final Account account : accounts) {
if (account.getStatus() != Account.State.DISABLED) {
if (account.isConnectionEnabled()) {
databaseBackend.writeRoster(account.getRoster());
activeAccounts++;
}
@ -2808,6 +2823,7 @@ public class XmppConnectionService extends Service {
}
private void switchToForeground() {
toggleSoftDisabled(false);
final boolean broadcastLastActivity = broadcastLastActivity();
for (Conversation conversation : getConversations()) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
@ -3188,8 +3204,8 @@ public class XmppConnectionService extends Service {
if (this.accounts == null) {
return false;
}
for (Account account : this.accounts) {
if (account.isEnabled()) {
for (final Account account : this.accounts) {
if (account.isConnectionEnabled()) {
return true;
}
}
@ -3587,23 +3603,23 @@ public class XmppConnectionService extends Service {
});
}
private void disconnect(Account account, boolean force) {
if ((account.getStatus() == Account.State.ONLINE)
|| (account.getStatus() == Account.State.DISABLED)) {
final XmppConnection connection = account.getXmppConnection();
if (!force) {
List<Conversation> conversations = getConversations();
for (Conversation conversation : conversations) {
if (conversation.getAccount() == account) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
leaveMuc(conversation, true);
}
private void disconnect(final Account account, boolean force) {
final XmppConnection connection = account.getXmppConnection();
if (connection == null) {
return;
}
if (!force) {
final List<Conversation> conversations = getConversations();
for (Conversation conversation : conversations) {
if (conversation.getAccount() == account) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
leaveMuc(conversation, true);
}
}
sendOfflinePresence(account);
}
connection.disconnect(force);
sendOfflinePresence(account);
}
connection.disconnect(force);
}
@Override
@ -4093,7 +4109,7 @@ public class XmppConnectionService extends Service {
account.setXmppConnection(connection);
}
boolean hasInternet = hasInternetConnection();
if (account.isEnabled() && hasInternet) {
if (account.isConnectionEnabled() && hasInternet) {
if (!force) {
disconnect(account, false);
}
@ -4581,7 +4597,7 @@ public class XmppConnectionService extends Service {
public void refreshAllPresences() {
boolean includeIdleTimestamp = checkListeners() && broadcastLastActivity();
for (Account account : getAccounts()) {
if (account.isEnabled()) {
if (account.isConnectionEnabled()) {
sendPresence(account, includeIdleTimestamp);
}
}

View file

@ -270,7 +270,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
return;
}
for (final Account account : xmppConnectionService.getAccounts()) {
if (account.getStatus() != Account.State.DISABLED) {
if (account.isEnabled()) {
for (final Contact contact : account.getRoster().getContacts()) {
if (contact.showInContactList() &&
!filterContacts.contains(contact.getJid().asBareJid().toString())
@ -362,7 +362,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
filterContacts();
this.mActivatedAccounts.clear();
for (Account account : xmppConnectionService.getAccounts()) {
if (account.getStatus() != Account.State.DISABLED) {
if (account.isEnabled()) {
if (Config.DOMAIN_LOCK != null) {
this.mActivatedAccounts.add(account.getJid().getEscapedLocal());
} else {
@ -382,6 +382,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
ScanActivity.onRequestPermissionResult(this, requestCode, grantResults);
}

View file

@ -416,6 +416,7 @@ public class ConversationFragment extends XmppFragment
public void onClick(View v) {
final Account account = conversation == null ? null : conversation.getAccount();
if (account != null) {
account.setOption(Account.OPTION_SOFT_DISABLED, false);
account.setOption(Account.OPTION_DISABLED, false);
activity.xmppConnectionService.updateAccount(account);
}
@ -2660,6 +2661,8 @@ public class ConversationFragment extends XmppFragment
R.string.this_account_is_disabled,
R.string.enable,
this.mEnableAccountListener);
} else if (account.getStatus() == Account.State.LOGGED_OUT) {
showSnackbar(R.string.this_account_is_logged_out,R.string.log_in,this.mEnableAccountListener);
} else if (conversation.isBlocked()) {
showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener);
} else if (contact != null

View file

@ -149,7 +149,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
if (mInitMode && mAccount != null) {
mAccount.setOption(Account.OPTION_DISABLED, false);
}
if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited) {
if (mAccount != null && Arrays.asList(Account.State.DISABLED, Account.State.LOGGED_OUT).contains(mAccount.getStatus()) && !accountInfoEdited) {
mAccount.setOption(Account.OPTION_SOFT_DISABLED, false);
mAccount.setOption(Account.OPTION_DISABLED, false);
if (!xmppConnectionService.updateAccount(mAccount)) {
Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();

View file

@ -61,7 +61,7 @@ public class ShortcutActivity extends AbstractSearchableListItemActivity {
return;
}
for (final Account account : xmppConnectionService.getAccounts()) {
if (account.getStatus() != Account.State.DISABLED) {
if (account.isEnabled()) {
for (final Contact contact : account.getRoster().getContacts()) {
if (contact.showInContactList()
&& contact.match(this, needle)) {

View file

@ -971,8 +971,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
protected void filterContacts(String needle) {
this.contacts.clear();
final List<Account> accounts = xmppConnectionService.getAccounts();
for (Account account : accounts) {
if (account.getStatus() != Account.State.DISABLED) {
for (final Account account : accounts) {
if (account.isEnabled()) {
for (Contact contact : account.getRoster().getContacts()) {
Presence.Status s = contact.getShownStatus();
if (contact.showInContactList() && contact.match(this, needle)
@ -991,7 +991,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
protected void filterConferences(String needle) {
this.conferences.clear();
for (final Account account : xmppConnectionService.getAccounts()) {
if (account.getStatus() != Account.State.DISABLED) {
if (account.isEnabled()) {
for (final Bookmark bookmark : account.getBookmarks()) {
if (bookmark.match(this, needle)) {
this.conferences.add(bookmark);

View file

@ -59,6 +59,7 @@ public class AccountAdapter extends ArrayAdapter<Account> {
viewHolder.binding.accountStatus.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorOnline));
break;
case DISABLED:
case LOGGED_OUT:
case CONNECTING:
viewHolder.binding.accountStatus.setTextColor(StyledAttributes.getColor(activity, android.R.attr.textColorSecondary));
break;

View file

@ -60,7 +60,7 @@ public class AccountUtils {
public static List<String> getEnabledAccounts(final XmppConnectionService service) {
final ArrayList<String> accounts = new ArrayList<>();
for (final Account account : service.getAccounts()) {
if (account.getStatus() != Account.State.DISABLED) {
if (account.isEnabled()) {
if (Config.DOMAIN_LOCK != null) {
accounts.add(account.getJid().getEscapedLocal());
} else {

View file

@ -233,10 +233,11 @@ public class XmppConnection implements Runnable {
return;
}
if (account.getStatus() != nextStatus) {
if ((nextStatus == Account.State.OFFLINE)
&& (account.getStatus() != Account.State.CONNECTING)
&& (account.getStatus() != Account.State.ONLINE)
&& (account.getStatus() != Account.State.DISABLED)) {
if (nextStatus == Account.State.OFFLINE
&& account.getStatus() != Account.State.CONNECTING
&& account.getStatus() != Account.State.ONLINE
&& account.getStatus() != Account.State.DISABLED
&& account.getStatus() != Account.State.LOGGED_OUT) {
return;
}
if (nextStatus == Account.State.ONLINE) {

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#FFFFFF" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17,7l-1.41,1.41L18.17,11H8v2h10.17l-2.58,2.58L17,17l5,-5zM4,5h8V3H4c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h8v-2H4V5z"/>
</vector>

View file

@ -153,6 +153,7 @@
<string name="error_security_exception">The app you used to share this file did not provide enough permissions.</string>
<string name="account_status_unknown">Unknown</string>
<string name="account_status_disabled">Temporarily disabled</string>
<string name="account_state_logged_out">Logged out</string>
<string name="account_status_online">Online</string>
<string name="account_status_connecting">Connecting\u2026</string>
<string name="account_status_offline">Offline</string>
@ -538,6 +539,7 @@
<string name="send_corrected_message">Send corrected message</string>
<string name="no_keys_just_confirm">You have already validated this persons fingerprint securely to confirm trust. By selecting “Done” you are just confirming that %s is part of this group chat.</string>
<string name="this_account_is_disabled">You have disabled this account</string>
<string name="this_account_is_logged_out">You have logged out of this account</string>
<string name="security_error_invalid_file_access">Security error: Invalid file access!</string>
<string name="no_application_to_share_uri">No app found to share URI</string>
<string name="share_uri_with">Share URI with…</string>
@ -1013,5 +1015,7 @@
<string name="decline">Decline</string>
<string name="delete_from_server">Remove account from server</string>
<string name="could_not_delete_account_from_server">Could not delete account from server</string>
<string name="hide_notification">Hide notification</string>
<string name="log_out">Log out</string>
<string name="log_in">Log in</string>
</resources>