From 1901abd05fc051b776e2bbb10295f936408a0843 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 22 Apr 2016 21:25:06 +0200 Subject: [PATCH] expert setting to manually change presence --- src/main/AndroidManifest.xml | 6 + .../siacs/conversations/entities/Account.java | 35 ++- .../conversations/entities/Bookmark.java | 13 +- .../siacs/conversations/entities/Contact.java | 30 +-- .../conversations/entities/ListItem.java | 6 +- .../conversations/entities/Presence.java | 43 ++-- .../entities/PresenceTemplate.java | 55 +++++ .../conversations/parser/PresenceParser.java | 3 +- .../persistance/DatabaseBackend.java | 44 +++- .../services/XmppConnectionService.java | 40 +++- .../conversations/ui/BlocklistActivity.java | 2 +- .../ui/ChooseContactActivity.java | 2 +- .../ui/ConferenceDetailsActivity.java | 11 - .../ui/ContactDetailsActivity.java | 2 +- .../ui/ConversationFragment.java | 12 +- .../conversations/ui/EditAccountActivity.java | 14 +- .../conversations/ui/SetPresenceActivity.java | 200 ++++++++++++++++++ .../conversations/ui/SettingsActivity.java | 6 +- .../ui/StartConversationActivity.java | 4 +- .../siacs/conversations/ui/XmppActivity.java | 15 ++ .../ui/adapter/ListItemAdapter.java | 2 +- .../siacs/conversations/utils/UIHelper.java | 17 ++ .../ic_account_box_white_24dp.png | Bin 0 -> 337 bytes .../ic_announcement_white_24dp.png | Bin 0 -> 251 bytes .../ic_account_box_white_24dp.png | Bin 0 -> 290 bytes .../ic_announcement_white_24dp.png | Bin 0 -> 214 bytes .../ic_account_box_white_24dp.png | Bin 0 -> 431 bytes .../ic_announcement_white_24dp.png | Bin 0 -> 285 bytes .../ic_account_box_white_24dp.png | Bin 0 -> 578 bytes .../ic_announcement_white_24dp.png | Bin 0 -> 355 bytes .../ic_account_box_white_24dp.png | Bin 0 -> 894 bytes .../ic_announcement_white_24dp.png | Bin 0 -> 519 bytes src/main/res/layout/activity_set_presence.xml | 72 +++++++ src/main/res/layout/presence_template.xml | 49 +++++ src/main/res/layout/simple_list_item.xml | 26 +++ src/main/res/menu/change_presence.xml | 17 ++ src/main/res/menu/editaccount.xml | 6 + src/main/res/values/arrays.xml | 8 + src/main/res/values/strings.xml | 10 + src/main/res/xml/preferences.xml | 12 +- 40 files changed, 683 insertions(+), 79 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/entities/PresenceTemplate.java create mode 100644 src/main/java/eu/siacs/conversations/ui/SetPresenceActivity.java create mode 100644 src/main/res/drawable-hdpi/ic_account_box_white_24dp.png create mode 100644 src/main/res/drawable-hdpi/ic_announcement_white_24dp.png create mode 100644 src/main/res/drawable-mdpi/ic_account_box_white_24dp.png create mode 100644 src/main/res/drawable-mdpi/ic_announcement_white_24dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_account_box_white_24dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_announcement_white_24dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_account_box_white_24dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_announcement_white_24dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_account_box_white_24dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_announcement_white_24dp.png create mode 100644 src/main/res/layout/activity_set_presence.xml create mode 100644 src/main/res/layout/presence_template.xml create mode 100644 src/main/res/layout/simple_list_item.xml create mode 100644 src/main/res/menu/change_presence.xml diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 83f1c517..182a8ee9 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -87,6 +87,12 @@ android:label="@string/create_account" android:screenOrientation="portrait" android:launchMode="singleTask"/> + diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 2356ffb9..c172f0e7 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -43,6 +43,8 @@ public class Account extends AbstractEntity { public static final String DISPLAY_NAME = "display_name"; public static final String HOSTNAME = "hostname"; public static final String PORT = "port"; + public static final String STATUS = "status"; + public static final String STATUS_MESSAGE = "status_message"; public static final String PINNED_MECHANISM_KEY = "pinned_mechanism"; @@ -168,15 +170,18 @@ public class Account extends AbstractEntity { private final Roster roster = new Roster(this); private List bookmarks = new CopyOnWriteArrayList<>(); private final Collection blocklist = new CopyOnWriteArraySet<>(); + private Presence.Status presenceStatus = Presence.Status.ONLINE; + private String presenceStatusMessage = null; public Account(final Jid jid, final String password) { this(java.util.UUID.randomUUID().toString(), jid, - password, 0, null, "", null, null, null, 5222); + password, 0, null, "", null, null, null, 5222, Presence.Status.ONLINE, null); } private Account(final String uuid, final Jid jid, - final String password, final int options, final String rosterVersion, final String keys, - final String avatar, String displayName, String hostname, int port) { + final String password, final int options, final String rosterVersion, final String keys, + final String avatar, String displayName, String hostname, int port, + final Presence.Status status, String statusMessage) { this.uuid = uuid; this.jid = jid; if (jid.isBareJid()) { @@ -194,6 +199,8 @@ public class Account extends AbstractEntity { this.displayName = displayName; this.hostname = hostname; this.port = port; + this.presenceStatus = status; + this.presenceStatusMessage = statusMessage; } public static Account fromCursor(final Cursor cursor) { @@ -212,7 +219,9 @@ public class Account extends AbstractEntity { cursor.getString(cursor.getColumnIndex(AVATAR)), cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)), cursor.getString(cursor.getColumnIndex(HOSTNAME)), - cursor.getInt(cursor.getColumnIndex(PORT))); + cursor.getInt(cursor.getColumnIndex(PORT)), + Presence.Status.fromShowString(cursor.getString(cursor.getColumnIndex(STATUS))), + cursor.getString(cursor.getColumnIndex(STATUS_MESSAGE))); } public boolean isOptionSet(final int option) { @@ -287,6 +296,22 @@ public class Account extends AbstractEntity { return getXmppConnection() != null && getStatus().isError() && getXmppConnection().getAttempt() >= 3; } + public void setPresenceStatus(Presence.Status status) { + this.presenceStatus = status; + } + + public Presence.Status getPresenceStatus() { + return this.presenceStatus; + } + + public void setPresenceStatusMessage(String message) { + this.presenceStatusMessage = message; + } + + public String getPresenceStatusMessage() { + return this.presenceStatusMessage; + } + public String getResource() { return jid.getResourcepart(); } @@ -347,6 +372,8 @@ public class Account extends AbstractEntity { values.put(DISPLAY_NAME, displayName); values.put(HOSTNAME, hostname); values.put(PORT, port); + values.put(STATUS, presenceStatus.toShowString()); + values.put(STATUS_MESSAGE, presenceStatusMessage); return values; } diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index 088dfd8a..8d97751c 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.entities; +import android.content.Context; + import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -76,7 +78,7 @@ public class Bookmark extends Element implements ListItem { } @Override - public List getTags() { + public List getTags(Context context) { ArrayList tags = new ArrayList(); for (Element element : getChildren()) { if (element.getName().equals("group") && element.getContent() != null) { @@ -114,7 +116,8 @@ public class Bookmark extends Element implements ListItem { } } - public boolean match(String needle) { + @Override + public boolean match(Context context, String needle) { if (needle == null) { return true; } @@ -122,12 +125,12 @@ public class Bookmark extends Element implements ListItem { final Jid jid = getJid(); return (jid != null && jid.toString().contains(needle)) || getDisplayName().toLowerCase(Locale.US).contains(needle) || - matchInTag(needle); + matchInTag(context, needle); } - private boolean matchInTag(String needle) { + private boolean matchInTag(Context context, String needle) { needle = needle.toLowerCase(Locale.US); - for (Tag tag : getTags()) { + for (Tag tag : getTags(context)) { if (tag.getName().toLowerCase(Locale.US).contains(needle)) { return true; } diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 691fc3e4..e608688b 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -1,6 +1,7 @@ package eu.siacs.conversations.entities; import android.content.ContentValues; +import android.content.Context; import android.database.Cursor; import org.json.JSONArray; @@ -141,25 +142,14 @@ public class Contact implements ListItem, Blockable { } @Override - public List getTags() { + public List getTags(Context context) { final ArrayList tags = new ArrayList<>(); for (final String group : getGroups()) { tags.add(new Tag(group, UIHelper.getColorForName(group))); } - switch (getMostAvailableStatus()) { - case CHAT: - case ONLINE: - tags.add(new Tag("online", 0xff259b24)); - break; - case AWAY: - tags.add(new Tag("away", 0xffff9800)); - break; - case XA: - tags.add(new Tag("not available", 0xfff44336)); - break; - case DND: - tags.add(new Tag("dnd", 0xfff44336)); - break; + Presence.Status status = getMostAvailableStatus(); + if (status != Presence.Status.OFFLINE) { + tags.add(UIHelper.getTagForStatus(context, status)); } if (isBlocked()) { tags.add(new Tag("blocked", 0xff2e2f3b)); @@ -167,7 +157,7 @@ public class Contact implements ListItem, Blockable { return tags; } - public boolean match(String needle) { + public boolean match(Context context, String needle) { if (needle == null || needle.isEmpty()) { return true; } @@ -175,7 +165,7 @@ public class Contact implements ListItem, Blockable { String[] parts = needle.split("\\s+"); if (parts.length > 1) { for(int i = 0; i < parts.length; ++i) { - if (!match(parts[i])) { + if (!match(context, parts[i])) { return false; } } @@ -183,13 +173,13 @@ public class Contact implements ListItem, Blockable { } else { return jid.toString().contains(needle) || getDisplayName().toLowerCase(Locale.US).contains(needle) || - matchInTag(needle); + matchInTag(context, needle); } } - private boolean matchInTag(String needle) { + private boolean matchInTag(Context context, String needle) { needle = needle.toLowerCase(Locale.US); - for (Tag tag : getTags()) { + for (Tag tag : getTags(context)) { if (tag.getName().toLowerCase(Locale.US).contains(needle)) { return true; } diff --git a/src/main/java/eu/siacs/conversations/entities/ListItem.java b/src/main/java/eu/siacs/conversations/entities/ListItem.java index 22aedd4b..178df2d1 100644 --- a/src/main/java/eu/siacs/conversations/entities/ListItem.java +++ b/src/main/java/eu/siacs/conversations/entities/ListItem.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.entities; +import android.content.Context; + import java.util.List; import eu.siacs.conversations.xmpp.jid.Jid; @@ -11,7 +13,7 @@ public interface ListItem extends Comparable { Jid getJid(); - List getTags(); + List getTags(Context context); final class Tag { private final String name; @@ -31,5 +33,5 @@ public interface ListItem extends Comparable { } } - boolean match(final String needle); + boolean match(Context context, final String needle); } diff --git a/src/main/java/eu/siacs/conversations/entities/Presence.java b/src/main/java/eu/siacs/conversations/entities/Presence.java index 442f1bca..485adaa1 100644 --- a/src/main/java/eu/siacs/conversations/entities/Presence.java +++ b/src/main/java/eu/siacs/conversations/entities/Presence.java @@ -17,41 +17,46 @@ public class Presence implements Comparable { case XA: return "xa"; case DND: return "dnd"; } - return null; } + + public static Status fromShowString(String show) { + if (show == null) { + return ONLINE; + } else { + switch (show.toLowerCase(Locale.US)) { + case "away": + return AWAY; + case "xa": + return XA; + case "dnd": + return DND; + case "chat": + return CHAT; + default: + return ONLINE; + } + } + } } protected final Status status; protected ServiceDiscoveryResult disco; protected final String ver; protected final String hash; + protected final String message; - private Presence(Status status, String ver, String hash) { + private Presence(Status status, String ver, String hash, String message) { this.status = status; this.ver = ver; this.hash = hash; + this.message = message; } - public static Presence parse(String show, Element caps) { + public static Presence parse(String show, Element caps, String message) { final String hash = caps == null ? null : caps.getAttribute("hash"); final String ver = caps == null ? null : caps.getAttribute("ver"); - if (show == null) { - return new Presence(Status.ONLINE, ver, hash); - } else { - switch (show.toLowerCase(Locale.US)) { - case "away": - return new Presence(Status.AWAY, ver, hash); - case "xa": - return new Presence(Status.XA, ver, hash); - case "dnd": - return new Presence(Status.DND, ver, hash); - case "chat": - return new Presence(Status.CHAT, ver, hash); - default: - return new Presence(Status.ONLINE, ver, hash); - } - } + return new Presence(Status.fromShowString(show), ver, hash, message); } public int compareTo(Object other) { diff --git a/src/main/java/eu/siacs/conversations/entities/PresenceTemplate.java b/src/main/java/eu/siacs/conversations/entities/PresenceTemplate.java new file mode 100644 index 00000000..dd45d7e4 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/PresenceTemplate.java @@ -0,0 +1,55 @@ +package eu.siacs.conversations.entities; + +import android.content.ContentValues; +import android.database.Cursor; + + +public class PresenceTemplate extends AbstractEntity { + + public static final String TABELNAME = "presence_templates"; + public static final String LAST_USED = "last_used"; + public static final String MESSAGE = "message"; + public static final String STATUS = "status"; + + private long lastUsed = 0; + private String statusMessage; + private Presence.Status status = Presence.Status.ONLINE; + + public PresenceTemplate(Presence.Status status, String statusMessage) { + this.status = status; + this.statusMessage = statusMessage; + this.lastUsed = System.currentTimeMillis(); + this.uuid = java.util.UUID.randomUUID().toString(); + } + + private PresenceTemplate() { + + } + + @Override + public ContentValues getContentValues() { + ContentValues values = new ContentValues(); + values.put(LAST_USED, lastUsed); + values.put(MESSAGE, statusMessage); + values.put(STATUS, status.toShowString()); + values.put(UUID, uuid); + return values; + } + + public static PresenceTemplate fromCursor(Cursor cursor) { + PresenceTemplate template = new PresenceTemplate(); + template.uuid = cursor.getString(cursor.getColumnIndex(UUID)); + template.lastUsed = cursor.getLong(cursor.getColumnIndex(LAST_USED)); + template.statusMessage = cursor.getString(cursor.getColumnIndex(MESSAGE)); + template.status = Presence.Status.fromShowString(cursor.getString(cursor.getColumnIndex(STATUS))); + return template; + } + + public Presence.Status getStatus() { + return status; + } + + public String getStatusMessage() { + return statusMessage; + } +} diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index c2782d23..5c5ca6be 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -196,7 +196,8 @@ public class PresenceParser extends AbstractParser implements final String show = packet.findChildContent("show"); final Element caps = packet.findChild("c", "http://jabber.org/protocol/caps"); - final Presence presence = Presence.parse(show, caps); + final String message = packet.findChildContent("status"); + final Presence presence = Presence.parse(show, caps, message); contact.updatePresence(resource, presence); if (presence.hasCaps() && Config.REQUEST_DISCO) { mXmppConnectionService.fetchCaps(account, from, presence); diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 04c53238..1ccc1e41 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -41,6 +41,7 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.PresenceTemplate; import eu.siacs.conversations.entities.Roster; import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.xmpp.jid.InvalidJidException; @@ -51,7 +52,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 25; + private static final int DATABASE_VERSION = 26; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -73,6 +74,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { + "UNIQUE(" + ServiceDiscoveryResult.HASH + ", " + ServiceDiscoveryResult.VER + ") ON CONFLICT REPLACE);"; + private static String CREATE_PRESENCE_TEMPLATES_STATEMENT = "CREATE TABLE " + + PresenceTemplate.TABELNAME + "(" + + PresenceTemplate.UUID + " TEXT, " + + PresenceTemplate.LAST_USED + " NUMBER," + + PresenceTemplate.MESSAGE + " TEXT," + + PresenceTemplate.STATUS + " TEXT," + + "UNIQUE("+PresenceTemplate.MESSAGE + "," +PresenceTemplate.STATUS+") ON CONFLICT REPLACE);"; + private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE " + SQLiteAxolotlStore.PREKEY_TABLENAME + "(" + SQLiteAxolotlStore.ACCOUNT + " TEXT, " @@ -175,6 +184,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_PREKEYS_STATEMENT); db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); db.execSQL(CREATE_IDENTITIES_STATEMENT); + db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT); } @Override @@ -331,6 +341,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT"); db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222"); } + if (oldVersion < 26 && newVersion >= 26) { + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS + " TEXT"); + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS_MESSAGE + " TEXT"); + } /* Any migrations that alter the Account table need to happen BEFORE this migration, as it * depends on account de-serialization. */ @@ -380,6 +394,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 25 && newVersion >= 25) { db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER"); } + + if (oldVersion < 26 && newVersion >= 26) { + db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT); + } } public static synchronized DatabaseBackend getInstance(Context context) { @@ -430,6 +448,30 @@ public class DatabaseBackend extends SQLiteOpenHelper { return result; } + public void insertPresenceTemplate(PresenceTemplate template) { + SQLiteDatabase db = this.getWritableDatabase(); + db.insert(PresenceTemplate.TABELNAME, null, template.getContentValues()); + } + + public List getPresenceTemplates() { + ArrayList templates = new ArrayList<>(); + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor = db.query(PresenceTemplate.TABELNAME,null,null,null,null,null,PresenceTemplate.LAST_USED+" desc"); + while (cursor.moveToNext()) { + templates.add(PresenceTemplate.fromCursor(cursor)); + } + cursor.close(); + return templates; + } + + public void deletePresenceTemplate(PresenceTemplate template) { + Log.d(Config.LOGTAG,"deleting presence template with uuid "+template.getUuid()); + SQLiteDatabase db = this.getWritableDatabase(); + String where = PresenceTemplate.UUID+"=?"; + String[] whereArgs = {template.getUuid()}; + db.delete(PresenceTemplate.TABELNAME,where,whereArgs); + } + public CopyOnWriteArrayList getConversations(int status) { CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 5040664a..4aee6079 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -73,6 +73,7 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.OnRenameListener; import eu.siacs.conversations.entities.Presence; +import eu.siacs.conversations.entities.PresenceTemplate; import eu.siacs.conversations.entities.Roster; import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.entities.Transferable; @@ -629,6 +630,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return getPreferences().getBoolean("xa_on_silent_mode", false); } + private boolean manuallyChangePresence() { + return getPreferences().getBoolean("manually_change_presence", false); + } + private boolean treatVibrateAsSilent() { return getPreferences().getBoolean("treat_vibrate_as_silent", false); } @@ -762,7 +767,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void toggleScreenEventReceiver() { - if (awayWhenScreenOff()) { + if (awayWhenScreenOff() && !manuallyChangePresence()) { final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); registerReceiver(this.mEventReceiver, filter); @@ -2998,7 +3003,17 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void sendPresence(final Account account) { - sendPresencePacket(account, mPresenceGenerator.selfPresence(account, getTargetPresence())); + PresencePacket packet; + if (manuallyChangePresence()) { + packet = mPresenceGenerator.selfPresence(account, account.getPresenceStatus()); + String message = account.getPresenceStatusMessage(); + if (message != null && !message.isEmpty()) { + packet.addChild(new Element("status").setContent(message)); + } + } else { + packet = mPresenceGenerator.selfPresence(account, getTargetPresence()); + } + sendPresencePacket(account, packet); } public void refreshAllPresences() { @@ -3229,6 +3244,27 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return pending; } + public void changeStatus(Account account, Presence.Status status, String statusMessage) { + databaseBackend.insertPresenceTemplate(new PresenceTemplate(status, statusMessage)); + changeStatusReal(account, status, statusMessage); + } + + private void changeStatusReal(Account account, Presence.Status status, String statusMessage) { + account.setPresenceStatus(status); + account.setPresenceStatusMessage(statusMessage); + databaseBackend.updateAccount(account); + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + sendPresence(account); + } + } + + public void changeStatus(Presence.Status status, String statusMessage) { + databaseBackend.insertPresenceTemplate(new PresenceTemplate(status, statusMessage)); + for(Account account : getAccounts()) { + changeStatusReal(account, status, statusMessage); + } + } + public interface OnMamPreferencesFetched { void onPreferencesFetched(Element prefs); void onPreferencesFetchFailed(); diff --git a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java index 5a85c17b..abec8ed7 100644 --- a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java @@ -49,7 +49,7 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem if (account != null) { for (final Jid jid : account.getBlocklist()) { final Contact contact = account.getRoster().getContact(jid); - if (contact.match(needle) && contact.isBlocked()) { + if (contact.match(this, needle) && contact.isBlocked()) { getListItems().add(contact); } } diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java index c5357a5e..1698343b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java @@ -144,7 +144,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { for (final Contact contact : account.getRoster().getContacts()) { if (contact.showInRoster() && !filterContacts.contains(contact.getJid().toBareJid().toString()) - && contact.match(needle)) { + && contact.match(this, needle)) { getListItems().add(contact); } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index a2757070..d7420359 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -643,17 +643,6 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } } - @SuppressWarnings("deprecation") - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private void setListItemBackgroundOnView(View view) { - int sdk = android.os.Build.VERSION.SDK_INT; - if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) { - view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground)); - } else { - view.setBackground(getResources().getDrawable(R.drawable.greybackground)); - } - } - private void viewPgpKey(User user) { PgpEngine pgp = xmppConnectionService.getPgpEngine(); if (pgp != null) { diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index e4fc59fa..308bc742 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -439,7 +439,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd keys.setVisibility(View.GONE); } - List tagList = contact.getTags(); + List tagList = contact.getTags(this); if (tagList.size() == 0 || !this.showDynamicTags) { tags.setVisibility(View.GONE); } else { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 095bf58f..23b68e3b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -474,9 +474,15 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } else { Account account = message.getConversation().getAccount(); - Intent intent = new Intent(activity, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().toBareJid().toString()); - intent.putExtra("fingerprint", message.getFingerprint()); + Intent intent; + if (activity.manuallyChangePresence()) { + intent = new Intent(activity, SetPresenceActivity.class); + intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT, account.getJid().toBareJid().toString()); + } else { + intent = new Intent(activity, EditAccountActivity.class); + intent.putExtra("jid", account.getJid().toBareJid().toString()); + intent.putExtra("fingerprint", message.getFingerprint()); + } startActivity(intent); } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index b2c7f1a9..04824193 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -476,6 +476,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices); final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate); final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs); + final MenuItem changePresence = menu.findItem(R.id.action_change_presence); + + changePresence.setVisible(manuallyChangePresence()); renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null); @@ -536,8 +539,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate protected void onBackendConnected() { if (this.jidToEdit != null) { this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit); - this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER); if (this.mAccount != null) { + this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER); if (this.mAccount.getPrivateKeyAlias() != null) { this.mPassword.setHint(R.string.authenticate_with_certificate); if (this.mInitMode) { @@ -593,6 +596,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate case R.id.action_renew_certificate: renewCertificate(); break; + case R.id.action_change_presence: + changePresence(); + break; } return super.onOptionsItemSelected(item); } @@ -601,6 +607,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null); } + private void changePresence() { + Intent intent = new Intent(this, SetPresenceActivity.class); + intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT,mAccount.getJid().toBareJid().toString()); + startActivity(intent); + } + @Override public void alias(String alias) { if (alias != null) { diff --git a/src/main/java/eu/siacs/conversations/ui/SetPresenceActivity.java b/src/main/java/eu/siacs/conversations/ui/SetPresenceActivity.java new file mode 100644 index 00000000..5fe9c5d8 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/SetPresenceActivity.java @@ -0,0 +1,200 @@ +package eu.siacs.conversations.ui; + +import android.os.Bundle; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.Spinner; +import android.widget.TextView; + +import android.util.Log; + +import java.util.List; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.ListItem; +import eu.siacs.conversations.entities.Presence; +import eu.siacs.conversations.entities.PresenceTemplate; +import eu.siacs.conversations.utils.UIHelper; + +public class SetPresenceActivity extends XmppActivity implements View.OnClickListener { + + //data + protected Account mAccount; + private List mTemplates; + + //UI Elements + protected ScrollView mScrollView; + protected EditText mStatusMessage; + protected Spinner mShowSpinner; + protected CheckBox mAllAccounts; + protected LinearLayout mTemplatesView; + + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_set_presence); + mScrollView = (ScrollView) findViewById(R.id.scroll_view); + mShowSpinner = (Spinner) findViewById(R.id.presence_show); + ArrayAdapter adapter = ArrayAdapter.createFromResource(this, + R.array.presence_show_options, + R.layout.simple_list_item); + mShowSpinner.setAdapter(adapter); + mShowSpinner.setSelection(1); + mStatusMessage = (EditText) findViewById(R.id.presence_status_message); + mAllAccounts = (CheckBox) findViewById(R.id.all_accounts); + mTemplatesView = (LinearLayout) findViewById(R.id.templates); + final Button changePresence = (Button) findViewById(R.id.change_presence); + changePresence.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + executeChangePresence(); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.change_presence, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == R.id.action_account_details) { + if (mAccount != null) { + switchToAccount(mAccount); + } + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + private void executeChangePresence() { + Presence.Status status = getStatusFromSpinner(); + boolean allAccounts = mAllAccounts.isChecked(); + String statusMessage = mStatusMessage.getText().toString().trim(); + if (allAccounts) { + xmppConnectionService.changeStatus(status, statusMessage); + } else if (mAccount != null) { + xmppConnectionService.changeStatus(mAccount, status, statusMessage); + } + finish(); + } + + private Presence.Status getStatusFromSpinner() { + switch (mShowSpinner.getSelectedItemPosition()) { + case 0: + return Presence.Status.CHAT; + case 2: + return Presence.Status.AWAY; + case 3: + return Presence.Status.XA; + case 4: + return Presence.Status.DND; + default: + return Presence.Status.ONLINE; + } + } + + private void setStatusInSpinner(Presence.Status status) { + switch(status) { + case AWAY: + mShowSpinner.setSelection(2); + break; + case XA: + mShowSpinner.setSelection(3); + break; + case CHAT: + mShowSpinner.setSelection(0); + break; + case DND: + mShowSpinner.setSelection(4); + break; + default: + mShowSpinner.setSelection(1); + break; + } + } + + @Override + protected void refreshUiReal() { + + } + + @Override + void onBackendConnected() { + mAccount = extractAccount(getIntent()); + if (mAccount != null) { + setStatusInSpinner(mAccount.getPresenceStatus()); + String message = mAccount.getPresenceStatusMessage(); + if (mStatusMessage.getText().length() == 0 && message != null) { + mStatusMessage.append(message); + } + mTemplates = xmppConnectionService.databaseBackend.getPresenceTemplates(); + } + redrawTemplates(); + } + + private void redrawTemplates() { + if (mTemplates == null || mTemplates.size() == 0) { + mTemplatesView.setVisibility(View.GONE); + } else { + mTemplatesView.removeAllViews(); + mTemplatesView.setVisibility(View.VISIBLE); + LayoutInflater inflater = getLayoutInflater(); + for (PresenceTemplate template : mTemplates) { + View templateLayout = inflater.inflate(R.layout.presence_template, mTemplatesView, false); + templateLayout.setTag(template); + setListItemBackgroundOnView(templateLayout); + templateLayout.setOnClickListener(this); + TextView message = (TextView) templateLayout.findViewById(R.id.presence_status_message); + TextView status = (TextView) templateLayout.findViewById(R.id.status); + ImageButton button = (ImageButton) templateLayout.findViewById(R.id.delete_button); + button.setTag(template); + button.setOnClickListener(this); + ListItem.Tag tag = UIHelper.getTagForStatus(this, template.getStatus()); + status.setText(tag.getName()); + status.setBackgroundColor(tag.getColor()); + message.setText(template.getStatusMessage()); + mTemplatesView.addView(templateLayout); + } + } + } + + @Override + public void onClick(View v) { + PresenceTemplate template = (PresenceTemplate) v.getTag(); + if (template == null) { + return; + } + if (v.getId() == R.id.presence_template) { + setStatusInSpinner(template.getStatus()); + mStatusMessage.getEditableText().clear(); + mStatusMessage.getEditableText().append(template.getStatusMessage()); + new Handler().post(new Runnable() { + @Override + public void run() { + mScrollView.smoothScrollTo(0,0); + } + }); + } else if (v.getId() == R.id.delete_button) { + xmppConnectionService.databaseBackend.deletePresenceTemplate(template); + mTemplates.remove(template); + redrawTemplates(); + } + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java index 750a7421..6eef7eb3 100644 --- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java @@ -161,7 +161,8 @@ public class SettingsActivity extends XmppActivity implements "xa_on_silent_mode", "away_when_screen_off", "allow_message_correction", - "treat_vibrate_as_silent"); + "treat_vibrate_as_silent", + "manually_change_presence"); if (name.equals("resource")) { String resource = preferences.getString("resource", "mobile") .toLowerCase(Locale.US); @@ -182,7 +183,8 @@ public class SettingsActivity extends XmppActivity implements xmppConnectionService.toggleForegroundService(); } else if (resendPresence.contains(name)) { if (xmppConnectionServiceBound) { - if (name.equals("away_when_screen_off")) { + if (name.equals("away_when_screen_off") + || name.equals("manually_change_presence")) { xmppConnectionService.toggleScreenEventReceiver(); } xmppConnectionService.refreshAllPresences(); diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 7d650e5b..4a59da21 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -738,7 +738,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU for (Contact contact : account.getRoster().getContacts()) { Presence p = contact.getPresences().getMostAvailablePresence(); Presence.Status s = p == null ? Presence.Status.OFFLINE : p.getStatus(); - if (contact.showInRoster() && contact.match(needle) + if (contact.showInRoster() && contact.match(this, needle) && (!this.mHideOfflineContacts || (needle != null && !needle.trim().isEmpty()) || s.compareTo(Presence.Status.OFFLINE) < 0)) { @@ -756,7 +756,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU for (Account account : xmppConnectionService.getAccounts()) { if (account.getStatus() != Account.State.DISABLED) { for (Bookmark bookmark : account.getBookmarks()) { - if (bookmark.match(needle)) { + if (bookmark.match(this, needle)) { this.conferences.add(bookmark); } } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 7d70b20f..17086b7e 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -536,6 +536,17 @@ public abstract class XmppActivity extends Activity { } } + @SuppressWarnings("deprecation") + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + protected void setListItemBackgroundOnView(View view) { + int sdk = android.os.Build.VERSION.SDK_INT; + if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) { + view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground)); + } else { + view.setBackground(getResources().getDrawable(R.drawable.greybackground)); + } + } + protected void choosePgpSignId(Account account) { xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback() { @Override @@ -1006,6 +1017,10 @@ public abstract class XmppActivity extends Activity { return getPreferences().getString("picture_compression", "auto").equals("never"); } + protected boolean manuallyChangePresence() { + return getPreferences().getBoolean("manually_change_presence", false); + } + protected void unregisterNdefPushMessageCallback() { NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); if (nfcAdapter != null && nfcAdapter.isEnabled()) { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java index da8e3910..02c54b77 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java @@ -62,7 +62,7 @@ public class ListItemAdapter extends ArrayAdapter { ImageView picture = (ImageView) view.findViewById(R.id.contact_photo); LinearLayout tagLayout = (LinearLayout) view.findViewById(R.id.tags); - List tags = item.getTags(); + List tags = item.getTags(activity); if (tags.size() == 0 || !this.showDynamicTags) { tagLayout.setVisibility(View.GONE); } else { diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index add3d80c..5ab058d1 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -14,7 +14,9 @@ import java.util.Locale; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Presence; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.xmpp.jid.Jid; @@ -267,4 +269,19 @@ public class UIHelper { body = body.replace("?","").replace("¿",""); return LOCATION_QUESTIONS.contains(body); } + + public static ListItem.Tag getTagForStatus(Context context, Presence.Status status) { + switch (status) { + case CHAT: + return new ListItem.Tag(context.getString(R.string.presence_chat), 0xff259b24); + case AWAY: + return new ListItem.Tag(context.getString(R.string.presence_away), 0xffff9800); + case XA: + return new ListItem.Tag(context.getString(R.string.presence_xa), 0xfff44336); + case DND: + return new ListItem.Tag(context.getString(R.string.presence_dnd), 0xfff44336); + default: + return new ListItem.Tag(context.getString(R.string.presence_online), 0xff259b24); + } + } } diff --git a/src/main/res/drawable-hdpi/ic_account_box_white_24dp.png b/src/main/res/drawable-hdpi/ic_account_box_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..2f0f491d6178654b22b4f50fa1a4bd9889f9d9f9 GIT binary patch literal 337 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZK3>dAqwX{BQ3+vmeOgEbxddW?dwo#yRyr(3FYln4+||$34T*cGBt_)jL*9o+}geWA{|A zCC9|YKkhaB;5cde_eT%bOg<;KKwH;)vGTQrPk9_xJ72nC5HfLc&@D&Xi{-HwPcO^} z(e~mBjk+0Xq;Y+L58Kjg$E9qxOWN1Eow@n!*H!+{TXawKL1rOi3B;IPhFVdQ&MBb@0Crn~PXGV_ literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-hdpi/ic_announcement_white_24dp.png b/src/main/res/drawable-hdpi/ic_announcement_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..034702001a8db3baa3e6567331b92b5705b2daeb GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZK3>dAqwX{BQ3+vmeOgEbxddW?Tp)5S5wO=~G=WkJ` z%hSa%#Nzbc%eGuC4m@oSzdZ7P-!y$=>^0jqjS9K^E!Dzv<4UCCcQ-nw_iUK4rLwC< zzbPhrC+{9f&Z!TdHhr)-@Pf&=@}Q5ekJI5r%v)nOUTwV56v8gVZ6uqa^P(hSp{nZQ z$IF?gpXbtMJG_cf*j81rNg(i&aqEHh8u9Jy@%9>g!cjjO^{@QjG09R^J|O=~G=WkM1 z=jq}YVsSe8$9V^-6a|0h2mh!4=WGoB|5s3oLz}&EItT9&CL5`OuD+}VDiQ37HUUTZ z^*t_sc(h-CR~y?SV}XmO1f`@rPA}p$(P!~!U004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00AdSL_t(o!|m5WYQjJi#_<7*AqzJiLIT<99uT@(tjlh{dKAlAIu4)2!?Gs8CumF-XkaCv}(%Z8-ov%rZ1 z9M+_*b(jbPE)q451~wvq7Y#^NK7oKSZ7eKqnXr=vR@6f?ulbM!f}{2d#SMEoV5~i* zQ#qim1v+wor3LIW0`yDNl>?r%z_T2%qw%YKQ*xWa`w`KUJ!s#5F`LWdf~M8 Zz5wq-B;BD+E=rO~ut8{6X3~OS)*7BO7kpWC@)^GEW|R03o?!Yw*x^!o`hm~= zJj^ByOWglSJhAJF?@nNrn*Iz+_cRixfzv@}d Yf004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00Fv5L_t(&-tF1FP69y?z;Q@~GZCV-Lczx?5JG$c?Wr{K z1c3Su+M>^)9U%~72nm&Z#Gr)0b7+8yzlDX0;O^nw+!Z@h@8y^5-R|5>fPlX;U_b_B zKnCPfkVTm%-f$!4^f?#fr`Po;hhyO<71Z zWI-oN(3vbKp#-I5K_Ml`k_CnR21+VHX<5*z5>$`{)s>(J>61G9C+AT9>KGAO%ZW1a z;&N@SAw_9!4s_&5Q~cOD(5@q(J?B794Gs)Q3}ng=pM&Op8!hz3Kpj2M zr5LEF2ij&R!gI(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s z6uj-};uw-~@9iyLu0sYQEDzQ^l6@tpdPDh1NmHoKHmD3oxyy2jsp_7iLL-Zi#{zriyR1SU3XLcHBY=F3?>C%*;-_rBFz3=6e~O7zvdYah=I^@GHTj%pv(kQ+WyLuHoym004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00Q<&L_t(|+U?t2NRwd{!14byQYkG%Z_*O&18mTeAXEs! zAPl0LD8IU^H2V}Rq%OMXCJW4{G%@dr>4+|ZpdwNVG6_l~G>b5ok)FObnhtz3YcN)rp|WWP*+7pGzI{2j>*k|_~T zhR0-PC3oZjMp!qm^B6@u!>pACxX3vR*_SyZ4X~3Z7P3otC=HOon1$?2Mx_A~n6{KX zr2s9V|7;UCa5lEvsF$4BKFFY)PLr7jxGnK6^cg2VOys37NQTVobO#kC7UE%_+=1o_ zbqg$`iBqyi%1#a&7E&_pK@Bqz9_mM_$1|@Z#ZyK@5Q9vVzE<(#p^E{&0_ix}L8;}& z8C5{|LjejjBfVVQiucY5gIHT`_~=g{e5D}@x2A`kG9dpw|* zUnX*wkk4-RvR(#Y5;yI<3gVe=;}8|53(FS31dX&X5kh6JqJ}zBqXC%V8W%!Z{GUkW zJST~ZEZ`Fr^oFrX=Hn(ia)6iYVLZ%zKArm%L9kr z66^*vbKJ%YLo-M11{8#K^UjSzUf2y-X-oaje)@#n0Ef-DPQL;aARM40r2-ZUAm)Gg z`ksw~0Pomj6>yUyf&e!-VHMz_&-R{g{%mmZ)+#{K5AXwA;R68@c|?(b<#)3SUoZej zI(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g&? zz`$7V>EaktaqI1EZ?D4vBJCgb?Tm|f{X$*tHd?hhdj@zoYbnB@58{zBH+-lpK;CR<6_-sw#HaInHs&z z#)R=n%m(kY7jEHSED|TMo!FUn`N5Vun`MPwF|hu2W8Wt|t+he)*Rr=u~Hz{H{8Ae3-o z4T#ae!1RN4kFhe4265^b<$aQWf`vIAFkiT(^gG21;=XT;YjusjH|TG^pUs)@P^IQW ziu8vgoAM8+y3b=2&HiG`yv~AWUZ#74b(zLqCM_<%!+)$Aw#svP#qC&VZuNUV>zs>* z+pbG7o_T7{EO92E$zbMuK7(2FxeR8{XET^HpUGgZJiEmCe#SG;{n;!|%x5W>XfO9b xrC#QMZ=LOdz&hClk2T}#OSnPq{ZoFxyyX2~#tovu?||{c;OXk;vd$@?2>`TOzVrY9 literal 0 HcmV?d00001 diff --git a/src/main/res/layout/activity_set_presence.xml b/src/main/res/layout/activity_set_presence.xml new file mode 100644 index 00000000..8195092f --- /dev/null +++ b/src/main/res/layout/activity_set_presence.xml @@ -0,0 +1,72 @@ + + + + + + + + + +