From 41cad291f9ce422f4d26fd536cdfbb8b80fb88a5 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 4 May 2015 11:36:18 -0700 Subject: [PATCH] Display a generated avatar icon rather than a single default. If the contact doesn't have an image, render a color-coded background and the first letter of the contact's name. 1) Don't display anything during recipient resolution. 2) Display a # icon in material gray for recipients with no name. 3) Display a material group icon in material gray for groups with no avatar icon set. Closes #3104 // FREEBIE --- AndroidManifest.xml | 2 + build.gradle | 5 + res/layout/conversation_item_received.xml | 6 +- res/layout/conversation_list_item_view.xml | 7 +- res/layout/message_recipient_list_item.xml | 7 +- .../push_contact_selection_list_item.xml | 4 +- res/layout/share_list_item_view.xml | 7 +- res/values/attrs.xml | 1 + .../securesms/ConversationItem.java | 34 +---- .../securesms/ConversationListItem.java | 35 +++-- .../securesms/MessageRecipientListItem.java | 35 +++-- .../thoughtcrime/securesms/ShareListItem.java | 30 ++-- .../securesms/components/AvatarImageView.java | 49 +++++++ .../securesms/components/FromTextView.java | 69 ++++++++++ .../contacts/ContactPhotoFactory.java | 130 +++++++++++------- .../contacts/ContactSelectionListAdapter.java | 12 +- .../securesms/jobs/AvatarDownloadJob.java | 3 +- .../notifications/MessageNotifier.java | 6 +- .../securesms/recipients/AvatarGenerator.java | 120 ---------------- .../securesms/recipients/Recipient.java | 31 ++--- .../recipients/RecipientProvider.java | 43 +++--- .../securesms/util/BitmapUtil.java | 21 +++ .../securesms/util/BitmapWorkerRunnable.java | 24 ++-- .../securesms/util/RecipientViewUtil.java | 83 ----------- 24 files changed, 342 insertions(+), 422 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/components/AvatarImageView.java create mode 100644 src/org/thoughtcrime/securesms/components/FromTextView.java delete mode 100644 src/org/thoughtcrime/securesms/recipients/AvatarGenerator.java delete mode 100644 src/org/thoughtcrime/securesms/util/RecipientViewUtil.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 344f4282e3..e8d5599d06 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -5,6 +5,8 @@ android:versionCode="113" android:versionName="2.13.0"> + + diff --git a/build.gradle b/build.gradle index a7b845c23c..b8f14ce291 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,9 @@ repositories { maven { url "https://raw.github.com/whispersystems/maven/master/shortcutbadger/releases/" } + maven { // textdrawable + url 'https://dl.bintray.com/amulyakhare/maven' + } jcenter() mavenLocal() } @@ -64,6 +67,7 @@ dependencies { compile 'org.whispersystems:jobmanager:0.11.0' compile 'org.whispersystems:libpastelog:1.0.6' compile 'org.whispersystems:textsecure-android:1.3.0' + compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' @@ -104,6 +108,7 @@ dependencyVerification { 'org.whispersystems:jobmanager:ea9cb943c4892fb90c1eea1be30efeb85cefca213d52c788419553b58d0ed70d', 'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2', 'org.whispersystems:textsecure-android:df4c1ac9ee8f7cd43c8c07d64b16e31875e04632afc3fe73f2a47292a86c79a1', + 'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb', 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a', 'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f', diff --git a/res/layout/conversation_item_received.xml b/res/layout/conversation_item_received.xml index 0ca454c25e..b41f77390b 100644 --- a/res/layout/conversation_item_received.xml +++ b/res/layout/conversation_item_received.xml @@ -26,17 +26,15 @@ android:layout_marginBottom="6dp" android:layout_marginRight="0dp"> - + android:contentDescription="@string/conversation_item_received__contact_photo_description" /> - - - @@ -38,7 +36,8 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - - - + diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index 3f389192ee..a413c17a51 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -21,9 +21,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.provider.ContactsContract; -import android.provider.ContactsContract.QuickContact; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.AttributeSet; @@ -38,9 +35,9 @@ import android.widget.Toast; import com.afollestad.materialdialogs.AlertDialogWrapper; import org.thoughtcrime.securesms.ConversationFragment.SelectionClickListener; +import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.BubbleContainer; import org.thoughtcrime.securesms.components.ThumbnailView; -import org.thoughtcrime.securesms.contacts.ContactPhotoFactory; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; @@ -85,7 +82,7 @@ public class ConversationItem extends LinearLayout { private TextView groupStatusText; private ImageView secureImage; private ImageView failedImage; - private ImageView contactPhoto; + private AvatarImageView contactPhoto; private ImageView deliveryImage; private ImageView pendingIndicator; private BubbleContainer bubbleContainer; @@ -123,7 +120,7 @@ public class ConversationItem extends LinearLayout { this.failedImage = (ImageView)findViewById(R.id.sms_failed_indicator); this.mmsDownloadButton = (Button) findViewById(R.id.mms_download_button); this.mmsDownloadingLabel = (TextView) findViewById(R.id.mms_label_downloading); - this.contactPhoto = (ImageView)findViewById(R.id.contact_photo); + this.contactPhoto = (AvatarImageView) findViewById(R.id.contact_photo); this.deliveryImage = (ImageView)findViewById(R.id.delivered_indicator); this.bodyBubble = findViewById(R.id.body_bubble); this.pendingIndicator = (ImageView)findViewById(R.id.pending_approval_indicator); @@ -369,30 +366,7 @@ public class ConversationItem extends LinearLayout { private void setContactPhotoForRecipient(final Recipient recipient) { if (contactPhoto == null) return; - Bitmap contactPhotoBitmap; - - if ((recipient.getContactPhoto() == ContactPhotoFactory.getDefaultContactPhoto(context)) && (groupThread)) { - contactPhotoBitmap = recipient.getGeneratedAvatar(context); - } else { - contactPhotoBitmap = recipient.getContactPhoto(); - } - - contactPhoto.setImageBitmap(contactPhotoBitmap); - - contactPhoto.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (recipient.getContactUri() != null) { - QuickContact.showQuickContact(context, contactPhoto, recipient.getContactUri(), QuickContact.MODE_LARGE, null); - } else { - final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); - intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.getNumber()); - intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); - context.startActivity(intent); - } - } - }); - + contactPhoto.setAvatar(recipient, true); contactPhoto.setVisibility(View.VISIBLE); } diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java index daf867c42c..ff3b9664a8 100644 --- a/src/org/thoughtcrime/securesms/ConversationListItem.java +++ b/src/org/thoughtcrime/securesms/ConversationListItem.java @@ -22,16 +22,16 @@ import android.graphics.Typeface; import android.os.Handler; import android.util.AttributeSet; import android.view.View; -import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; +import org.thoughtcrime.securesms.components.AvatarImageView; +import org.thoughtcrime.securesms.components.FromTextView; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.Emoji; -import org.thoughtcrime.securesms.util.RecipientViewUtil; import java.util.Locale; import java.util.Set; @@ -53,15 +53,15 @@ public class ConversationListItem extends RelativeLayout private final static Typeface BOLD_TYPEFACE = Typeface.create("sans-serif", Typeface.BOLD); private final static Typeface LIGHT_TYPEFACE = Typeface.create("sans-serif-light", Typeface.NORMAL); - private Context context; - private Set selectedThreads; - private Recipients recipients; - private long threadId; - private TextView subjectView; - private TextView fromView; - private TextView dateView; - private boolean read; - private ImageView contactPhotoImage; + private Context context; + private Set selectedThreads; + private Recipients recipients; + private long threadId; + private TextView subjectView; + private FromTextView fromView; + private TextView dateView; + private boolean read; + private AvatarImageView contactPhotoImage; private final Handler handler = new Handler(); private int distributionType; @@ -79,10 +79,9 @@ public class ConversationListItem extends RelativeLayout @Override protected void onFinishInflate() { this.subjectView = (TextView) findViewById(R.id.subject); - this.fromView = (TextView) findViewById(R.id.from); + this.fromView = (FromTextView) findViewById(R.id.from); this.dateView = (TextView) findViewById(R.id.date); - - this.contactPhotoImage = (ImageView) findViewById(R.id.contact_photo_image); + this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image); initializeContactWidgetVisibility(); } @@ -95,7 +94,7 @@ public class ConversationListItem extends RelativeLayout this.distributionType = thread.getDistributionType(); this.recipients.addListener(this); - this.fromView.setText(RecipientViewUtil.formatFrom(context, recipients, read)); + this.fromView.setText(recipients, read); this.subjectView.setText(Emoji.getInstance(context).emojify(thread.getDisplayBody(), Emoji.EMOJI_SMALL, @@ -110,7 +109,7 @@ public class ConversationListItem extends RelativeLayout } setBackground(read, batchMode); - RecipientViewUtil.setContactPhoto(context, contactPhotoImage, recipients.getPrimaryRecipient(), true); + this.contactPhotoImage.setAvatar(recipients.getPrimaryRecipient(), true); } public void unbind() { @@ -157,8 +156,8 @@ public class ConversationListItem extends RelativeLayout handler.post(new Runnable() { @Override public void run() { - ConversationListItem.this.fromView.setText(RecipientViewUtil.formatFrom(context, recipients, read)); - RecipientViewUtil.setContactPhoto(context, contactPhotoImage, recipients.getPrimaryRecipient(), true); + fromView.setText(recipients, read); + contactPhotoImage.setAvatar(recipients.getPrimaryRecipient(), true); } }); } diff --git a/src/org/thoughtcrime/securesms/MessageRecipientListItem.java b/src/org/thoughtcrime/securesms/MessageRecipientListItem.java index 06547fe982..c3c81741f2 100644 --- a/src/org/thoughtcrime/securesms/MessageRecipientListItem.java +++ b/src/org/thoughtcrime/securesms/MessageRecipientListItem.java @@ -23,10 +23,11 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.Button; -import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; +import org.thoughtcrime.securesms.components.AvatarImageView; +import org.thoughtcrime.securesms.components.FromTextView; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; @@ -35,7 +36,6 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.MessageSender; -import org.thoughtcrime.securesms.util.RecipientViewUtil; /** * A simple view to show the recipients of a message @@ -47,12 +47,12 @@ public class MessageRecipientListItem extends RelativeLayout { private final static String TAG = MessageRecipientListItem.class.getSimpleName(); - private Recipient recipient; - private TextView fromView; - private TextView errorDescription; - private Button conflictButton; - private Button resendButton; - private ImageView contactPhotoImage; + private Recipient recipient; + private FromTextView fromView; + private TextView errorDescription; + private Button conflictButton; + private Button resendButton; + private AvatarImageView contactPhotoImage; private final Handler handler = new Handler(); @@ -66,11 +66,11 @@ public class MessageRecipientListItem extends RelativeLayout @Override protected void onFinishInflate() { - this.fromView = (TextView) findViewById(R.id.from); - this.errorDescription = (TextView) findViewById(R.id.error_description); - this.contactPhotoImage = (ImageView) findViewById(R.id.contact_photo_image); - this.conflictButton = (Button) findViewById(R.id.conflict_button); - this.resendButton = (Button) findViewById(R.id.resend_button); + this.fromView = (FromTextView) findViewById(R.id.from); + this.errorDescription = (TextView) findViewById(R.id.error_description); + this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image); + this.conflictButton = (Button) findViewById(R.id.conflict_button); + this.resendButton = (Button) findViewById(R.id.resend_button); } public void set(final MasterSecret masterSecret, @@ -81,9 +81,8 @@ public class MessageRecipientListItem extends RelativeLayout this.recipient = recipient; recipient.addListener(this); - fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipient)); - - RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, recipient, false); + fromView.setText(recipient); + contactPhotoImage.setAvatar(recipient, false); setIssueIndicators(masterSecret, record, isPushGroup); } @@ -160,8 +159,8 @@ public class MessageRecipientListItem extends RelativeLayout handler.post(new Runnable() { @Override public void run() { - fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipient)); - RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, recipient, false); + fromView.setText(recipient); + contactPhotoImage.setAvatar(recipient, false); } }); } diff --git a/src/org/thoughtcrime/securesms/ShareListItem.java b/src/org/thoughtcrime/securesms/ShareListItem.java index aa8769520f..0a739c6e15 100644 --- a/src/org/thoughtcrime/securesms/ShareListItem.java +++ b/src/org/thoughtcrime/securesms/ShareListItem.java @@ -20,17 +20,13 @@ import android.content.Context; import android.content.res.TypedArray; import android.os.Handler; import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.makeramen.RoundedImageView; +import org.thoughtcrime.securesms.components.AvatarImageView; +import org.thoughtcrime.securesms.components.FromTextView; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipients; -import org.thoughtcrime.securesms.util.RecipientViewUtil; /** * A simple view to show the recipients of an open conversation @@ -42,12 +38,12 @@ public class ShareListItem extends RelativeLayout { private final static String TAG = ShareListItem.class.getSimpleName(); - private Context context; - private Recipients recipients; - private long threadId; - private TextView fromView; + private Context context; + private Recipients recipients; + private long threadId; + private FromTextView fromView; - private RoundedImageView contactPhotoImage; + private AvatarImageView contactPhotoImage; private final Handler handler = new Handler(); private int distributionType; @@ -64,8 +60,8 @@ public class ShareListItem extends RelativeLayout @Override protected void onFinishInflate() { - this.fromView = (TextView) findViewById(R.id.from); - this.contactPhotoImage = (RoundedImageView) findViewById(R.id.contact_photo_image); + this.fromView = (FromTextView) findViewById(R.id.from); + this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image); } public void set(ThreadRecord thread) { @@ -74,10 +70,10 @@ public class ShareListItem extends RelativeLayout this.distributionType = thread.getDistributionType(); this.recipients.addListener(this); - this.fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipients)); + this.fromView.setText(recipients); setBackground(); - RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, this.recipients.getPrimaryRecipient(), false); + this.contactPhotoImage.setAvatar(this.recipients.getPrimaryRecipient(), false); } public void unbind() { @@ -110,8 +106,8 @@ public class ShareListItem extends RelativeLayout handler.post(new Runnable() { @Override public void run() { - fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipients)); - RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, recipients.getPrimaryRecipient(), false); + fromView.setText(recipients); + contactPhotoImage.setAvatar(recipients.getPrimaryRecipient(), false); } }); } diff --git a/src/org/thoughtcrime/securesms/components/AvatarImageView.java b/src/org/thoughtcrime/securesms/components/AvatarImageView.java new file mode 100644 index 0000000000..90bb807a62 --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/AvatarImageView.java @@ -0,0 +1,49 @@ +package org.thoughtcrime.securesms.components; + +import android.content.Context; +import android.content.Intent; +import android.provider.ContactsContract; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; + +import org.thoughtcrime.securesms.recipients.Recipient; + +public class AvatarImageView extends ImageView { + + public AvatarImageView(Context context) { + super(context); + setScaleType(ScaleType.CENTER_INSIDE); + } + + public AvatarImageView(Context context, AttributeSet attrs) { + super(context, attrs); + setScaleType(ScaleType.CENTER_INSIDE); + } + + public void setAvatar(Recipient recipient, boolean quickContactEnabled) { + setImageDrawable(recipient.getContactPhoto()); + setAvatarClickHandler(recipient, quickContactEnabled); + } + + private void setAvatarClickHandler(final Recipient recipient, boolean quickContactEnabled) { + if (!recipient.isGroupRecipient() && quickContactEnabled) { + setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (recipient.getContactUri() != null) { + ContactsContract.QuickContact.showQuickContact(getContext(), AvatarImageView.this, recipient.getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null); + } else { + final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.getNumber()); + intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); + getContext().startActivity(intent); + } + } + }); + } else { + setOnClickListener(null); + } + } + +} diff --git a/src/org/thoughtcrime/securesms/components/FromTextView.java b/src/org/thoughtcrime/securesms/components/FromTextView.java new file mode 100644 index 0000000000..225fbfb12e --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/FromTextView.java @@ -0,0 +1,69 @@ +package org.thoughtcrime.securesms.components; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.style.StyleSpan; +import android.util.AttributeSet; +import android.widget.TextView; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.Recipients; + +public class FromTextView extends TextView { + + public FromTextView(Context context) { + super(context); + } + + public FromTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setText(Recipient recipient) { + setText(new Recipients(recipient)); + } + + public void setText(Recipients recipients) { + setText(recipients, true); + } + + public void setText(Recipients recipients, boolean read) { + int attributes[] = new int[]{R.attr.conversation_list_item_count_color}; + TypedArray colors = getContext().obtainStyledAttributes(attributes); + boolean isUnnamedGroup = recipients.isGroupRecipient() && TextUtils.isEmpty(recipients.getPrimaryRecipient().getName()); + + String fromString; + + if (isUnnamedGroup) { + fromString = getContext().getString(R.string.ConversationActivity_unnamed_group); + } else { + fromString = recipients.toShortString(); + } + + int typeface; + + if (isUnnamedGroup) { + if (!read) typeface = Typeface.BOLD_ITALIC; + else typeface = Typeface.ITALIC; + } else if (!read) { + typeface = Typeface.BOLD; + } else { + typeface = Typeface.NORMAL; + } + + SpannableStringBuilder builder = new SpannableStringBuilder(fromString); + builder.setSpan(new StyleSpan(typeface), 0, builder.length(), + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + + colors.recycle(); + + setText(builder); + } + + +} diff --git a/src/org/thoughtcrime/securesms/contacts/ContactPhotoFactory.java b/src/org/thoughtcrime/securesms/contacts/ContactPhotoFactory.java index 1ea3606023..37cee29e76 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactPhotoFactory.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactPhotoFactory.java @@ -1,15 +1,31 @@ package org.thoughtcrime.securesms.contacts; import android.content.Context; -import android.database.Cursor; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.net.Uri; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.provider.ContactsContract; -import android.provider.ContactsContract.Contacts; +import android.support.annotation.Nullable; import android.util.Log; +import android.view.Gravity; +import android.widget.ImageView; + +import com.amulyakhare.textdrawable.TextDrawable; +import com.amulyakhare.textdrawable.util.ColorGenerator; +import com.makeramen.RoundedDrawable; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.recipients.Recipient; @@ -24,62 +40,56 @@ import java.util.Map; public class ContactPhotoFactory { private static final String TAG = ContactPhotoFactory.class.getSimpleName(); + private static final ColorGenerator COLOR_GENERATOR = ColorGenerator.MATERIAL; + private static final int UNKNOWN_COLOR = 0xff9E9E9E; + private static final Object defaultPhotoLock = new Object(); private static final Object defaultGroupPhotoLock = new Object(); + private static final Object loadingPhotoLock = new Object(); - private static Bitmap defaultContactPhoto; - private static Bitmap defaultGroupContactPhoto; + private static Drawable defaultContactPhoto; + private static Drawable defaultGroupContactPhoto; + private static Drawable loadingPhoto; private static final Map localUserContactPhotoCache = Collections.synchronizedMap(new LRUCache(2)); - private static final String[] CONTENT_URI_PROJECTION = new String[] { - ContactsContract.Contacts._ID, - ContactsContract.Contacts.DISPLAY_NAME, - ContactsContract.Contacts.LOOKUP_KEY - }; + public static Drawable getLoadingPhoto(Context context) { + synchronized (loadingPhotoLock) { + if (loadingPhoto == null) + loadingPhoto = RoundedDrawable.fromDrawable(context.getResources().getDrawable(android.R.color.transparent)); + + return loadingPhoto; + } + } + + public static Drawable getDefaultContactPhoto(@Nullable String name) { + if (name != null && !name.isEmpty()) { + return TextDrawable.builder().buildRound(String.valueOf(name.charAt(0)), + COLOR_GENERATOR.getColor(name)); + } - public static Bitmap getDefaultContactPhoto(Context context) { synchronized (defaultPhotoLock) { if (defaultContactPhoto == null) - defaultContactPhoto = BitmapFactory.decodeResource(context.getResources(), - R.drawable.ic_contact_picture); + defaultContactPhoto = TextDrawable.builder().buildRound("#", UNKNOWN_COLOR); + return defaultContactPhoto; } } - public static Bitmap getDefaultGroupPhoto(Context context) { + public static Drawable getDefaultGroupPhoto(Context context) { synchronized (defaultGroupPhotoLock) { - if (defaultGroupContactPhoto == null) - defaultGroupContactPhoto = BitmapFactory.decodeResource(context.getResources(), - R.drawable.ic_group_photo); - return defaultGroupContactPhoto; - } - } + if (defaultGroupContactPhoto == null) { + Drawable background = TextDrawable.builder().buildRound(" ", UNKNOWN_COLOR); + RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(context.getResources().getDrawable(R.drawable.ic_group_white_24dp)); + foreground.setScaleType(ImageView.ScaleType.CENTER); - public static Bitmap getLocalUserContactPhoto(Context context, Uri uri) { - if (uri == null) return getDefaultContactPhoto(context); - Bitmap contactPhoto = localUserContactPhotoCache.get(uri); - - if (contactPhoto == null) { - Cursor cursor = context.getContentResolver().query(uri, CONTENT_URI_PROJECTION, - null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - contactPhoto = getContactPhoto(context, Uri.withAppendedPath(Contacts.CONTENT_URI, - cursor.getLong(0) + "")); - } else { - contactPhoto = getDefaultContactPhoto(context); - } - } finally { - if (cursor != null) cursor.close(); + defaultGroupContactPhoto = new ExpandingLayerDrawable(new Drawable[] {background, foreground}); } - localUserContactPhotoCache.put(uri, contactPhoto); + return defaultGroupContactPhoto; } - - return contactPhoto; } public static void clearCache() { @@ -91,25 +101,32 @@ public class ContactPhotoFactory { localUserContactPhotoCache.remove(recipient.getContactUri()); } - public static Bitmap getContactPhoto(Context context, Uri uri) { + public static Drawable getContactPhoto(Context context, Uri uri, String name) { final InputStream inputStream = getContactPhotoStream(context, uri); final int targetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size); - Bitmap contactPhoto = null; + if (inputStream != null) { try { - contactPhoto = BitmapUtil.createScaledBitmap(inputStream, - getContactPhotoStream(context, uri), - targetSize, - targetSize); + return RoundedDrawable.fromBitmap(BitmapUtil.createScaledBitmap(inputStream, + getContactPhotoStream(context, uri), + targetSize, + targetSize)) + .setScaleType(ImageView.ScaleType.CENTER_CROP) + .setOval(true); } catch (BitmapDecodingException bde) { Log.w(TAG, bde); } } - if (contactPhoto == null) { - contactPhoto = ContactPhotoFactory.getDefaultContactPhoto(context); - } - return contactPhoto; + return getDefaultContactPhoto(name); + } + + public static Drawable getGroupContactPhoto(Context context, @Nullable byte[] avatar) { + if (avatar == null) return getDefaultGroupPhoto(context); + + return RoundedDrawable.fromBitmap(BitmapFactory.decodeByteArray(avatar, 0, avatar.length)) + .setScaleType(ImageView.ScaleType.CENTER_CROP) + .setOval(true); } private static InputStream getContactPhotoStream(Context context, Uri uri) { @@ -119,4 +136,21 @@ public class ContactPhotoFactory { return ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), uri); } } + + private static class ExpandingLayerDrawable extends LayerDrawable { + + public ExpandingLayerDrawable(Drawable[] layers) { + super(layers); + } + + @Override + public int getIntrinsicWidth() { + return -1; + } + + @Override + public int getIntrinsicHeight() { + return -1; + } + } } diff --git a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java index 95e1d7f9e9..fe14322be4 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java @@ -19,7 +19,6 @@ package org.thoughtcrime.securesms.contacts; import android.content.Context; import android.content.res.TypedArray; import android.database.Cursor; -import android.graphics.Bitmap; import android.provider.ContactsContract; import android.support.v4.widget.CursorAdapter; import android.text.Spannable; @@ -73,7 +72,6 @@ public class ContactSelectionListAdapter extends CursorAdapter private final boolean multiSelect; private final LayoutInflater li; private final TypedArray drawables; - private final Bitmap defaultPhoto; private final int scaledPhotoSize; private final HashMap selectedContacts = new HashMap<>(); @@ -84,7 +82,6 @@ public class ContactSelectionListAdapter extends CursorAdapter this.li = LayoutInflater.from(context); this.drawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES); this.multiSelect = multiSelect; - this.defaultPhoto = ContactPhotoFactory.getDefaultContactPhoto(context); this.scaledPhotoSize = context.getResources().getDimensionPixelSize(R.dimen.contact_selection_photo_size); } @@ -180,7 +177,7 @@ public class ContactSelectionListAdapter extends CursorAdapter numberLabelSpan.setSpan(new ForegroundColorSpan(drawables.getColor(2, 0xff444444)), contactData.number.length(), numberWithLabel.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); holder.number.setText(numberLabelSpan); } - holder.contactPhoto.setImageBitmap(defaultPhoto); + holder.contactPhoto.setImageDrawable(ContactPhotoFactory.getLoadingPhoto(context)); if (contactData.id > -1) loadBitmap(contactData.number, holder.contactPhoto); } @@ -229,11 +226,14 @@ public class ContactSelectionListAdapter extends CursorAdapter return true; } + // FIXME -- It should be unnecessary to duplicate the existing asynchronous resolution + // infrastructure we've built for Recipient objects here. + public void loadBitmap(String number, ImageView imageView) { if (cancelPotentialWork(number, imageView)) { - final BitmapWorkerRunnable runnable = new BitmapWorkerRunnable(context, imageView, defaultPhoto, number, scaledPhotoSize); + final BitmapWorkerRunnable runnable = new BitmapWorkerRunnable(context, imageView, number, scaledPhotoSize); final TaggedFutureTask task = new TaggedFutureTask(runnable, null, number); - final AsyncDrawable asyncDrawable = new AsyncDrawable(defaultPhoto, task); + final AsyncDrawable asyncDrawable = new AsyncDrawable(task); imageView.setImageDrawable(asyncDrawable); if (!task.isCancelled()) photoResolver.execute(new FutureTask(task, null)); diff --git a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java index 6a7123f494..6ac1e0a8e4 100644 --- a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; import android.util.Log; import org.thoughtcrime.securesms.Release; @@ -74,7 +75,7 @@ public class AvatarDownloadJob extends MasterSecretJob { Recipient groupRecipient = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(groupId), true) .getPrimaryRecipient(); - groupRecipient.setContactPhoto(avatar); + groupRecipient.setContactPhoto(new BitmapDrawable(avatar)); } } catch (InvalidMessageException | BitmapDecodingException | NonSuccessfulResponseCodeException e) { Log.w(TAG, e); diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java index 12af949543..12b92c084d 100644 --- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java +++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java @@ -24,9 +24,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.database.Cursor; -import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; @@ -181,9 +181,9 @@ public class MessageNotifier { List notifications = notificationState.getNotifications(); NotificationCompat.Builder builder = new NotificationCompat.Builder(context); Recipient recipient = notifications.get(0).getIndividualRecipient(); - Bitmap recipientPhoto = recipient.getContactPhoto(); + Drawable recipientPhoto = recipient.getContactPhoto(); - if (recipientPhoto != null) builder.setLargeIcon(BitmapUtil.getCircleBitmap(recipientPhoto)); + if (recipientPhoto != null) builder.setLargeIcon(BitmapUtil.createFromDrawable(recipientPhoto)); builder.setSmallIcon(R.drawable.icon_notification); builder.setColor(context.getResources().getColor(R.color.textsecure_primary)); builder.setContentTitle(recipient.toShortString()); diff --git a/src/org/thoughtcrime/securesms/recipients/AvatarGenerator.java b/src/org/thoughtcrime/securesms/recipients/AvatarGenerator.java deleted file mode 100644 index 142f238613..0000000000 --- a/src/org/thoughtcrime/securesms/recipients/AvatarGenerator.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.thoughtcrime.securesms.recipients; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.Typeface; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.contacts.ContactPhotoFactory; -import org.thoughtcrime.securesms.util.BitmapUtil; - -/** - * Utility class to generate avatars for contacts who don't have a contact - * picture set. - * - * @author Lukas Barth - */ -public class AvatarGenerator { - - public static Bitmap generateFor(Context context, Recipient recipient) { - if ((recipient == null) || (recipient.getName() == null)) { - return ContactPhotoFactory.getDefaultContactPhoto(context); - } - - final int size = ContactPhotoFactory.getDefaultContactPhoto(context).getHeight(); - final Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(output); - final int color = getColorForRecipient(recipient, context); - final Paint paint = new Paint(); - final int innerRectOffset = (int) Math.ceil((size - Math.sqrt(2) * (size / 2)) / 2); - final Rect innerRect = new Rect(innerRectOffset, innerRectOffset, - size - innerRectOffset, size - innerRectOffset); - - paint.setAntiAlias(true); - paint.setColor(color); - canvas.drawCircle(size / 2, size / 2, size / 2, paint); - - paint.setColor(Color.WHITE); - Typeface robotoLightTypeface = Typeface.createFromAsset(context.getAssets(), "fonts/Roboto-Light.ttf"); - paint.setTypeface(robotoLightTypeface); - setFontSize(innerRect, paint); - - paint.setTextAlign(Paint.Align.CENTER); - - int initialIndex = 0; - char[] contactName = recipient.getName().toCharArray(); - - if (contactName.length == 0) { - contactName = new char[]{'?'}; - initialIndex = 0; - } else { - while ((! Character.isLetter(contactName[initialIndex]))) { - initialIndex ++; - - if (initialIndex >= contactName.length) { - contactName[0] = '?'; - initialIndex = 0; - break; - } - } - } - - Rect textBounds = new Rect(); - paint.getTextBounds(contactName, initialIndex, 1, textBounds); - - int bottomOffset = (innerRect.height() - textBounds.height()) / 2; - - canvas.drawText(Character.toString(contactName[initialIndex]), - innerRect.centerX(), innerRect.bottom - bottomOffset, paint); - - return output; - } - - - private static int getColorForRecipient(Recipient recipient, Context context) { - if ((recipient == null) || (recipient.getName() == null)) { - return Color.WHITE; - } - - long nameHash = recipient.getName().hashCode(); - Resources res = context.getResources(); - TypedArray colorArray = res.obtainTypedArray(R.array.avatar_colors); - int index = Math.abs((int) (nameHash % colorArray.length())); - int color = colorArray.getColor(index, Color.BLACK); - - colorArray.recycle(); - - return color; - } - - private static int setFontSize(Rect textRect, Paint paint) { - boolean overflow = false; - int currentSize = 0; - - while (!overflow) { - currentSize++; - paint.setTextSize(currentSize); - - Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); - int textHeight = fontMetrics.descent - fontMetrics.ascent; - - if (textHeight > textRect.height()) { - overflow = true; - } - } - - currentSize--; - - currentSize *= 1.2; - - paint.setTextSize(currentSize); - - return currentSize; - } -} diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index cde6e9c262..c14cdc862f 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -18,13 +18,14 @@ package org.thoughtcrime.securesms.recipients; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.Log; import org.thoughtcrime.securesms.contacts.ContactPhotoFactory; import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.FutureTaskListener; +import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.ListenableFutureTask; import java.util.HashSet; @@ -33,25 +34,22 @@ public class Recipient { private final static String TAG = Recipient.class.getSimpleName(); - private final HashSet listeners = new HashSet(); + private final HashSet listeners = new HashSet<>(); private final long recipientId; private String number; private String name; - private Bitmap contactPhoto; - private Bitmap generatedAvatar; + private Drawable contactPhoto; + private Uri contactUri; - private Uri contactUri; - - Recipient(String number, Bitmap contactPhoto, + Recipient(String number, Drawable contactPhoto, long recipientId, ListenableFutureTask future) { this.number = number; this.contactPhoto = contactPhoto; this.recipientId = recipientId; - this.generatedAvatar = null; future.addListener(new FutureTaskListener() { @Override @@ -81,7 +79,7 @@ public class Recipient { }); } - Recipient(String name, String number, long recipientId, Uri contactUri, Bitmap contactPhoto) { + Recipient(String name, String number, long recipientId, Uri contactUri, Drawable contactPhoto) { this.number = number; this.recipientId = recipientId; this.contactUri = contactUri; @@ -93,7 +91,7 @@ public class Recipient { return this.contactUri; } - public synchronized void setContactPhoto(Bitmap bitmap) { + public synchronized void setContactPhoto(Drawable bitmap) { this.contactPhoto = bitmap; notifyListeners(); } @@ -143,20 +141,13 @@ public class Recipient { return (name == null ? number : name); } - public synchronized Bitmap getContactPhoto() { + public synchronized Drawable getContactPhoto() { return contactPhoto; } - public synchronized Bitmap getGeneratedAvatar(Context context) { - if (this.generatedAvatar == null) - this.generatedAvatar = AvatarGenerator.generateFor(context, this); - - return this.generatedAvatar; - } - public static Recipient getUnknownRecipient(Context context) { return new Recipient("Unknown", "Unknown", -1, null, - ContactPhotoFactory.getDefaultContactPhoto(context)); + ContactPhotoFactory.getDefaultContactPhoto(null)); } @Override @@ -174,7 +165,7 @@ public class Recipient { return 31 + (int)this.recipientId; } - public static interface RecipientModifiedListener { + public interface RecipientModifiedListener { public void onModified(Recipient recipient); } } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java index a0a941221e..39c3a44e4e 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java @@ -18,8 +18,7 @@ package org.thoughtcrime.securesms.recipients; import android.content.Context; import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.PhoneLookup; @@ -29,11 +28,10 @@ import org.thoughtcrime.securesms.contacts.ContactPhotoFactory; import org.thoughtcrime.securesms.database.CanonicalAddressDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; -import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.LRUCache; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ListenableFutureTask; +import org.thoughtcrime.securesms.util.Util; import java.io.IOException; import java.util.Collections; @@ -75,9 +73,9 @@ public class RecipientProvider { if (details != null) { recipient = new Recipient(details.name, details.number, recipientId, details.contactUri, details.avatar); } else { - final Bitmap defaultPhoto = isGroupRecipient + final Drawable defaultPhoto = isGroupRecipient ? ContactPhotoFactory.getDefaultGroupPhoto(context) - : ContactPhotoFactory.getDefaultContactPhoto(context); + : ContactPhotoFactory.getDefaultContactPhoto(null); recipient = new Recipient(null, number, recipientId, null, defaultPhoto); } @@ -100,16 +98,16 @@ public class RecipientProvider { } }; - ListenableFutureTask future = new ListenableFutureTask(task); + ListenableFutureTask future = new ListenableFutureTask<>(task); asyncRecipientResolver.submit(future); - Bitmap contactPhoto; + Drawable contactPhoto; if (isGroupRecipient) { contactPhoto = ContactPhotoFactory.getDefaultGroupPhoto(context); } else { - contactPhoto = ContactPhotoFactory.getDefaultContactPhoto(context); + contactPhoto = ContactPhotoFactory.getLoadingPhoto(context); } Recipient recipient = new Recipient(number, contactPhoto, recipientId, future); @@ -134,9 +132,10 @@ public class RecipientProvider { try { if (cursor != null && cursor.moveToFirst()) { - Uri contactUri = Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1)); - Bitmap contactPhoto = ContactPhotoFactory.getContactPhoto(context, Uri.withAppendedPath(Contacts.CONTENT_URI, - cursor.getLong(2)+"")); + Uri contactUri = Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1)); + Drawable contactPhoto = ContactPhotoFactory.getContactPhoto(context, + Uri.withAppendedPath(Contacts.CONTENT_URI, cursor.getLong(2)+""), + cursor.getString(0)); return new RecipientDetails(cursor.getString(0), cursor.getString(3), contactUri, contactPhoto); } } finally { @@ -144,7 +143,7 @@ public class RecipientProvider { cursor.close(); } - return null; + return new RecipientDetails(null, number, null, ContactPhotoFactory.getDefaultContactPhoto(null)); } private RecipientDetails getGroupRecipientDetails(Context context, String groupId) { @@ -153,12 +152,7 @@ public class RecipientProvider { .getGroup(GroupUtil.getDecodedId(groupId)); if (record != null) { - byte[] avatarBytes = record.getAvatar(); - Bitmap avatar; - - if (avatarBytes == null) avatar = ContactPhotoFactory.getDefaultGroupPhoto(context); - else avatar = BitmapFactory.decodeByteArray(avatarBytes, 0, avatarBytes.length); - + Drawable avatar = ContactPhotoFactory.getGroupContactPhoto(context, record.getAvatar()); return new RecipientDetails(record.getTitle(), groupId, null, avatar); } @@ -170,12 +164,12 @@ public class RecipientProvider { } public static class RecipientDetails { - public final String name; - public final String number; - public final Bitmap avatar; - public final Uri contactUri; + public final String name; + public final String number; + public final Drawable avatar; + public final Uri contactUri; - public RecipientDetails(String name, String number, Uri contactUri, Bitmap avatar) { + public RecipientDetails(String name, String number, Uri contactUri, Drawable avatar) { this.name = name; this.number = number; this.avatar = avatar; @@ -183,4 +177,5 @@ public class RecipientProvider { } } + } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/util/BitmapUtil.java b/src/org/thoughtcrime/securesms/util/BitmapUtil.java index 0b189f6ef3..3b7b8a091b 100644 --- a/src/org/thoughtcrime/securesms/util/BitmapUtil.java +++ b/src/org/thoughtcrime/securesms/util/BitmapUtil.java @@ -12,6 +12,8 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.Log; import android.util.Pair; @@ -253,4 +255,23 @@ public class BitmapUtil { return output; } + public static Bitmap createFromDrawable(Drawable drawable) { + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable)drawable).getBitmap(); + } + + int width = drawable.getIntrinsicWidth(); + width = width > 0 ? width : 1; + + int height = drawable.getIntrinsicHeight(); + height = height > 0 ? height : 1; + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + + return bitmap; + } + } diff --git a/src/org/thoughtcrime/securesms/util/BitmapWorkerRunnable.java b/src/org/thoughtcrime/securesms/util/BitmapWorkerRunnable.java index ef45bfdf40..1018050a73 100644 --- a/src/org/thoughtcrime/securesms/util/BitmapWorkerRunnable.java +++ b/src/org/thoughtcrime/securesms/util/BitmapWorkerRunnable.java @@ -21,6 +21,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.DrawableContainer; import android.widget.ImageView; import com.makeramen.RoundedDrawable; @@ -38,49 +39,42 @@ import java.lang.ref.WeakReference; public class BitmapWorkerRunnable implements Runnable { private final static String TAG = BitmapWorkerRunnable.class.getSimpleName(); - private final Bitmap defaultPhoto; - private final WeakReference imageViewReference; private final Context context; private final int size; public final String number; - public BitmapWorkerRunnable(Context context, ImageView imageView, Bitmap defaultPhoto, String number, int size) { + public BitmapWorkerRunnable(Context context, ImageView imageView, String number, int size) { this.imageViewReference = new WeakReference<>(imageView); this.context = context; - this.defaultPhoto = defaultPhoto; this.size = size; this.number = number; } @Override public void run() { - final Recipient recipient = RecipientFactory.getRecipientsFromString(context, number, false).getPrimaryRecipient(); - final Bitmap contactPhoto = recipient.getContactPhoto(); - if (defaultPhoto == contactPhoto) { - return; - } - if (recipient.getContactPhoto() != null) { + final Recipient recipient = RecipientFactory.getRecipientsFromString(context, number, false).getPrimaryRecipient(); + final Drawable contactPhoto = recipient.getContactPhoto(); + + if (contactPhoto != null) { final ImageView imageView = imageViewReference.get(); final TaggedFutureTask bitmapWorkerTask = AsyncDrawable.getBitmapWorkerTask(imageView); if (bitmapWorkerTask.getTag().equals(number) && imageView != null) { - final BitmapDrawable drawable = new BitmapDrawable(context.getResources(), recipient.getContactPhoto()); imageView.post(new Runnable() { @Override public void run() { - imageView.setImageDrawable(drawable); + imageView.setImageDrawable(contactPhoto); } }); } } } - public static class AsyncDrawable extends RoundedDrawable { + public static class AsyncDrawable extends BitmapDrawable { private final WeakReference> bitmapWorkerTaskReference; - public AsyncDrawable(Bitmap bitmap, TaggedFutureTask bitmapWorkerTask) { - super(bitmap); + public AsyncDrawable(TaggedFutureTask bitmapWorkerTask) { bitmapWorkerTaskReference = new WeakReference>(bitmapWorkerTask); } diff --git a/src/org/thoughtcrime/securesms/util/RecipientViewUtil.java b/src/org/thoughtcrime/securesms/util/RecipientViewUtil.java deleted file mode 100644 index 3660a8ed08..0000000000 --- a/src/org/thoughtcrime/securesms/util/RecipientViewUtil.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.thoughtcrime.securesms.util; - -import android.content.Context; -import android.content.Intent; -import android.content.res.TypedArray; -import android.graphics.Typeface; -import android.net.Uri; -import android.provider.Contacts.Intents; -import android.provider.ContactsContract.QuickContact; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.text.TextUtils; -import android.text.style.StyleSpan; -import android.view.View; -import android.widget.ImageView; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.Recipients; - -public class RecipientViewUtil { - public static CharSequence formatFrom(Context context, Recipient recipient) { - return formatFrom(context, new Recipients(recipient)); - } - - public static CharSequence formatFrom(Context context, Recipients from) { - return formatFrom(context, from, true); - } - - public static CharSequence formatFrom(Context context, Recipients from, boolean read) { - int attributes[] = new int[] {R.attr.conversation_list_item_count_color}; - TypedArray colors = context.obtainStyledAttributes(attributes); - - final String fromString; - final boolean isUnnamedGroup = from.isGroupRecipient() && TextUtils.isEmpty(from.getPrimaryRecipient().getName()); - if (isUnnamedGroup) { - fromString = context.getString(R.string.ConversationActivity_unnamed_group); - } else { - fromString = from.toShortString(); - } - SpannableStringBuilder builder = new SpannableStringBuilder(fromString); - - final int typeface; - if (isUnnamedGroup) { - if (!read) typeface = Typeface.BOLD_ITALIC; - else typeface = Typeface.ITALIC; - } else if (!read) { - typeface = Typeface.BOLD; - } else { - typeface = Typeface.NORMAL; - } - - builder.setSpan(new StyleSpan(typeface), 0, builder.length(), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - - - colors.recycle(); - return builder; - } - - public static void setContactPhoto(final Context context, final ImageView imageView, final Recipient recipient, boolean showQuickContact) { - if (recipient == null) return; - - imageView.setImageBitmap(recipient.getContactPhoto()); - - if (!recipient.isGroupRecipient() && showQuickContact) { - imageView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (recipient.getContactUri() != null) { - QuickContact.showQuickContact(context, imageView, recipient.getContactUri(), QuickContact.MODE_LARGE, null); - } else { - Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, Uri.fromParts("tel", recipient.getNumber(), null)); - context.startActivity(intent); - } - } - }); - } else { - imageView.setOnClickListener(null); - } - } - -}