diff --git a/res/layout/activity_publish_profile_picture.xml b/res/layout/activity_publish_profile_picture.xml index ff7f6fa2..9dd7a5dc 100644 --- a/res/layout/activity_publish_profile_picture.xml +++ b/res/layout/activity_publish_profile_picture.xml @@ -75,13 +75,20 @@ android:paddingLeft="8dp" android:paddingRight="8dp" > + + + android:textSize="14sp" /> \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 0b999482..de925254 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -270,5 +270,5 @@ %s has read up to this point Publish avatar Touch avatar to select picture from gallary - Publish avatar for %s. Everyone subscribed to your presence updates will also be able to see this picture. + Please note: Everyone subscribed to your presence updates will be allowed to see this picture. diff --git a/src/eu/siacs/conversations/generator/AbstractGenerator.java b/src/eu/siacs/conversations/generator/AbstractGenerator.java index 05d5799c..0de15092 100644 --- a/src/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/eu/siacs/conversations/generator/AbstractGenerator.java @@ -18,7 +18,8 @@ public abstract class AbstractGenerator { "http://jabber.org/protocol/muc", "jabber:x:conference", "http://jabber.org/protocol/caps", - "http://jabber.org/protocol/disco#info"}; + "http://jabber.org/protocol/disco#info", + "urn:xmpp:avatar:metadata+notify"}; public final String IDENTITY_NAME = "Conversations 0.5"; public final String IDENTITY_TYPE = "phone"; /*public final String[] FEATURES = { "http://jabber.org/protocol/muc","http://jabber.org/protocol/disco#info", "http://jabber.org/protocol/disco#items", "http://jabber.org/protocol/caps" }; diff --git a/src/eu/siacs/conversations/generator/IqGenerator.java b/src/eu/siacs/conversations/generator/IqGenerator.java index 7b3350d4..1a38347b 100644 --- a/src/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/eu/siacs/conversations/generator/IqGenerator.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.IqPacket; public class IqGenerator extends AbstractGenerator { @@ -28,4 +29,34 @@ public class IqGenerator extends AbstractGenerator { } return packet; } + + protected IqPacket publish(String node, Element item) { + IqPacket packet = new IqPacket(IqPacket.TYPE_SET); + Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub"); + Element publish = pubsub.addChild("publish"); + publish.setAttribute("node", node); + publish.addChild(item); + return packet; + } + + public IqPacket publishAvatar(Avatar avatar) { + Element item = new Element("item"); + item.setAttribute("id", avatar.sha1sum); + Element data = item.addChild("data","urn:xmpp:avatar:data"); + data.setContent(avatar.image); + return publish("urn:xmpp:avatar:data", item); + } + + public IqPacket publishAvatarMetadata(Avatar avatar) { + Element item = new Element("item"); + item.setAttribute("id", avatar.sha1sum); + Element metadata = item.addChild("metadata","urn:xmpp:avatar:metadata"); + Element info = metadata.addChild("info"); + info.setAttribute("bytes",avatar.size); + info.setAttribute("id",avatar.sha1sum); + info.setAttribute("height",avatar.height); + info.setAttribute("width",avatar.height); + info.setAttribute("type", avatar.type); + return publish("urn:xmpp:avatar:metadata",item); + } } diff --git a/src/eu/siacs/conversations/parser/MessageParser.java b/src/eu/siacs/conversations/parser/MessageParser.java index 5c04b09c..0a812669 100644 --- a/src/eu/siacs/conversations/parser/MessageParser.java +++ b/src/eu/siacs/conversations/parser/MessageParser.java @@ -1,6 +1,7 @@ package eu.siacs.conversations.parser; import android.os.SystemClock; +import android.util.Log; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; import eu.siacs.conversations.entities.Account; @@ -213,6 +214,10 @@ public class MessageParser extends AbstractParser implements } private void parseNormal(Element packet, Account account) { + if (packet.hasChild("event","http://jabber.org/protocol/pubsub#event")) { + Element event = packet.findChild("event","http://jabber.org/protocol/pubsub#event"); + parseEvent(event,packet.getAttribute("from"),account); + } if (packet.hasChild("displayed", "urn:xmpp:chat-markers:0")) { String id = packet .findChild("displayed", "urn:xmpp:chat-markers:0") @@ -254,6 +259,16 @@ public class MessageParser extends AbstractParser implements } } + private void parseEvent(Element event, String from, Account account) { + Element items = event.findChild("items"); + String node = items.getAttribute("node"); + if (node!=null) { + Log.d("xmppService",account.getJid()+": "+node+" from "+from); + } else { + Log.d("xmppService",event.toString()); + } + } + private String getPgpBody(Element message) { Element child = message.findChild("x", "jabber:x:encrypted"); if (child == null) { @@ -324,6 +339,9 @@ public class MessageParser extends AbstractParser implements } else if (packet.getType() == MessagePacket.TYPE_NORMAL) { this.parseNormal(packet, account); return; + } else if (packet.getType() == MessagePacket.TYPE_HEADLINE) { + this.parseHeadline(packet, account); + return; } if ((message == null) || (message.getBody() == null)) { return; @@ -346,4 +364,11 @@ public class MessageParser extends AbstractParser implements } mXmppConnectionService.notifyUi(conversation, notify); } + + private void parseHeadline(MessagePacket packet, Account account) { + if (packet.hasChild("event","http://jabber.org/protocol/pubsub#event")) { + Element event = packet.findChild("event","http://jabber.org/protocol/pubsub#event"); + parseEvent(event,packet.getFrom(),account); + } + } } diff --git a/src/eu/siacs/conversations/persistance/FileBackend.java b/src/eu/siacs/conversations/persistance/FileBackend.java index 8895a2c8..9a94329a 100644 --- a/src/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/eu/siacs/conversations/persistance/FileBackend.java @@ -261,7 +261,7 @@ public class FileBackend { mDigestOutputStream.write(avatar.getImageAsBytes()); mDigestOutputStream.flush(); mDigestOutputStream.close(); - Log.d("xmppService","sha1sum after write: "+CryptoHelper.bytesToHex(digest.digest())); + avatar.size = file.length(); } catch (FileNotFoundException e) { } catch (IOException e) { diff --git a/src/eu/siacs/conversations/services/Defaults.java b/src/eu/siacs/conversations/services/Defaults.java new file mode 100644 index 00000000..c942dd48 --- /dev/null +++ b/src/eu/siacs/conversations/services/Defaults.java @@ -0,0 +1,11 @@ +package eu.siacs.conversations.services; + +import android.graphics.Bitmap; + +public final class Defaults { + public static final int AVATAR_SIZE = 192; + public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP; + private Defaults() { + + } +} diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java index 4ba0954f..c750ed53 100644 --- a/src/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/eu/siacs/conversations/services/XmppConnectionService.java @@ -20,6 +20,7 @@ import de.duenndns.ssl.MemorizingTrustManager; import net.java.otr4j.OtrException; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; +import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Bookmark; @@ -1186,13 +1187,46 @@ public class XmppConnectionService extends Service { } - public void pushAvatar(Account account, Uri image) { - Avatar avatar = getFileBackend().getPepAvatar(image, 192, Bitmap.CompressFormat.WEBP); + public void publishAvatar(Account account, Uri image, final UiCallback callback) { + final Bitmap.CompressFormat format = Defaults.AVATAR_FORMAT; + final int size = Defaults.AVATAR_SIZE; + final Avatar avatar = getFileBackend().getPepAvatar(image, size, format); if (avatar!=null) { - Log.d(LOGTAG,avatar.sha1sum); - Log.d(LOGTAG,avatar.image); - avatar.type = "image/webp"; + avatar.height = size; + avatar.width = size; + if (format.equals(Bitmap.CompressFormat.WEBP)) { + avatar.type = "image/webp"; + } else if (format.equals(Bitmap.CompressFormat.JPEG)) { + avatar.type = "image/jpeg"; + } else if (format.equals(Bitmap.CompressFormat.PNG)) { + avatar.type = "image/png"; + } getFileBackend().save(avatar); + 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) { + 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) { + callback.success(avatar); + } else { + callback.error(R.string.error, avatar); + } + } + }); + } else { + callback.error(R.string.error, avatar); + } + } + }); + } else { + callback.error(R.string.error, null); } } diff --git a/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index 776ed774..a2cc84a6 100644 --- a/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -1,10 +1,11 @@ package eu.siacs.conversations.ui; +import android.app.PendingIntent; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; -import android.util.Log; +import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; @@ -13,47 +14,69 @@ import android.widget.TextView; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.utils.PhoneHelper; +import eu.siacs.conversations.xmpp.pep.Avatar; public class PublishProfilePictureActivity extends XmppActivity { - + private static final int REQUEST_CHOOSE_FILE = 0xac23; - + private ImageView avatar; - private TextView explanation; + private TextView accountTextView; private Button cancelButton; private Button publishButton; - + private Uri avatarUri; - + private Account account; - + + private UiCallback avatarPublication = new UiCallback() { + + @Override + public void success(Avatar object) { + finish(); + } + + @Override + public void error(int errorCode, Avatar object) { + // TODO Auto-generated method stub + + } + + @Override + public void userInputRequried(PendingIntent pi, Avatar object) { + // TODO Auto-generated method stub + + } + }; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_publish_profile_picture); this.avatar = (ImageView) findViewById(R.id.account_image); - this.explanation = (TextView) findViewById(R.id.explanation); this.cancelButton = (Button) findViewById(R.id.cancel_button); this.publishButton = (Button) findViewById(R.id.publish_button); + this.accountTextView = (TextView) findViewById(R.id.account); this.publishButton.setOnClickListener(new OnClickListener() { - + @Override public void onClick(View v) { - if (avatarUri!=null) { - xmppConnectionService.pushAvatar(account, avatarUri); - finish(); + if (avatarUri != null) { + disablePublishButton(); + xmppConnectionService.publishAvatar(account, avatarUri, + avatarPublication); } } }); this.cancelButton.setOnClickListener(new OnClickListener() { - + @Override public void onClick(View v) { finish(); } }); this.avatar.setOnClickListener(new OnClickListener() { - + @Override public void onClick(View v) { Intent attachFileIntent = new Intent(); @@ -65,45 +88,61 @@ public class PublishProfilePictureActivity extends XmppActivity { } }); } - + @Override protected void onActivityResult(int requestCode, int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { if (requestCode == REQUEST_CHOOSE_FILE) { - Log.d("xmppService","bla"); this.avatarUri = data.getData(); } } } + + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + super.onOptionsItemSelected(menuItem); + switch (menuItem.getItemId()) { + case android.R.id.home: + finish(); + break; + } + return true; + } @Override protected void onBackendConnected() { - if (getIntent()!=null) { + if (getIntent() != null) { String jid = getIntent().getStringExtra("account"); - if (jid!=null) { + if (jid != null) { this.account = xmppConnectionService.findAccountByJid(jid); if (this.avatarUri == null) { - avatarUri = PhoneHelper.getSefliUri(getApplicationContext()); + avatarUri = PhoneHelper + .getSefliUri(getApplicationContext()); } loadImageIntoPreview(avatarUri); - String explainText = getString(R.string.publish_avatar_explanation,account.getJid()); - this.explanation.setText(explainText); + this.accountTextView.setText(this.account.getJid()); } } - + } - + protected void loadImageIntoPreview(Uri uri) { - Bitmap bm = xmppConnectionService.getFileBackend().cropCenterSquare(uri, 384); + Bitmap bm = xmppConnectionService.getFileBackend().cropCenterSquare( + uri, 384); this.avatar.setImageBitmap(bm); enablePublishButton(); } - + protected void enablePublishButton() { this.publishButton.setEnabled(true); this.publishButton.setTextColor(getPrimaryTextColor()); } + + protected void disablePublishButton() { + this.publishButton.setEnabled(false); + this.publishButton.setTextColor(getSecondaryTextColor()); + } } diff --git a/src/eu/siacs/conversations/xml/Element.java b/src/eu/siacs/conversations/xml/Element.java index f8e070f7..3d22ba6a 100644 --- a/src/eu/siacs/conversations/xml/Element.java +++ b/src/eu/siacs/conversations/xml/Element.java @@ -144,4 +144,12 @@ public class Element { public void clearChildren() { this.children.clear(); } + + public void setAttribute(String name, long value) { + this.setAttribute(name, ""+value); + } + + public void setAttribute(String name, int value) { + this.setAttribute(name, ""+value); + } } diff --git a/src/eu/siacs/conversations/xmpp/pep/Avatar.java b/src/eu/siacs/conversations/xmpp/pep/Avatar.java index 196c876c..b000c055 100644 --- a/src/eu/siacs/conversations/xmpp/pep/Avatar.java +++ b/src/eu/siacs/conversations/xmpp/pep/Avatar.java @@ -6,6 +6,9 @@ public class Avatar { public String type; public String sha1sum; public String image; + public int height; + public int width; + public long size; public byte[] getImageAsBytes() { return Base64.decode(image, Base64.DEFAULT); } diff --git a/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java b/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java index 64a9edc3..433b08c9 100644 --- a/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java +++ b/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java @@ -8,6 +8,7 @@ public class MessagePacket extends AbstractStanza { public static final int TYPE_NORMAL = 2; public static final int TYPE_GROUPCHAT = 3; public static final int TYPE_ERROR = 4; + public static final int TYPE_HEADLINE = 5; public MessagePacket() { super("message"); @@ -59,6 +60,8 @@ public class MessagePacket extends AbstractStanza { return TYPE_GROUPCHAT; } else if (type.equals("error")) { return TYPE_ERROR; + } else if (type.equals("headline")) { + return TYPE_HEADLINE; } else { return TYPE_UNKNOWN; }