avatars for contact / new chat view #55

This commit is contained in:
daniel.boehrs
2018-11-01 16:21:43 +01:00
parent d4f1af7521
commit dd67248066
11 changed files with 145 additions and 67 deletions
@@ -38,7 +38,6 @@ import com.soundcloud.android.crop.Crop;
import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
@@ -309,7 +308,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
private void initializeProfileAvatar(boolean excludeSystem) {
String address = DcHelper.get(this, DcHelper.CONFIG_ADDRESS);
if (AvatarHelper.getAvatarFile(this, address).exists() && AvatarHelper.getAvatarFile(this, address).length() > 0) {
if (AvatarHelper.getSelfAvatarFile(this, address).exists() && AvatarHelper.getSelfAvatarFile(this, address).length() > 0) {
new AsyncTask<Void, Void, byte[]>() {
@Override
protected byte[] doInBackground(Void... params) {
@@ -452,7 +451,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
TextSecurePreferences.setProfileName(context, name);
try {
AvatarHelper.setAvatar(CreateProfileActivity.this, DcHelper.get(context, DcHelper.CONFIG_ADDRESS), avatarBytes);
AvatarHelper.setSelfAvatar(CreateProfileActivity.this, DcHelper.get(context, DcHelper.CONFIG_ADDRESS), avatarBytes);
TextSecurePreferences.setProfileAvatarId(CreateProfileActivity.this, new SecureRandom().nextInt());
} catch (IOException e) {
Log.w(TAG, e);
@@ -63,8 +63,6 @@ import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -298,7 +296,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
dcContext.addContactToChat(chatId, contactId);
}
}
setGroupAvatar(chatId);
AvatarHelper.setGroupAvatar(this, chatId, avatarBmp);
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, chatId);
@@ -306,29 +304,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
finish();
}
private void setGroupAvatar(int chatId) {
if(avatarBmp == null) {
return;
}
String avatarPath = dcContext.getChat(chatId).getProfileImage();
if (avatarPath != null && !avatarPath.isEmpty()) {
File oldImage = new File(avatarPath);
if (oldImage.exists()) {
//noinspection ResultOfMethodCallIgnored
oldImage.delete();
}
}
avatarPath = AvatarHelper.getFilePathForGroupAvatar(this, chatId, System.currentTimeMillis());
FileOutputStream outStream;
try {
outStream = new FileOutputStream(avatarPath);
avatarBmp.compress(Bitmap.CompressFormat.JPEG, 85, outStream);
dcContext.setChatProfileImage(chatId, avatarPath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private boolean showGroupNameEmptyToast(String groupName) {
if(groupName == null) {
Toast.makeText(this, getString(R.string.GroupCreateActivity_please_enter_group_name), Toast.LENGTH_LONG).show();
@@ -348,7 +323,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
dcContext.setChatName(editGroupChatId, groupName);
updateGroupParticipants();
setGroupAvatar(editGroupChatId);
AvatarHelper.setGroupAvatar(this, editGroupChatId, avatarBmp);
Intent intent = new Intent();
Recipient recipient = dcContext.getRecipient(ApplicationDcContext.RECIPIENT_TYPE_CHAT, editGroupChatId);
@@ -33,6 +33,8 @@ import android.text.TextUtils;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.util.Hash;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.ArrayList;
import java.util.Collection;
@@ -61,7 +63,7 @@ public class ContactAccessor {
private static final int CONTACT_CURSOR_MAIL = 1;
private static final int CONTACT_CURSOR_PHOTO = 2;
private static final int CONTACT_CURSOR_CONTACT_ID = 2;
public static final String PUSH_COLUMN = "push";
@@ -86,7 +88,7 @@ public class ContactAccessor {
}
public Cursor getAllSystemContacts(Context context) {
String[] projection = {ContactsContract.Data.DISPLAY_NAME, ContactsContract.CommonDataKinds.Email.ADDRESS, Phone.PHOTO_ID};
String[] projection = {ContactsContract.Data.DISPLAY_NAME, ContactsContract.CommonDataKinds.Email.ADDRESS, ContactsContract.Data.CONTACT_ID};
return context.getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, projection, null, null, null);
}
@@ -94,15 +96,16 @@ public class ContactAccessor {
Cursor systemContactsCursor = getAllSystemContacts(context);
StringBuilder result = new StringBuilder();
List<String> mailList = new ArrayList<>();
Set<String> contactPhotoIdentifiers = new HashSet<>();
while (systemContactsCursor != null && systemContactsCursor.moveToNext()) {
String name = systemContactsCursor.getString(CONTACT_CURSOR_NAME);
String mail = systemContactsCursor.getString(CONTACT_CURSOR_MAIL);
/*
TODO
- Persist this data to get it via DcContact.getProfileImage();
- Copy non existing images to the Delta avatar directory
- Use: String contactPhotoUri = systemContactsCursor.getString(CONTACT_CURSOR_PHOTO);
*/
String contactId = systemContactsCursor.getString(CONTACT_CURSOR_CONTACT_ID);
if (contactId != null) {
String identifier = name + mail;
String hashedIdentifierAndId = Hash.sha256(identifier) + "|" + contactId;
contactPhotoIdentifiers.add(hashedIdentifierAndId);
}
if (mail != null && !mail.isEmpty() && !mailList.contains(mail)) {
mailList.add(mail);
if (name.isEmpty()) {
@@ -111,6 +114,7 @@ public class ContactAccessor {
result.append(name).append("\n").append(mail).append("\n");
}
}
TextSecurePreferences.setSystemContactPhotos(context, contactPhotoIdentifiers);
return result.toString();
}
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
@@ -17,6 +18,8 @@ import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.Hash;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -69,6 +72,11 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientM
}
else {
this.recipient = DcHelper.getContext(getContext()).getRecipient(contact);
String identifier = Hash.sha256(contact.getName() + contact.getAddr());
Uri systemContactPhoto = TextSecurePreferences.getSystemContactPhoto(getContext(), identifier);
if (systemContactPhoto != null) {
this.recipient.setSystemContactPhoto(systemContactPhoto);
}
this.recipient.addListener(this);
if (this.recipient.getName() != null) {
name = this.recipient.getName();
@@ -35,7 +35,7 @@ public abstract class LocalFileContactPhoto implements ContactPhoto {
@Override
public @Nullable Uri getUri(@NonNull Context context) {
return isProfilePhoto() ? Uri.fromFile(AvatarHelper.getAvatarFile(context, address)) : null;
return isProfilePhoto() ? Uri.fromFile(AvatarHelper.getSelfAvatarFile(context, address)) : null;
}
@Override
@@ -30,7 +30,7 @@ public class MyProfileContactPhoto implements ContactPhoto {
@Override
public @Nullable
Uri getUri(@NonNull Context context) {
return Uri.fromFile(AvatarHelper.getAvatarFile(context, address));
return Uri.fromFile(AvatarHelper.getSelfAvatarFile(context, address));
}
@Override
@@ -3,13 +3,13 @@ package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.net.Uri;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.util.Conversions;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.security.MessageDigest;
@@ -26,8 +26,8 @@ public class SystemContactPhoto implements ContactPhoto {
}
@Override
public InputStream openInputStream(Context context) throws FileNotFoundException {
return context.getContentResolver().openInputStream(contactPhotoUri);
public InputStream openInputStream(Context context) {
return ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), contactPhotoUri, false);
}
@Nullable
@@ -64,7 +64,7 @@ public class RetrieveProfileAvatarJob extends ContextJob implements InjectableTy
if (TextUtils.isEmpty(profileAvatar)) {
Log.w(TAG, "Removing profile avatar for: " + recipient.getAddress().serialize());
AvatarHelper.delete(context, recipient.getAddress());
AvatarHelper.deleteSelfAvatar(context, recipient.getAddress());
database.setProfileAvatar(recipient, profileAvatar);
return;
}
@@ -76,7 +76,7 @@ public class RetrieveProfileAvatarJob extends ContextJob implements InjectableTy
File decryptDestination = File.createTempFile("avatar", "jpg", context.getCacheDir());
Util.copy(avatarStream, new FileOutputStream(decryptDestination));
decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.getAddress()));
decryptDestination.renameTo(AvatarHelper.getSelfAvatarFile(context, recipient.getAddress()));
} finally {
if (downloadDestination != null) downloadDestination.delete();
}
@@ -3,16 +3,19 @@ package org.thoughtcrime.securesms.profiles;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.annimon.stream.Stream;
import com.b44t.messenger.DcContext;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.database.Address;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -37,19 +40,63 @@ public class AvatarHelper {
return context.getFilesDir().getAbsolutePath() + File.separator + AVATAR_DIRECTORY + File.separator;
}
public static void setGroupAvatar(Context context, int chatId, Bitmap bitmap) {
if (bitmap == null) {
return;
}
DcContext dcContext = DcHelper.getContext(context);
String avatarPath = dcContext.getChat(chatId).getProfileImage();
if (avatarPath != null && !avatarPath.isEmpty()) {
File oldImage = new File(avatarPath);
if (oldImage.exists()) {
//noinspection ResultOfMethodCallIgnored
oldImage.delete();
}
}
avatarPath = getFilePathForGroupAvatar(context, chatId, System.currentTimeMillis());
FileOutputStream outStream;
try {
outStream = new FileOutputStream(avatarPath);
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, outStream);
dcContext.setChatProfileImage(chatId, avatarPath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
@SuppressLint("DefaultLocale")
public static String getFilePathForContactAvatar(Context context, int contactId, long timestamp) {
return getAvatarDirectoryPath(context) + String.format(CONTACT_TEMPLATE, contactId, timestamp);
}
public static InputStream getInputStreamFor(@NonNull Context context, @NonNull Address address)
throws IOException {
return new FileInputStream(getAvatarFile(context, address));
public static void setContactAvatar(Context context, int contactId, Bitmap bitmap) {
if (bitmap == null) {
return;
}
DcContext dcContext = DcHelper.getContext(context);
String avatarPath = dcContext.getContact(contactId).getProfileImage();
if (avatarPath != null && !avatarPath.isEmpty()) {
File oldImage = new File(avatarPath);
if (oldImage.exists()) {
//noinspection ResultOfMethodCallIgnored
oldImage.delete();
}
}
avatarPath = getFilePathForContactAvatar(context, contactId, System.currentTimeMillis());
FileOutputStream outStream;
try {
outStream = new FileOutputStream(avatarPath);
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, outStream);
//dcContext.setContactProfileImage(contactId, avatarPath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static InputStream getInputStreamFor(@NonNull Context context, @NonNull String address)
throws IOException {
return new FileInputStream(getAvatarFile(context, address));
return new FileInputStream(getSelfAvatarFile(context, address));
}
public static List<File> getAvatarFiles(@NonNull Context context) {
@@ -60,50 +107,50 @@ public class AvatarHelper {
else return Stream.of(results).toList();
}
public static void delete(@NonNull Context context, @NonNull Address address) {
delete(context, getAvatarFile(context, address));
public static void deleteSelfAvatar(@NonNull Context context, @NonNull Address address) {
deleteSelfAvatar(context, getSelfAvatarFile(context, address));
}
public static void delete(@NonNull Context context, @NonNull File avatar) {
public static void deleteSelfAvatar(@NonNull Context context, @NonNull File avatar) {
avatar.delete();
DcHelper.set(context, DcHelper.CONFIG_SELF_AVATAR, null);
}
public static @NonNull
File getAvatarFile(@NonNull Context context, @NonNull Address address) {
public static File getSelfAvatarFile(@NonNull Context context, @NonNull Address address) {
String name = new File(address.serialize()).getName();
return getAvatarFile(context, name);
return getSelfAvatarFile(context, name);
}
public static @NonNull
File getAvatarFile(@NonNull Context context, @NonNull String address) {
public static File getSelfAvatarFile(@NonNull Context context, @NonNull String address) {
File avatarDirectory = new File(context.getFilesDir(), AVATAR_DIRECTORY);
avatarDirectory.mkdirs();
return new File(avatarDirectory, address);
}
public static void setAvatar(@NonNull Context context, @NonNull Address address, @Nullable byte[] data)
public static void setSelfAvatar(@NonNull Context context, @NonNull Address address, @Nullable byte[] data)
throws IOException {
if (data == null) {
delete(context, address);
deleteSelfAvatar(context, address);
} else {
File avatar = getAvatarFile(context, address);
writeAvatarFile(context, avatar, data);
File avatar = getSelfAvatarFile(context, address);
writeSelfAvatarFile(context, avatar, data);
}
}
public static void setAvatar(@NonNull Context context, @NonNull String address, @Nullable byte[] data) throws IOException {
File avatar = getAvatarFile(context, address);
public static void setSelfAvatar(@NonNull Context context, @NonNull String address, @Nullable byte[] data) throws IOException {
File avatar = getSelfAvatarFile(context, address);
if (data == null) {
delete(context, avatar);
deleteSelfAvatar(context, avatar);
} else {
writeAvatarFile(context, avatar, data);
writeSelfAvatarFile(context, avatar, data);
}
}
private static void writeAvatarFile(@NonNull Context context, @NonNull File avatar, @Nullable byte[] data) throws IOException {
private static void writeSelfAvatarFile(@NonNull Context context, @NonNull File avatar, @Nullable byte[] data) throws IOException {
FileOutputStream out = new FileOutputStream(avatar);
out.write(data);
if (data != null) {
out.write(data);
}
out.close();
String path = avatar.getPath();
DcHelper.set(context, DcHelper.CONFIG_SELF_AVATAR, path);
@@ -0,0 +1,22 @@
package org.thoughtcrime.securesms.util;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Hash {
public static String sha256(String input) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(input.getBytes(Charset.forName("UTF-8")));
byte[] digest = messageDigest.digest();
return String.format("%064x", new BigInteger(1, digest));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
@@ -1,11 +1,13 @@
package org.thoughtcrime.securesms.util;
import android.content.ContentUris;
import android.content.Context;
import android.content.SharedPreferences;
import android.hardware.Camera.CameraInfo;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.support.annotation.ArrayRes;
import android.support.annotation.NonNull;
@@ -20,9 +22,11 @@ import org.whispersystems.libsignal.util.Medium;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class TextSecurePreferences {
@@ -134,6 +138,8 @@ public class TextSecurePreferences {
private static final String LAST_FULL_CONTACT_SYNC_TIME = "pref_last_full_contact_sync_time";
private static final String NEEDS_FULL_CONTACT_SYNC = "pref_needs_full_contact_sync";
private static final String PREF_CONTACT_PHOTO_IDENTIFIERS = "pref_contact_photo_identifiers";
public static boolean isScreenLockEnabled(@NonNull Context context) {
return getBooleanPreference(context, SCREEN_LOCK, false);
}
@@ -859,4 +865,21 @@ public class TextSecurePreferences {
return defaultValues;
}
}
public static void setSystemContactPhotos(Context context, Set<String> contactPhotoIdentifiers) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putStringSet(PREF_CONTACT_PHOTO_IDENTIFIERS, contactPhotoIdentifiers).apply();
}
public static Uri getSystemContactPhoto(Context context, String identifier) {
List<String> contactPhotoIdentifiers = new ArrayList<>(getStringSetPreference(context, PREF_CONTACT_PHOTO_IDENTIFIERS, new HashSet<>()));
for(String contactPhotoIdentifier : contactPhotoIdentifiers) {
if (contactPhotoIdentifier.contains(identifier)) {
String[] parts = contactPhotoIdentifier.split("\\|");
long contactId = Long.valueOf(parts[1]);
return ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
}
}
return null;
}
}