diff options
Diffstat (limited to 'src/eu/siacs/conversations/services')
5 files changed, 723 insertions, 271 deletions
diff --git a/src/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/eu/siacs/conversations/services/AbstractConnectionManager.java new file mode 100644 index 00000000..676a09c9 --- /dev/null +++ b/src/eu/siacs/conversations/services/AbstractConnectionManager.java @@ -0,0 +1,23 @@ +package eu.siacs.conversations.services; + +public class AbstractConnectionManager { + protected XmppConnectionService mXmppConnectionService; + + public AbstractConnectionManager(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public XmppConnectionService getXmppConnectionService() { + return this.mXmppConnectionService; + } + + public long getAutoAcceptFileSize() { + String config = this.mXmppConnectionService.getPreferences().getString( + "auto_accept_file_size", "524288"); + try { + return Long.parseLong(config); + } catch (NumberFormatException e) { + return 524288; + } + } +} diff --git a/src/eu/siacs/conversations/services/AvatarService.java b/src/eu/siacs/conversations/services/AvatarService.java new file mode 100644 index 00000000..bd27b555 --- /dev/null +++ b/src/eu/siacs/conversations/services/AvatarService.java @@ -0,0 +1,292 @@ +package eu.siacs.conversations.services; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Bookmark; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.ListItem; +import eu.siacs.conversations.entities.MucOptions; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.net.Uri; + +public class AvatarService { + + private static final int FG_COLOR = 0xFFFAFAFA; + private static final int TRANSPARENT = 0x00000000; + + private static final String PREFIX_CONTACT = "contact"; + private static final String PREFIX_CONVERSATION = "conversation"; + private static final String PREFIX_ACCOUNT = "account"; + private static final String PREFIX_GENERIC = "generic"; + + private ArrayList<Integer> sizes = new ArrayList<Integer>(); + + protected XmppConnectionService mXmppConnectionService = null; + + public AvatarService(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public Bitmap get(Contact contact, int size) { + final String KEY = key(contact, size); + Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null) { + return avatar; + } + avatar = mXmppConnectionService.getFileBackend().getAvatar( + contact.getAvatar(), size); + if (avatar == null) { + if (contact.getProfilePhoto() != null) { + avatar = mXmppConnectionService.getFileBackend() + .cropCenterSquare(Uri.parse(contact.getProfilePhoto()), + size); + if (avatar == null) { + avatar = get(contact.getDisplayName(), size); + } + } else { + avatar = get(contact.getDisplayName(), size); + } + } + this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public void clear(Contact contact) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove( + key(contact, size)); + } + } + + private String key(Contact contact, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_CONTACT + "_" + contact.getAccount().getJid() + "_" + + contact.getJid() + "_" + String.valueOf(size); + } + + public Bitmap get(ListItem item, int size) { + if (item instanceof Contact) { + return get((Contact) item, size); + } else if (item instanceof Bookmark) { + Bookmark bookmark = (Bookmark) item; + if (bookmark.getConversation() != null) { + return get(bookmark.getConversation(), size); + } else { + return get(bookmark.getDisplayName(), size); + } + } else { + return get(item.getDisplayName(), size); + } + } + + public Bitmap get(Conversation conversation, int size) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + return get(conversation.getContact(), size); + } else { + return get(conversation.getMucOptions(), size); + } + } + + public void clear(Conversation conversation) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + clear(conversation.getContact()); + } else { + clear(conversation.getMucOptions()); + } + } + + public Bitmap get(MucOptions mucOptions, int size) { + final String KEY = key(mucOptions, size); + Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (bitmap != null) { + return bitmap; + } + List<MucOptions.User> users = mucOptions.getUsers(); + int count = users.size(); + bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + bitmap.eraseColor(TRANSPARENT); + + if (count == 0) { + String name = mucOptions.getConversation().getName(); + String letter = name.substring(0, 1); + int color = this.getColorForName(name); + drawTile(canvas, letter, color, 0, 0, size, size); + } else if (count == 1) { + drawTile(canvas, users.get(0), 0, 0, size, size); + } else if (count == 2) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size); + } else if (count == 3) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, users.get(2), size / 2 + 1, size / 2 + 1, size, + size); + } else if (count == 4) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); + drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); + drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, users.get(3), size / 2 + 1, size / 2 + 1, size, + size); + } else { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); + drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); + drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, "\u2026", 0xFF202020, size / 2 + 1, size / 2 + 1, + size, size); + } + this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap); + return bitmap; + } + + public void clear(MucOptions options) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove( + key(options, size)); + } + } + + private String key(MucOptions options, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid() + + "_" + String.valueOf(size); + } + + public Bitmap get(Account account, int size) { + final String KEY = key(account, size); + Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null) { + return avatar; + } + avatar = mXmppConnectionService.getFileBackend().getAvatar( + account.getAvatar(), size); + if (avatar == null) { + avatar = get(account.getJid(), size); + } + mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public void clear(Account account) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove( + key(account, size)); + } + } + + private String key(Account account, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_ACCOUNT + "_" + account.getUuid() + "_" + + String.valueOf(size); + } + + public Bitmap get(String name, int size) { + final String KEY = key(name, size); + Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY); + if (bitmap != null) { + return bitmap; + } + bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + String letter = name.substring(0, 1); + int color = this.getColorForName(name); + drawTile(canvas, letter, color, 0, 0, size, size); + mXmppConnectionService.getBitmapCache().put(KEY, bitmap); + return bitmap; + } + + private String key(String name, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_GENERIC + "_" + name + "_" + String.valueOf(size); + } + + private void drawTile(Canvas canvas, String letter, int tileColor, + int left, int top, int right, int bottom) { + letter = letter.toUpperCase(Locale.getDefault()); + Paint tilePaint = new Paint(), textPaint = new Paint(); + tilePaint.setColor(tileColor); + textPaint.setFlags(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(FG_COLOR); + textPaint.setTypeface(Typeface.create("sans-serif-light", + Typeface.NORMAL)); + textPaint.setTextSize((float) ((right - left) * 0.8)); + Rect rect = new Rect(); + + canvas.drawRect(new Rect(left, top, right, bottom), tilePaint); + textPaint.getTextBounds(letter, 0, 1, rect); + float width = textPaint.measureText(letter); + canvas.drawText(letter, (right + left) / 2 - width / 2, (top + bottom) + / 2 + rect.height() / 2, textPaint); + } + + private void drawTile(Canvas canvas, MucOptions.User user, int left, + int top, int right, int bottom) { + Contact contact = user.getContact(); + if (contact != null) { + Uri uri = null; + if (contact.getAvatar() != null) { + uri = mXmppConnectionService.getFileBackend().getAvatarUri( + contact.getAvatar()); + } else if (contact.getProfilePhoto() != null) { + uri = Uri.parse(contact.getProfilePhoto()); + } + if (uri != null) { + Bitmap bitmap = mXmppConnectionService.getFileBackend() + .cropCenter(uri, bottom - top, right - left); + if (bitmap != null) { + drawTile(canvas, bitmap, left, top, right, bottom); + } else { + String letter = user.getName().substring(0, 1); + int color = this.getColorForName(user.getName()); + drawTile(canvas, letter, color, left, top, right, bottom); + } + } else { + String letter = user.getName().substring(0, 1); + int color = this.getColorForName(user.getName()); + drawTile(canvas, letter, color, left, top, right, bottom); + } + } else { + String letter = user.getName().substring(0, 1); + int color = this.getColorForName(user.getName()); + drawTile(canvas, letter, color, left, top, right, bottom); + } + } + + private void 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); + } + + private int getColorForName(String name) { + int holoColors[] = { 0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5, + 0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722, + 0xFF795548, 0xFF607d8b }; + return holoColors[(int) ((name.hashCode() & 0xffffffffl) % holoColors.length)]; + } + +} diff --git a/src/eu/siacs/conversations/services/ImageProvider.java b/src/eu/siacs/conversations/services/ImageProvider.java deleted file mode 100644 index ac78a454..00000000 --- a/src/eu/siacs/conversations/services/ImageProvider.java +++ /dev/null @@ -1,109 +0,0 @@ -package eu.siacs.conversations.services; - -import java.io.File; -import java.io.FileNotFoundException; - -import eu.siacs.conversations.Config; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Message; -import eu.siacs.conversations.persistance.DatabaseBackend; -import eu.siacs.conversations.persistance.FileBackend; -import android.content.ContentProvider; -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -public class ImageProvider extends ContentProvider { - - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) - throws FileNotFoundException { - ParcelFileDescriptor pfd; - FileBackend fileBackend = new FileBackend(getContext()); - if ("r".equals(mode)) { - DatabaseBackend databaseBackend = DatabaseBackend - .getInstance(getContext()); - String uuids = uri.getPath(); - Log.d(Config.LOGTAG, "uuids = " + uuids + " mode=" + mode); - if (uuids == null) { - throw new FileNotFoundException(); - } - String[] uuidsSplited = uuids.split("/", 2); - if (uuidsSplited.length != 3) { - throw new FileNotFoundException(); - } - String conversationUuid = uuidsSplited[1]; - String messageUuid = uuidsSplited[2].split("\\.")[0]; - - Log.d(Config.LOGTAG, "messageUuid=" + messageUuid); - - Conversation conversation = databaseBackend - .findConversationByUuid(conversationUuid); - if (conversation == null) { - throw new FileNotFoundException("conversation " - + conversationUuid + " could not be found"); - } - Message message = databaseBackend.findMessageByUuid(messageUuid); - if (message == null) { - throw new FileNotFoundException("message " + messageUuid - + " could not be found"); - } - - Account account = databaseBackend.findAccountByUuid(conversation - .getAccountUuid()); - if (account == null) { - throw new FileNotFoundException("account " - + conversation.getAccountUuid() + " cound not be found"); - } - message.setConversation(conversation); - conversation.setAccount(account); - - File file = fileBackend.getJingleFileLegacy(message); - pfd = ParcelFileDescriptor.open(file, - ParcelFileDescriptor.MODE_READ_ONLY); - return pfd; - } else { - throw new FileNotFoundException(); - } - } - - @Override - public int delete(Uri arg0, String arg1, String[] arg2) { - return 0; - } - - @Override - public String getType(Uri arg0) { - return null; - } - - @Override - public Uri insert(Uri arg0, ContentValues arg1) { - return null; - } - - @Override - public boolean onCreate() { - return false; - } - - @Override - public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, - String arg4) { - return null; - } - - @Override - public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) { - return 0; - } - - public static Uri getProviderUri(Message message) { - return Uri.parse("content://eu.siacs.conversations.images/" - + message.getConversationUuid() + "/" + message.getUuid() - + ".webp"); - } -}
\ No newline at end of file diff --git a/src/eu/siacs/conversations/services/NotificationService.java b/src/eu/siacs/conversations/services/NotificationService.java index 41656707..7b2e16df 100644 --- a/src/eu/siacs/conversations/services/NotificationService.java +++ b/src/eu/siacs/conversations/services/NotificationService.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.services; +import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.regex.Matcher; @@ -11,70 +12,83 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.Bitmap; import android.net.Uri; import android.os.PowerManager; import android.os.SystemClock; import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationCompat.BigPictureStyle; +import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.app.TaskStackBuilder; import android.text.Html; +import android.util.DisplayMetrics; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Downloadable; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ConversationActivity; public class NotificationService { private XmppConnectionService mXmppConnectionService; - private NotificationManager mNotificationManager; private LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<String, ArrayList<Message>>(); - public int NOTIFICATION_ID = 0x2342; + public static int NOTIFICATION_ID = 0x2342; private Conversation mOpenConversation; private boolean mIsInForeground; - - private long mEndGracePeriod = 0L; + private long mLastNotification; public NotificationService(XmppConnectionService service) { this.mXmppConnectionService = service; - this.mNotificationManager = (NotificationManager) service - .getSystemService(Context.NOTIFICATION_SERVICE); } - public synchronized void push(Message message) { - + public void push(Message message) { PowerManager pm = (PowerManager) mXmppConnectionService .getSystemService(Context.POWER_SERVICE); boolean isScreenOn = pm.isScreenOn(); + if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) { return; } - String conversationUuid = message.getConversationUuid(); - if (notifications.containsKey(conversationUuid)) { - notifications.get(conversationUuid).add(message); - } else { - ArrayList<Message> mList = new ArrayList<Message>(); - mList.add(message); - notifications.put(conversationUuid, mList); + synchronized (notifications) { + String conversationUuid = message.getConversationUuid(); + if (notifications.containsKey(conversationUuid)) { + notifications.get(conversationUuid).add(message); + } else { + ArrayList<Message> mList = new ArrayList<Message>(); + mList.add(message); + notifications.put(conversationUuid, mList); + } + Account account = message.getConversation().getAccount(); + updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn) + && !account.inGracePeriod() + && !this.inMiniGracePeriod(account)); } - updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn) - && !inGracePeriod()); + } public void clear() { - notifications.clear(); - updateNotification(false); + synchronized (notifications) { + notifications.clear(); + updateNotification(false); + } } public void clear(Conversation conversation) { - notifications.remove(conversation.getUuid()); - updateNotification(false); + synchronized (notifications) { + notifications.remove(conversation.getUuid()); + updateNotification(false); + } } private void updateNotification(boolean notify) { + NotificationManager notificationManager = (NotificationManager) mXmppConnectionService + .getSystemService(Context.NOTIFICATION_SERVICE); SharedPreferences preferences = mXmppConnectionService.getPreferences(); String ringtone = preferences.getString("notification_ringtone", null); @@ -82,76 +96,16 @@ public class NotificationService { true); if (notifications.size() == 0) { - mNotificationManager.cancel(NOTIFICATION_ID); + notificationManager.cancel(NOTIFICATION_ID); } else { - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder( - mXmppConnectionService); - mBuilder.setSmallIcon(R.drawable.ic_notification); + if (notify) { + this.markLastNotification(); + } + Builder mBuilder; if (notifications.size() == 1) { - ArrayList<Message> messages = notifications.values().iterator() - .next(); - if (messages.size() >= 1) { - Conversation conversation = messages.get(0) - .getConversation(); - mBuilder.setLargeIcon(conversation.getImage( - mXmppConnectionService, 64)); - mBuilder.setContentTitle(conversation.getName()); - StringBuilder text = new StringBuilder(); - for (int i = 0; i < messages.size(); ++i) { - text.append(messages.get(i).getReadableBody( - mXmppConnectionService)); - if (i != messages.size() - 1) { - text.append("\n"); - } - } - mBuilder.setStyle(new NotificationCompat.BigTextStyle() - .bigText(text.toString())); - mBuilder.setContentText(messages.get(0).getReadableBody( - mXmppConnectionService)); - if (notify) { - mBuilder.setTicker(messages.get(messages.size() - 1) - .getReadableBody(mXmppConnectionService)); - } - mBuilder.setContentIntent(createContentIntent(conversation - .getUuid())); - } else { - mNotificationManager.cancel(NOTIFICATION_ID); - return; - } + mBuilder = buildSingleConversations(notify); } else { - NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); - style.setBigContentTitle(notifications.size() - + " " - + mXmppConnectionService - .getString(R.string.unread_conversations)); - StringBuilder names = new StringBuilder(); - Conversation conversation = null; - for (ArrayList<Message> messages : notifications.values()) { - if (messages.size() > 0) { - conversation = messages.get(0).getConversation(); - String name = conversation.getName(); - style.addLine(Html.fromHtml("<b>" - + name - + "</b> " - + messages.get(0).getReadableBody( - mXmppConnectionService))); - names.append(name); - names.append(", "); - } - } - if (names.length() >= 2) { - names.delete(names.length() - 2, names.length()); - } - mBuilder.setContentTitle(notifications.size() - + " " - + mXmppConnectionService - .getString(R.string.unread_conversations)); - mBuilder.setContentText(names.toString()); - mBuilder.setStyle(style); - if (conversation != null) { - mBuilder.setContentIntent(createContentIntent(conversation - .getUuid())); - } + mBuilder = buildMultipleConversation(); } if (notify) { if (vibrate) { @@ -163,12 +117,147 @@ public class NotificationService { mBuilder.setSound(Uri.parse(ringtone)); } } + mBuilder.setSmallIcon(R.drawable.ic_notification); mBuilder.setDeleteIntent(createDeleteIntent()); - if (!inGracePeriod()) { - mBuilder.setLights(0xffffffff, 2000, 4000); - } + mBuilder.setLights(0xffffffff, 2000, 4000); Notification notification = mBuilder.build(); - mNotificationManager.notify(NOTIFICATION_ID, notification); + notificationManager.notify(NOTIFICATION_ID, notification); + } + } + + private Builder buildMultipleConversation() { + Builder mBuilder = new NotificationCompat.Builder( + mXmppConnectionService); + NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); + style.setBigContentTitle(notifications.size() + + " " + + mXmppConnectionService + .getString(R.string.unread_conversations)); + StringBuilder names = new StringBuilder(); + Conversation conversation = null; + for (ArrayList<Message> messages : notifications.values()) { + if (messages.size() > 0) { + conversation = messages.get(0).getConversation(); + String name = conversation.getName(); + style.addLine(Html.fromHtml("<b>" + name + "</b> " + + getReadableBody(messages.get(0)))); + names.append(name); + names.append(", "); + } + } + if (names.length() >= 2) { + names.delete(names.length() - 2, names.length()); + } + mBuilder.setContentTitle(notifications.size() + + " " + + mXmppConnectionService + .getString(R.string.unread_conversations)); + mBuilder.setContentText(names.toString()); + mBuilder.setStyle(style); + if (conversation != null) { + mBuilder.setContentIntent(createContentIntent(conversation + .getUuid())); + } + return mBuilder; + } + + private Builder buildSingleConversations(boolean notify) { + Builder mBuilder = new NotificationCompat.Builder( + mXmppConnectionService); + ArrayList<Message> messages = notifications.values().iterator().next(); + if (messages.size() >= 1) { + Conversation conversation = messages.get(0).getConversation(); + mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService() + .get(conversation, getPixel(64))); + mBuilder.setContentTitle(conversation.getName()); + Message message; + if ((message = getImage(messages)) != null) { + modifyForImage(mBuilder, message, messages, notify); + } else { + modifyForTextOnly(mBuilder, messages, notify); + } + mBuilder.setContentIntent(createContentIntent(conversation + .getUuid())); + } + return mBuilder; + + } + + private void modifyForImage(Builder builder, Message message, + ArrayList<Message> messages, boolean notify) { + try { + Bitmap bitmap = mXmppConnectionService.getFileBackend() + .getThumbnail(message, getPixel(288), false); + ArrayList<Message> tmp = new ArrayList<Message>(); + for (Message msg : messages) { + if (msg.getType() == Message.TYPE_TEXT + && msg.getDownloadable() == null) { + tmp.add(msg); + } + } + BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle(); + bigPictureStyle.bigPicture(bitmap); + if (tmp.size() > 0) { + bigPictureStyle.setSummaryText(getMergedBodies(tmp)); + builder.setContentText(getReadableBody(tmp.get(0))); + } else { + builder.setContentText(mXmppConnectionService.getString(R.string.image_file)); + } + builder.setStyle(bigPictureStyle); + } catch (FileNotFoundException e) { + modifyForTextOnly(builder, messages, notify); + } + } + + private void modifyForTextOnly(Builder builder, + ArrayList<Message> messages, boolean notify) { + builder.setStyle(new NotificationCompat.BigTextStyle() + .bigText(getMergedBodies(messages))); + builder.setContentText(getReadableBody(messages.get(0))); + if (notify) { + builder.setTicker(getReadableBody(messages.get(messages.size() - 1))); + } + } + + private Message getImage(ArrayList<Message> messages) { + for (Message message : messages) { + if (message.getType() == Message.TYPE_IMAGE + && message.getDownloadable() == null + && message.getEncryption() != Message.ENCRYPTION_PGP) { + return message; + } + } + return null; + } + + private String getMergedBodies(ArrayList<Message> messages) { + StringBuilder text = new StringBuilder(); + for (int i = 0; i < messages.size(); ++i) { + text.append(getReadableBody(messages.get(i))); + if (i != messages.size() - 1) { + text.append("\n"); + } + } + return text.toString(); + } + + private String getReadableBody(Message message) { + if (message.getDownloadable() != null + && (message.getDownloadable().getStatus() == Downloadable.STATUS_OFFER || message + .getDownloadable().getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE)) { + return mXmppConnectionService.getText( + R.string.image_offered_for_download).toString(); + } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { + return mXmppConnectionService.getText( + R.string.encrypted_message_received).toString(); + } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { + return mXmppConnectionService.getText(R.string.decryption_failed) + .toString(); + } else if (message.getType() == Message.TYPE_IMAGE) { + return mXmppConnectionService.getText(R.string.image_file) + .toString(); + } else { + return message.getBody().trim(); } } @@ -226,16 +315,19 @@ public class NotificationService { this.mIsInForeground = foreground; } - public void activateGracePeriod() { - this.mEndGracePeriod = SystemClock.elapsedRealtime() - + (Config.CARBON_GRACE_PERIOD * 1000); + private int getPixel(int dp) { + DisplayMetrics metrics = mXmppConnectionService.getResources() + .getDisplayMetrics(); + return ((int) (dp * metrics.density)); } - public void deactivateGracePeriod() { - this.mEndGracePeriod = 0L; + private void markLastNotification() { + this.mLastNotification = SystemClock.elapsedRealtime(); } - private boolean inGracePeriod() { - return SystemClock.elapsedRealtime() < this.mEndGracePeriod; + private boolean inMiniGracePeriod(Account account) { + int miniGrace = account.getStatus() == Account.STATUS_ONLINE ? Config.MINI_GRACE_PERIOD + : Config.MINI_GRACE_PERIOD * 2; + return SystemClock.elapsedRealtime() < (this.mLastNotification + miniGrace); } } diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java index e6297f4f..be73e07f 100644 --- a/src/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/eu/siacs/conversations/services/XmppConnectionService.java @@ -27,6 +27,7 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Downloadable; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.OnRenameListener; @@ -34,6 +35,7 @@ import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.generator.MessageGenerator; import eu.siacs.conversations.generator.PresenceGenerator; +import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.parser.MessageParser; import eu.siacs.conversations.parser.PresenceParser; @@ -74,6 +76,7 @@ import android.net.NetworkInfo; import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.FileObserver; import android.os.IBinder; import android.os.PowerManager; import android.os.PowerManager.WakeLock; @@ -81,11 +84,12 @@ import android.os.SystemClock; import android.preference.PreferenceManager; import android.provider.ContactsContract; import android.util.Log; +import android.util.LruCache; public class XmppConnectionService extends Service { public DatabaseBackend databaseBackend; - private FileBackend fileBackend; + private FileBackend fileBackend = new FileBackend(this); public long startDate; @@ -94,7 +98,8 @@ public class XmppConnectionService extends Service { private MemorizingTrustManager mMemorizingTrustManager; - private NotificationService mNotificationService; + private NotificationService mNotificationService = new NotificationService( + this); private MessageParser mMessageParser = new MessageParser(this); private PresenceParser mPresenceParser = new PresenceParser(this); @@ -106,20 +111,27 @@ public class XmppConnectionService extends Service { private CopyOnWriteArrayList<Conversation> conversations = null; private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( this); + private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( + this); + private AvatarService mAvatarService = new AvatarService(this); private OnConversationUpdate mOnConversationUpdate = null; - private int convChangedListenerCount = 0; + private Integer convChangedListenerCount = 0; private OnAccountUpdate mOnAccountUpdate = null; - private int accountChangedListenerCount = 0; + private Integer accountChangedListenerCount = 0; private OnRosterUpdate mOnRosterUpdate = null; - private int rosterChangedListenerCount = 0; + private Integer rosterChangedListenerCount = 0; public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() { @Override public void onContactStatusChanged(Contact contact, boolean online) { Conversation conversation = find(getConversations(), contact); if (conversation != null) { - conversation.endOtrIfNeeded(); + if (online && contact.getPresences().size() > 1) { + conversation.endOtrIfNeeded(); + } else { + conversation.resetOtrSession(); + } if (online && (contact.getPresences().size() == 1)) { sendUnsendMessages(conversation); } @@ -140,6 +152,17 @@ public class XmppConnectionService extends Service { } }; + private FileObserver fileObserver = new FileObserver( + FileBackend.getConversationsDirectory()) { + + @Override + public void onEvent(int event, String path) { + if (event == FileObserver.DELETE) { + markFileDeleted(path.split("\\.")[0]); + } + } + }; + private final IBinder mBinder = new XmppConnectionBinder(); private OnStatusChanged statusListener = new OnStatusChanged() { @@ -252,6 +275,7 @@ public class XmppConnectionService extends Service { } } }; + private LruCache<String, Bitmap> mBitmapCache; public PgpEngine getPgpEngine() { if (pgpServiceConnection.isBound()) { @@ -271,6 +295,10 @@ public class XmppConnectionService extends Service { return this.fileBackend; } + public AvatarService getAvatarService() { + return this.mAvatarService; + } + public Message attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback<Message> callback) { final Message message; @@ -331,15 +359,10 @@ public class XmppConnectionService extends Service { } } this.wakeLock.acquire(); - ConnectivityManager cm = (ConnectivityManager) getApplicationContext() - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); - boolean isConnected = activeNetwork != null - && activeNetwork.isConnected(); for (Account account : accounts) { if (!account.isOptionSet(Account.OPTION_DISABLED)) { - if (!isConnected) { + if (!hasInternetConnection()) { account.setStatus(Account.STATUS_NO_INTERNET); if (statusListener != null) { statusListener.onStatusChanged(account); @@ -398,6 +421,13 @@ public class XmppConnectionService extends Service { return START_STICKY; } + public boolean hasInternetConnection() { + ConnectivityManager cm = (ConnectivityManager) getApplicationContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + return activeNetwork != null && activeNetwork.isConnected(); + } + @SuppressLint("TrulyRandom") @Override public void onCreate() { @@ -406,10 +436,18 @@ public class XmppConnectionService extends Service { this.mRandom = new SecureRandom(); this.mMemorizingTrustManager = new MemorizingTrustManager( getApplicationContext()); - this.mNotificationService = new NotificationService(this); + + int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); + int cacheSize = maxMemory / 8; + this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) { + @Override + protected int sizeOf(String key, Bitmap bitmap) { + return bitmap.getByteCount() / 1024; + } + }; + this.databaseBackend = DatabaseBackend .getInstance(getApplicationContext()); - this.fileBackend = new FileBackend(getApplicationContext()); this.accounts = databaseBackend.getAccounts(); for (Account account : this.accounts) { @@ -420,6 +458,7 @@ public class XmppConnectionService extends Service { getContentResolver().registerContentObserver( ContactsContract.Contacts.CONTENT_URI, true, contactObserver); + this.fileObserver.startWatching(); this.pgpServiceConnection = new OpenPgpServiceConnection( getApplicationContext(), "org.sufficientlysecure.keychain"); this.pgpServiceConnection.bindToService(); @@ -511,8 +550,9 @@ public class XmppConnectionService extends Service { return connection; } - synchronized public void sendMessage(Message message) { + public void sendMessage(Message message) { Account account = message.getConversation().getAccount(); + account.deactivateGracePeriod(); Conversation conv = message.getConversation(); MessagePacket packet = null; boolean saveInDb = true; @@ -531,13 +571,14 @@ public class XmppConnectionService extends Service { && conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) { mJingleConnectionManager .createNewConnection(message); - } else if (message.getPresence() == null) { - message.setStatus(Message.STATUS_WAITING); } } else { mJingleConnectionManager.createNewConnection(message); } } else { + if (message.getEncryption() == Message.ENCRYPTION_OTR) { + conv.startOtrIfNeeded(); + } message.setStatus(Message.STATUS_WAITING); } } else { @@ -554,6 +595,7 @@ public class XmppConnectionService extends Service { send = true; } else if (message.getPresence() == null) { + conv.startOtrIfNeeded(); message.setStatus(Message.STATUS_WAITING); } } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { @@ -596,7 +638,7 @@ public class XmppConnectionService extends Service { } } - conv.getMessages().add(message); + conv.add(message); if (saveInDb) { if (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages()) { @@ -776,6 +818,7 @@ public class XmppConnectionService extends Service { .getString("photouri")); contact.setSystemName(phoneContact .getString("displayname")); + getAvatarService().clear(contact); } } } @@ -794,12 +837,39 @@ public class XmppConnectionService extends Service { Account account = accountLookupTable.get(conv.getAccountUuid()); conv.setAccount(account); conv.setMessages(databaseBackend.getMessages(conv, 50)); + checkDeletedFiles(conv); } } - return this.conversations; } + private void checkDeletedFiles(Conversation conversation) { + for (Message message : conversation.getMessages()) { + if (message.getType() == Message.TYPE_IMAGE + && message.getEncryption() != Message.ENCRYPTION_PGP) { + if (!getFileBackend().isFileAvailable(message)) { + message.setDownloadable(new DeletedDownloadable()); + } + } + } + } + + private void markFileDeleted(String uuid) { + for (Conversation conversation : getConversations()) { + for (Message message : conversation.getMessages()) { + if (message.getType() == Message.TYPE_IMAGE + && message.getEncryption() != Message.ENCRYPTION_PGP + && message.getUuid().equals(uuid)) { + if (!getFileBackend().isFileAvailable(message)) { + message.setDownloadable(new DeletedDownloadable()); + updateConversationUi(); + } + return; + } + } + } + } + public void populateWithOrderedConversations(List<Conversation> list) { populateWithOrderedConversations(list, true); } @@ -838,7 +908,7 @@ public class XmppConnectionService extends Service { for (Message message : messages) { message.setConversation(conversation); } - conversation.getMessages().addAll(0, messages); + conversation.addAll(0, messages); return messages.size(); } @@ -858,9 +928,9 @@ public class XmppConnectionService extends Service { public Conversation find(List<Conversation> haystack, Account account, String jid) { for (Conversation conversation : haystack) { - if ((account == null || conversation.getAccount().equals(account)) + if ((account == null || conversation.getAccount() == account) && (conversation.getContactJid().split("/", 2)[0] - .equals(jid))) { + .equalsIgnoreCase(jid))) { return conversation; } } @@ -927,7 +997,6 @@ public class XmppConnectionService extends Service { public void clearConversationHistory(Conversation conversation) { this.databaseBackend.deleteMessagesInConversation(conversation); - this.fileBackend.removeFiles(conversation); conversation.getMessages().clear(); updateConversationUi(); } @@ -973,60 +1042,85 @@ public class XmppConnectionService extends Service { public void setOnConversationListChangedListener( OnConversationUpdate listener) { - this.mNotificationService.deactivateGracePeriod(); - if (checkListeners()) { - switchToForeground(); + if (!isScreenOn()) { + Log.d(Config.LOGTAG, + "ignoring setOnConversationListChangedListener"); + return; + } + synchronized (this.convChangedListenerCount) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnConversationUpdate = listener; + this.mNotificationService.setIsInForeground(true); + this.convChangedListenerCount++; } - this.mOnConversationUpdate = listener; - this.mNotificationService.setIsInForeground(true); - this.convChangedListenerCount++; } public void removeOnConversationListChangedListener() { - this.convChangedListenerCount--; - if (this.convChangedListenerCount == 0) { - this.mOnConversationUpdate = null; - this.mNotificationService.setIsInForeground(false); - if (checkListeners()) { - switchToBackground(); + synchronized (this.convChangedListenerCount) { + this.convChangedListenerCount--; + if (this.convChangedListenerCount <= 0) { + this.convChangedListenerCount = 0; + this.mOnConversationUpdate = null; + this.mNotificationService.setIsInForeground(false); + if (checkListeners()) { + switchToBackground(); + } } } } public void setOnAccountListChangedListener(OnAccountUpdate listener) { - this.mNotificationService.deactivateGracePeriod(); - if (checkListeners()) { - switchToForeground(); + if (!isScreenOn()) { + Log.d(Config.LOGTAG, "ignoring setOnAccountListChangedListener"); + return; + } + synchronized (this.accountChangedListenerCount) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnAccountUpdate = listener; + this.accountChangedListenerCount++; } - this.mOnAccountUpdate = listener; - this.accountChangedListenerCount++; } public void removeOnAccountListChangedListener() { - this.accountChangedListenerCount--; - if (this.accountChangedListenerCount == 0) { - this.mOnAccountUpdate = null; - if (checkListeners()) { - switchToBackground(); + synchronized (this.accountChangedListenerCount) { + this.accountChangedListenerCount--; + if (this.accountChangedListenerCount <= 0) { + this.mOnAccountUpdate = null; + this.accountChangedListenerCount = 0; + if (checkListeners()) { + switchToBackground(); + } } } } public void setOnRosterUpdateListener(OnRosterUpdate listener) { - this.mNotificationService.deactivateGracePeriod(); - if (checkListeners()) { - switchToForeground(); + if (!isScreenOn()) { + Log.d(Config.LOGTAG, "ignoring setOnRosterUpdateListener"); + return; + } + synchronized (this.rosterChangedListenerCount) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnRosterUpdate = listener; + this.rosterChangedListenerCount++; } - this.mOnRosterUpdate = listener; - this.rosterChangedListenerCount++; } public void removeOnRosterUpdateListener() { - this.rosterChangedListenerCount--; - if (this.rosterChangedListenerCount == 0) { - this.mOnRosterUpdate = null; - if (checkListeners()) { - switchToBackground(); + synchronized (this.rosterChangedListenerCount) { + this.rosterChangedListenerCount--; + if (this.rosterChangedListenerCount <= 0) { + this.rosterChangedListenerCount = 0; + this.mOnRosterUpdate = null; + if (checkListeners()) { + switchToBackground(); + } } } } @@ -1042,11 +1136,10 @@ public class XmppConnectionService extends Service { XmppConnection connection = account.getXmppConnection(); if (connection != null && connection.getFeatures().csi()) { connection.sendActive(); - Log.d(Config.LOGTAG, account.getJid() - + " sending csi//active"); } } } + Log.d(Config.LOGTAG, "app switched into foreground"); } private void switchToBackground() { @@ -1055,11 +1148,17 @@ public class XmppConnectionService extends Service { XmppConnection connection = account.getXmppConnection(); if (connection != null && connection.getFeatures().csi()) { connection.sendInactive(); - Log.d(Config.LOGTAG, account.getJid() - + " sending csi//inactive"); } } } + this.mNotificationService.setIsInForeground(false); + Log.d(Config.LOGTAG, "app switched into background"); + } + + private boolean isScreenOn() { + PowerManager pm = (PowerManager) this + .getSystemService(Context.POWER_SERVICE); + return pm.isScreenOn(); } public void connectMultiModeConversations(Account account) { @@ -1197,7 +1296,7 @@ public class XmppConnectionService extends Service { conversation.getMucOptions().setOffline(); conversation.deregisterWithBookmark(); Log.d(Config.LOGTAG, conversation.getAccount().getJid() - + " leaving muc " + conversation.getContactJid()); + + ": leaving muc " + conversation.getContactJid()); } else { account.pendingConferenceLeaves.add(conversation); } @@ -1214,7 +1313,11 @@ public class XmppConnectionService extends Service { if (conversation.getMode() == Conversation.MODE_MULTI) { leaveMuc(conversation); } else { - conversation.endOtrIfNeeded(); + if (conversation.endOtrIfNeeded()) { + Log.d(Config.LOGTAG, account.getJid() + + ": ended otr session with " + + conversation.getContactJid()); + } } } } @@ -1230,6 +1333,7 @@ public class XmppConnectionService extends Service { public void updateMessage(Message message) { databaseBackend.updateMessage(message); + updateConversationUi(); } protected void syncDirtyContacts(Account account) { @@ -1410,10 +1514,16 @@ public class XmppConnectionService extends Service { if (account.setAvatar(avatar.getFilename())) { databaseBackend.updateAccount(account); } + getAvatarService().clear(account); + updateConversationUi(); + updateAccountUi(); } else { Contact contact = account.getRoster() .getContact(avatar.owner); contact.setAvatar(avatar.getFilename()); + getAvatarService().clear(contact); + updateConversationUi(); + updateRosterUi(); } if (callback != null) { callback.success(avatar); @@ -1463,6 +1573,7 @@ public class XmppConnectionService extends Service { if (account.setAvatar(avatar.getFilename())) { databaseBackend.updateAccount(account); } + getAvatarService().clear(account); callback.success(avatar); } else { fetchAvatar(account, avatar, callback); @@ -1675,6 +1786,10 @@ public class XmppConnectionService extends Service { return this.pm; } + public LruCache<String, Bitmap> getBitmapCache() { + return this.mBitmapCache; + } + public void replyWithNotAcceptable(Account account, MessagePacket packet) { if (account.getStatus() == Account.STATUS_ONLINE) { MessagePacket error = this.mMessageGenerator @@ -1779,8 +1894,7 @@ public class XmppConnectionService extends Service { ArrayList<Contact> contacts = new ArrayList<Contact>(); for (Account account : getAccounts()) { if (!account.isOptionSet(Account.OPTION_DISABLED)) { - Contact contact = account.getRoster() - .getContactAsShownInRoster(jid); + Contact contact = account.getRoster().getContactFromRoster(jid); if (contact != null) { contacts.add(contact); } @@ -1792,4 +1906,44 @@ public class XmppConnectionService extends Service { public NotificationService getNotificationService() { return this.mNotificationService; } + + public HttpConnectionManager getHttpConnectionManager() { + return this.mHttpConnectionManager; + } + + private class DeletedDownloadable implements Downloadable { + + @Override + public boolean start() { + return false; + } + + @Override + public int getStatus() { + return Downloadable.STATUS_DELETED; + } + + @Override + public long getFileSize() { + return 0; + } + + } + + public void resendFailedMessages(Message message) { + List<Message> messages = new ArrayList<Message>(); + Message current = message; + while(current.getStatus() == Message.STATUS_SEND_FAILED) { + messages.add(current); + if (current.mergable(current.next())) { + current = current.next(); + } else { + break; + } + } + for(Message msg: messages) { + markMessage(msg, Message.STATUS_WAITING); + this.resendMessage(msg); + } + } } |