aboutsummaryrefslogtreecommitdiffstats
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/eu/siacs/conversations/Config.java1
-rw-r--r--src/main/java/eu/siacs/conversations/entities/MucOptions.java3
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/FileBackend.java64
-rw-r--r--src/main/java/eu/siacs/conversations/services/AvatarService.java23
-rw-r--r--src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java2
-rw-r--r--src/main/java/eu/siacs/conversations/services/NotificationService.java5
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java113
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java17
-rw-r--r--src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java4
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java16
-rw-r--r--src/main/res/drawable-hdpi/ic_action_share.pngbin0 -> 513 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_share_white_24dp.pngbin0 -> 506 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_action_share.pngbin0 -> 383 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_share_white_24dp.pngbin0 -> 361 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_action_share.pngbin0 -> 666 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_share_white_24dp.pngbin0 -> 625 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_action_share.pngbin0 -> 969 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_share_white_24dp.pngbin0 -> 857 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_share_white_24dp.pngbin0 -> 1115 bytes
-rw-r--r--src/main/res/menu/muc_details.xml45
-rw-r--r--src/main/res/values-v21/themes.xml1
-rw-r--r--src/main/res/values/attrs.xml1
-rw-r--r--src/main/res/values/strings.xml2
-rw-r--r--src/main/res/values/themes.xml1
24 files changed, 233 insertions, 65 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
index 763beb252..731b04cf5 100644
--- a/src/main/java/eu/siacs/conversations/Config.java
+++ b/src/main/java/eu/siacs/conversations/Config.java
@@ -49,6 +49,7 @@ public final class Config {
public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false;
public static final boolean SHOW_CONNECTED_ACCOUNTS = false; //show number of connected accounts in foreground notification
+ public static final boolean SHOW_DISABLE_FOREGROUND = true; //if set to true the foreground notification has a button to disable it
public static final boolean ALWAYS_NOTIFY_BY_DEFAULT = false;
diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
index ac627aaae..1309956eb 100644
--- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java
+++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
@@ -469,6 +469,9 @@ public class MucOptions {
}
public Jid getTrueCounterpart(String name) {
+ if (name.equals(getSelf().getName())) {
+ return account.getJid().toBareJid();
+ }
User user = findUser(name);
return user == null ? null : user.getJid();
}
diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
index 0861a3a86..306092141 100644
--- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
@@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -402,6 +403,43 @@ public class FileBackend {
}
}
+ public Avatar getStoredPepAvatar(String hash) {
+ if (hash == null) {
+ return null;
+ }
+ Avatar avatar = new Avatar();
+ File file = new File(getAvatarPath(hash));
+ FileInputStream is = null;
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+ is = new FileInputStream(file);
+ ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
+ Base64OutputStream mBase64OutputStream = new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ DigestOutputStream os = new DigestOutputStream(mBase64OutputStream, digest);
+ byte[] buffer = new byte[4096];
+ int length;
+ while ((length = is.read(buffer)) > 0) {
+ os.write(buffer, 0, length);
+ }
+ os.flush();
+ os.close();
+ avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest());
+ avatar.image = new String(mByteArrayOutputStream.toByteArray());
+ avatar.height = options.outHeight;
+ avatar.width = options.outWidth;
+ return avatar;
+ } catch (IOException e) {
+ return null;
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ } finally {
+ close(is);
+ }
+ }
+
public boolean isAvatarCached(Avatar avatar) {
File file = new File(getAvatarPath(avatar.getFilename()));
return file.exists();
@@ -655,13 +693,29 @@ public class FileBackend {
}
- public static boolean weOwnFile(Uri uri) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ public static boolean weOwnFile(Context context, Uri uri) {
+ if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return false;
+ } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return fileIsInFilesDir(context, uri);
} else {
- return uri != null
- && ContentResolver.SCHEME_FILE.equals(uri.getScheme())
- && weOwnFileLollipop(uri);
+ return weOwnFileLollipop(uri);
+ }
+ }
+
+
+ /**
+ * This is more than hacky but probably way better than doing nothing
+ * Further 'optimizations' might contain to get the parents of CacheDir and NoBackupDir
+ * and check against those as well
+ */
+ private static boolean fileIsInFilesDir(Context context, Uri uri) {
+ try {
+ final String haystack = context.getFilesDir().getParentFile().getCanonicalPath();
+ final String needle = new File(uri.getPath()).getCanonicalPath();
+ return needle.startsWith(haystack);
+ } catch (IOException e) {
+ return false;
}
}
diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java
index 8f9b26ab4..4cf9ade14 100644
--- a/src/main/java/eu/siacs/conversations/services/AvatarService.java
+++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java
@@ -6,11 +6,13 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.net.Uri;
+import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Bookmark;
import eu.siacs.conversations.entities.Contact;
@@ -19,8 +21,10 @@ import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
+import eu.siacs.conversations.xmpp.XmppConnection;
-public class AvatarService {
+public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
private static final int FG_COLOR = 0xFFFAFAFA;
private static final int TRANSPARENT = 0x00000000;
@@ -227,8 +231,7 @@ public class AvatarService {
if (avatar != null || cachedOnly) {
return avatar;
}
- avatar = mXmppConnectionService.getFileBackend().getAvatar(
- account.getAvatar(), size);
+ avatar = mXmppConnectionService.getFileBackend().getAvatar(account.getAvatar(), size);
if (avatar == null) {
avatar = get(account.getJid().toBareJid().toString(), size,false);
}
@@ -397,10 +400,20 @@ public class AvatarService {
return false;
}
- private boolean drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop,
- int dstright, int dstbottom) {
+ private boolean drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop, int dstright, int dstbottom) {
Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom);
canvas.drawBitmap(bm, null, dst, null);
return true;
}
+
+ @Override
+ public void onAdvancedStreamFeaturesAvailable(Account account) {
+ XmppConnection.Features features = account.getXmppConnection().getFeatures();
+ if (features.pep() && !features.pepPersistent()) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": has pep but is not persistent");
+ if (account.getAvatar() != null) {
+ mXmppConnectionService.republishAvatarIfNeeded(account);
+ }
+ }
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java
index c2a45bf28..0a8ac98ca 100644
--- a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java
+++ b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java
@@ -52,7 +52,7 @@ public class ContactChooserTargetService extends ChooserTargetService implements
final Conversation conversation = conversations.get(i);
final String name = conversation.getName();
final Icon icon = Icon.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation, pixel));
- final float score = (1.0f / MAX_TARGETS) * i;
+ final float score = 1 - (1.0f / MAX_TARGETS) * i;
final Bundle extras = new Bundle();
extras.putString("uuid", conversation.getUuid());
chooserTargets.add(new ChooserTarget(name, icon, score, componentName, extras));
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index 7eef10f8f..83e59faa0 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -544,6 +544,11 @@ public class NotificationService {
cancelIcon = R.drawable.ic_action_cancel;
}
mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp);
+ if (Config.SHOW_DISABLE_FOREGROUND) {
+ mBuilder.addAction(cancelIcon,
+ mXmppConnectionService.getString(R.string.disable_foreground_service),
+ createDisableForeground());
+ }
return mBuilder.build();
}
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 7849f8a39..b77bc4c1d 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -403,7 +403,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void attachFileToConversation(final Conversation conversation,
final Uri uri,
final UiCallback<Message> callback) {
- if (FileBackend.weOwnFile(uri)) {
+ if (FileBackend.weOwnFile(this, uri)) {
Log.d(Config.LOGTAG,"trying to attach file that belonged to us");
callback.error(R.string.security_error_invalid_file_access, null);
return;
@@ -446,7 +446,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback<Message> callback) {
- if (FileBackend.weOwnFile(uri)) {
+ if (FileBackend.weOwnFile(this, uri)) {
Log.d(Config.LOGTAG,"trying to attach file that belonged to us");
callback.error(R.string.security_error_invalid_file_access, null);
return;
@@ -801,7 +801,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
disconnect(account, false);
}
}).start();
- cancelWakeUpCall(account.getUuid().hashCode());
}
}
Log.d(Config.LOGTAG, "good bye");
@@ -840,6 +839,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
connection.setOnBindListener(this.mOnBindListener);
connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService);
+ connection.addOnAdvancedStreamFeaturesAvailableListener(this.mAvatarService);
AxolotlService axolotlService = account.getAxolotlService();
if (axolotlService != null) {
connection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
@@ -2338,9 +2338,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
- public void publishAvatar(final Account account,
- final Uri image,
- final UiCallback<Avatar> callback) {
+ public void publishAvatar(Account account, Uri image, UiCallback<Avatar> callback) {
final Bitmap.CompressFormat format = Config.AVATAR_FORMAT;
final int size = Config.AVATAR_SIZE;
final Avatar avatar = getFileBackend().getPepAvatar(image, size, format);
@@ -2358,40 +2356,96 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
callback.error(R.string.error_saving_avatar, avatar);
return;
}
- final IqPacket packet = this.mIqGenerator.publishAvatar(avatar);
- this.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ publishAvatar(account, avatar, callback);
+ } else {
+ callback.error(R.string.error_publish_avatar_converting, null);
+ }
+ }
- @Override
- public void onIqPacketReceived(Account account, IqPacket result) {
- if (result.getType() == IqPacket.TYPE.RESULT) {
- final IqPacket packet = XmppConnectionService.this.mIqGenerator
- .publishAvatarMetadata(avatar);
- sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket result) {
- if (result.getType() == IqPacket.TYPE.RESULT) {
- if (account.setAvatar(avatar.getFilename())) {
- getAvatarService().clear(account);
- databaseBackend.updateAccount(account);
- }
+ public void publishAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+ final IqPacket packet = this.mIqGenerator.publishAvatar(avatar);
+ this.sendIqPacket(account, packet, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket result) {
+ if (result.getType() == IqPacket.TYPE.RESULT) {
+ final IqPacket packet = XmppConnectionService.this.mIqGenerator
+ .publishAvatarMetadata(avatar);
+ sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket result) {
+ if (result.getType() == IqPacket.TYPE.RESULT) {
+ if (account.setAvatar(avatar.getFilename())) {
+ getAvatarService().clear(account);
+ databaseBackend.updateAccount(account);
+ }
+ if (callback != null) {
callback.success(avatar);
} else {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": published avatar");
+ }
+ } else {
+ if (callback != null) {
callback.error(
R.string.error_publish_avatar_server_reject,
avatar);
}
}
- });
- } else {
+ }
+ });
+ } else {
+ if (callback != null) {
callback.error(
R.string.error_publish_avatar_server_reject,
avatar);
}
}
- });
- } else {
- callback.error(R.string.error_publish_avatar_converting, null);
+ }
+ });
+ }
+
+ public void republishAvatarIfNeeded(Account account) {
+ if (account.getAxolotlService().isPepBroken()) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping republication of avatar because pep is broken");
+ return;
}
+ IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null);
+ this.sendIqPacket(account, packet, new OnIqPacketReceived() {
+
+ private Avatar parseAvatar(IqPacket packet) {
+ Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub");
+ if (pubsub != null) {
+ Element items = pubsub.findChild("items");
+ if (items != null) {
+ return Avatar.parseMetadata(items);
+ }
+ }
+ return null;
+ }
+
+ private boolean errorIsItemNotFound(IqPacket packet) {
+ Element error = packet.findChild("error");
+ return packet.getType() == IqPacket.TYPE.ERROR
+ && error != null
+ && error.hasChild("item-not-found");
+ }
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT || errorIsItemNotFound(packet)) {
+ Avatar serverAvatar = parseAvatar(packet);
+ if (serverAvatar == null && account.getAvatar() != null) {
+ Avatar avatar = fileBackend.getStoredPepAvatar(account.getAvatar());
+ if (avatar != null) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": avatar on server was null. republishing");
+ publishAvatar(account, fileBackend.getStoredPepAvatar(account.getAvatar()), null);
+ } else {
+ Log.e(Config.LOGTAG, account.getJid().toBareJid()+": error rereading avatar");
+ }
+ }
+ }
+ }
+ });
}
public void fetchAvatar(Account account, Avatar avatar) {
@@ -2401,9 +2455,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
final String KEY = generateFetchKey(account, avatar);
synchronized (this.mInProgressAvatarFetches) {
- if (this.mInProgressAvatarFetches.contains(KEY)) {
- return;
- } else {
+ if (!this.mInProgressAvatarFetches.contains(KEY)) {
switch (avatar.origin) {
case PEP:
this.mInProgressAvatarFetches.add(KEY);
@@ -2526,8 +2578,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.RESULT) {
- Element pubsub = packet.findChild("pubsub",
- "http://jabber.org/protocol/pubsub");
+ Element pubsub = packet.findChild("pubsub","http://jabber.org/protocol/pubsub");
if (pubsub != null) {
Element items = pubsub.findChild("items");
if (items != null) {
diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
index adbb09537..a27570704 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
@@ -3,8 +3,10 @@ package eu.siacs.conversations.ui;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.os.Build;
import android.os.Bundle;
@@ -281,6 +283,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
quickEdit(mConversation.getName(),this.onSubjectEdited);
}
break;
+ case R.id.action_share:
+ share();
+ break;
case R.id.action_save_as_bookmark:
saveAsBookmark();
break;
@@ -308,6 +313,18 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
}
+ private void share() {
+ Intent shareIntent = new Intent();
+ shareIntent.setAction(Intent.ACTION_SEND);
+ shareIntent.putExtra(Intent.EXTRA_TEXT, getShareableUri());
+ shareIntent.setType("text/plain");
+ try {
+ startActivity(Intent.createChooser(shareIntent, getText(R.string.share_uri_with)));
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
+ }
+ }
+
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark);
diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
index 2b25ad321..19c1c1f68 100644
--- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
@@ -192,7 +192,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
Uri source = data.getData();
switch (requestCode) {
case REQUEST_CHOOSE_FILE_AND_CROP:
- if (FileBackend.weOwnFile(source)) {
+ if (FileBackend.weOwnFile(this, source)) {
Toast.makeText(this,R.string.security_error_invalid_file_access,Toast.LENGTH_SHORT).show();
return;
}
@@ -205,7 +205,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
Crop.of(source, destination).asSquare().withMaxSize(size, size).start(this);
break;
case REQUEST_CHOOSE_FILE:
- if (FileBackend.weOwnFile(source)) {
+ if (FileBackend.weOwnFile(this, source)) {
Toast.makeText(this,R.string.security_error_invalid_file_access,Toast.LENGTH_SHORT).show();
return;
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index 7bc08e9ca..4dc5492e3 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -1530,13 +1530,15 @@ public class XmppConnection implements Runnable {
public boolean pep() {
synchronized (XmppConnection.this.disco) {
- ServiceDiscoveryResult info = disco.get(account.getServer());
- if (info != null && info.hasIdentity("pubsub", "pep")) {
- return true;
- } else {
- info = disco.get(account.getJid().toBareJid());
- return info != null && info.hasIdentity("pubsub", "pep");
- }
+ ServiceDiscoveryResult info = disco.get(account.getJid().toBareJid());
+ return info != null && info.hasIdentity("pubsub", "pep");
+ }
+ }
+
+ public boolean pepPersistent() {
+ synchronized (XmppConnection.this.disco) {
+ ServiceDiscoveryResult info = disco.get(account.getJid().toBareJid());
+ return info != null && info.getFeatures().contains("http://jabber.org/protocol/pubsub#persistent-items");
}
}
diff --git a/src/main/res/drawable-hdpi/ic_action_share.png b/src/main/res/drawable-hdpi/ic_action_share.png
new file mode 100644
index 000000000..a36464b8b
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_action_share.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_share_white_24dp.png b/src/main/res/drawable-hdpi/ic_share_white_24dp.png
new file mode 100644
index 000000000..93b3c219c
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_action_share.png b/src/main/res/drawable-mdpi/ic_action_share.png
new file mode 100644
index 000000000..7362f0d7b
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_action_share.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_share_white_24dp.png b/src/main/res/drawable-mdpi/ic_share_white_24dp.png
new file mode 100644
index 000000000..4d0197223
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_action_share.png b/src/main/res/drawable-xhdpi/ic_action_share.png
new file mode 100644
index 000000000..40771e480
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_action_share.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_share_white_24dp.png b/src/main/res/drawable-xhdpi/ic_share_white_24dp.png
new file mode 100644
index 000000000..dd536bca2
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_action_share.png b/src/main/res/drawable-xxhdpi/ic_action_share.png
new file mode 100644
index 000000000..22ed428fc
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_action_share.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png
new file mode 100644
index 000000000..9963c6a05
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png
new file mode 100644
index 000000000..bb521c141
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/src/main/res/menu/muc_details.xml b/src/main/res/menu/muc_details.xml
index 1ae217709..ad79f5778 100644
--- a/src/main/res/menu/muc_details.xml
+++ b/src/main/res/menu/muc_details.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_edit_subject"
@@ -8,25 +8,42 @@
android:showAsAction="always"
android:title="@string/action_edit_subject"/>
<item
+ android:id="@+id/action_share"
+ android:icon="?attr/icon_share"
+ android:showAsAction="always"
+ android:orderInCategory="15"
+ android:title="@string/share_uri_with"/>
+
+ <item
android:id="@+id/action_show_qr_code"
- android:title="@string/show_qr_code"
- android:showAsAction="never" />
+ android:showAsAction="never"
+ android:title="@string/show_qr_code"/>
<item
android:id="@+id/action_save_as_bookmark"
- android:title="@string/save_as_bookmark"
android:orderInCategory="80"
- android:showAsAction="never" />
+ android:showAsAction="never"
+ android:title="@string/save_as_bookmark"/>
<item
android:id="@+id/action_delete_bookmark"
- android:title="@string/delete_bookmark"
android:orderInCategory="80"
- android:showAsAction="never" />
- <item
- android:id="@+id/action_advanced_mode"
- android:title="@string/advanced_mode"
- android:checkable="true"
- android:checked="false"
- android:orderInCategory="85"
- android:showAsAction="never" />
+ android:showAsAction="never"
+ android:title="@string/delete_bookmark"/>
+ <item
+ android:id="@+id/action_advanced_mode"
+ android:checkable="true"
+ android:checked="false"
+ android:orderInCategory="85"
+ android:showAsAction="never"
+ android:title="@string/advanced_mode"/>
+ <item
+ android:id="@+id/action_accounts"
+ android:orderInCategory="90"
+ android:showAsAction="never"
+ android:title="@string/action_accounts"/>
+ <item
+ android:id="@+id/action_settings"
+ android:orderInCategory="100"
+ android:showAsAction="never"
+ android:title="@string/action_settings"/>
</menu> \ No newline at end of file
diff --git a/src/main/res/values-v21/themes.xml b/src/main/res/values-v21/themes.xml
index 91d43e777..8556c99ae 100644
--- a/src/main/res/values-v21/themes.xml
+++ b/src/main/res/values-v21/themes.xml
@@ -32,6 +32,7 @@
<item name="attr/icon_secure">@drawable/ic_lock_open_white_24dp</item>
<item name="attr/icon_settings">@drawable/ic_settings_grey600_24dp</item>
<item name="attr/icon_import_export">@drawable/ic_import_export_white_24dp</item>
+ <item name="attr/icon_share">@drawable/ic_share_white_24dp</item>
</style>
diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml
index d471e54ac..901ea7547 100644
--- a/src/main/res/values/attrs.xml
+++ b/src/main/res/values/attrs.xml
@@ -24,6 +24,7 @@
<attr name="icon_search" format="reference"/>
<attr name="icon_secure" format="reference"/>
<attr name="icon_settings" format="reference"/>
+ <attr name="icon_share" format="reference"/>
<attr name="icon_import_export" format="reference"/>
</resources> \ No newline at end of file
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index a11283bd3..b12f8b59d 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -640,4 +640,6 @@
<string name="pref_plugin_voice_recorder">Voice Recorder</string>
<string name="pref_plugin_voice_recorder_summary">Send and receive voice messages</string>
<string name="security_error_invalid_file_access">Security error: Invalid file access</string>
+ <string name="no_application_to_share_uri">No application found to share URI</string>
+ <string name="share_uri_with">Share URI with…</string>
</resources>
diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml
index 97342311b..b96c9b15e 100644
--- a/src/main/res/values/themes.xml
+++ b/src/main/res/values/themes.xml
@@ -31,6 +31,7 @@
<item name="attr/icon_secure">@drawable/ic_action_secure</item>
<item name="attr/icon_settings">@drawable/ic_action_settings</item>
<item name="attr/icon_import_export">@drawable/ic_stat_communication_import_export</item>
+ <item name="attr/icon_share">@drawable/ic_action_share</item>
</style>
<style name="ConversationsTheme.LargerText" parent="ConversationsTheme">