diff options
Diffstat (limited to '')
10 files changed, 197 insertions, 51 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"); } } |