From 8084822f16ca26f5bd52809b4b55e0c88f7bd6d4 Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Thu, 7 May 2020 14:33:27 -0300 Subject: [PATCH] Connect GV2 title and avatar updates and prevent no-change avatar updates. --- .../securesms/GroupCreateActivity.java | 88 ++++++++++--------- .../conversation/ConversationActivity.java | 9 +- .../securesms/groups/GroupManager.java | 33 +++---- .../profiles/edit/EditProfileRepository.java | 2 +- .../profiles/edit/EditProfileViewModel.java | 26 ++++-- .../edit/EditPushGroupProfileRepository.java | 12 ++- .../edit/EditSelfProfileRepository.java | 12 +-- 7 files changed, 103 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java b/app/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java index 57a3f08c1c..ddb4311ef5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -18,6 +18,7 @@ package org.thoughtcrime.securesms; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -69,16 +70,13 @@ import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.BitmapUtil; -import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter; import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter.OnRecipientDeletedListener; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.util.InvalidNumberException; import java.io.IOException; import java.util.Collection; @@ -88,7 +86,7 @@ import java.util.List; import java.util.Set; /** - * Activity to create and update groups + * Activity to create and update {@link GroupId.V1} groups * * @author Jake McGinty */ @@ -99,27 +97,31 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity private final static String TAG = GroupCreateActivity.class.getSimpleName(); - public static final String GROUP_ID_EXTRA = "group_id"; - public static final String GROUP_THREAD_EXTRA = "group_thread"; + private static final String GROUP_ID_EXTRA = "group_id"; + private static final String GROUP_THREAD_EXTRA = "group_thread"; - private final DynamicTheme dynamicTheme = new DynamicTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); + private final DynamicTheme dynamicTheme = new DynamicTheme(); private static final short REQUEST_CODE_SELECT_AVATAR = 26165; private static final int PICK_CONTACT = 1; - private EditText groupName; - private ListView lv; - private ImageView avatar; - private TextView creatingText; - private Bitmap avatarBmp; + private EditText groupName; + private ListView listView; + private ImageView avatar; + private TextView creatingText; + private Bitmap avatarBmp; @NonNull private Optional groupToUpdate = Optional.absent(); + public static Intent newEditGroupIntent(@NonNull Context context, @NonNull GroupId.V1 groupId) { + Intent intent = new Intent(context, GroupCreateActivity.class); + intent.putExtra(GroupCreateActivity.GROUP_ID_EXTRA, groupId.toString()); + return intent; + } + @Override protected void onPreCreate() { dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); } @Override @@ -135,7 +137,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity public void onResume() { super.onResume(); dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); updateViewState(); } @@ -192,18 +193,23 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } private void initializeResources() { - RecipientsEditor recipientsEditor = ViewUtil.findById(this, R.id.recipients_text); - PushRecipientsPanel recipientsPanel = ViewUtil.findById(this, R.id.recipients); - lv = ViewUtil.findById(this, R.id.selected_contacts_list); - avatar = ViewUtil.findById(this, R.id.avatar); - groupName = ViewUtil.findById(this, R.id.group_name); - creatingText = ViewUtil.findById(this, R.id.creating_group_text); + RecipientsEditor recipientsEditor = findViewById(R.id.recipients_text); + PushRecipientsPanel recipientsPanel = findViewById(R.id.recipients); + + listView = findViewById(R.id.selected_contacts_list); + avatar = findViewById(R.id.avatar); + groupName = findViewById(R.id.group_name); + creatingText = findViewById(R.id.creating_group_text); + SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(this); adapter.setOnRecipientDeletedListener(this); - lv.setAdapter(adapter); + listView.setAdapter(adapter); + recipientsEditor.setHint(R.string.recipients_panel__add_members); recipientsPanel.setPanelChangeListener(this); + findViewById(R.id.contacts_button).setOnClickListener(new AddRecipientButtonListener()); + avatar.setImageDrawable(getDefaultGroupAvatar()); avatar.setOnClickListener(view -> AvatarSelectionBottomSheetDialogFragment.create(avatarBmp != null, false, REQUEST_CODE_SELECT_AVATAR, true).show(getSupportFragmentManager(), null)); } @@ -216,10 +222,12 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity final GroupId groupId = GroupId.parseNullableOrThrow(getIntent().getStringExtra(GROUP_ID_EXTRA)); if (groupId != null) { - new FillExistingGroupInfoAsyncTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, groupId); + GroupId.V1 groupIdV1 = groupId.requireV1(); - if (FeatureFlags.newGroupUI() && groupId.isPush()) { - avatar.setOnClickListener(v -> startActivity(EditProfileActivity.getIntentForGroupProfile(this, groupId.requirePush()))); + new FillExistingGroupInfoAsyncTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, groupIdV1); + + if (FeatureFlags.newGroupUI()) { + avatar.setOnClickListener(v -> startActivity(EditProfileActivity.getIntentForGroupProfile(this, groupIdV1))); } } } @@ -275,8 +283,8 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } private void handleGroupUpdate() { - new UpdateSignalGroupTask(this, groupToUpdate.get().id, avatarBmp, - getGroupName(), getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new UpdateSignalGroupV1Task(this, groupToUpdate.get().id, avatarBmp, + getGroupName(), getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } private void handleOpenConversation(long threadId, Recipient recipient) { @@ -289,7 +297,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } private SelectedRecipientsAdapter getAdapter() { - return (SelectedRecipientsAdapter)lv.getAdapter(); + return (SelectedRecipientsAdapter) listView.getAdapter(); } private @Nullable String getGroupName() { @@ -451,11 +459,11 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } } - private static class UpdateSignalGroupTask extends SignalGroupTask { - private final GroupId groupId; + private static class UpdateSignalGroupV1Task extends SignalGroupTask { + private final GroupId.V1 groupId; - public UpdateSignalGroupTask(GroupCreateActivity activity, GroupId groupId, - Bitmap avatar, String name, Set members) + UpdateSignalGroupV1Task(GroupCreateActivity activity, GroupId.V1 groupId, + Bitmap avatar, String name, Set members) { super(activity, avatar, name, members); this.groupId = groupId; @@ -463,11 +471,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity @Override protected Optional doInBackground(Void... aVoid) { - try { - return Optional.of(GroupManager.updateGroup(activity, groupId, members, avatar, name)); - } catch (InvalidNumberException e) { - return Optional.absent(); - } + return Optional.fromNullable(GroupManager.updateGroup(activity, groupId, members, BitmapUtil.toByteArray(avatar), name)); } @Override @@ -543,7 +547,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } } - private static class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask> { + private static class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask> { private GroupCreateActivity activity; public FillExistingGroupInfoAsyncTask(GroupCreateActivity activity) { @@ -554,7 +558,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } @Override - protected Optional doInBackground(GroupId... groupIds) { + protected Optional doInBackground(GroupId.V1... groupIds) { final GroupDatabase db = DatabaseFactory.getGroupDatabase(activity); final List recipients = db.getGroupMembers(groupIds[0], GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF); final Optional group = db.getGroup(groupIds[0]); @@ -591,7 +595,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(activity, group.get().recipients); adapter.setOnRecipientDeletedListener(activity); - activity.lv.setAdapter(adapter); + activity.listView.setAdapter(adapter); activity.updateViewState(); } } @@ -608,13 +612,13 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } private static class GroupData { - GroupId id; + GroupId.V1 id; Set recipients; Bitmap avatarBmp; byte[] avatarBytes; String name; - GroupData(GroupId id, Set recipients, Bitmap avatarBmp, byte[] avatarBytes, String name) { + GroupData(GroupId.V1 id, Set recipients, Bitmap avatarBmp, byte[] avatarBytes, String name) { this.id = id; this.recipients = recipients; this.avatarBmp = avatarBmp; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index d3d6758bc0..691acbf4f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -195,7 +195,6 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.profiles.GroupShareProfileView; -import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment; import org.thoughtcrime.securesms.recipients.LiveRecipient; @@ -855,7 +854,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true; case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true; case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true; - case R.id.menu_edit_group: handleEditPushGroup(); return true; + case R.id.menu_edit_group: handleEditPushGroupV1(); return true; case R.id.menu_manage_group: handleManagePushGroup(); return true; case R.id.menu_pending_members: handlePendingMembers(); return true; case R.id.menu_leave: handleLeavePushGroup(); return true; @@ -1151,10 +1150,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity this::initializeEnabledCheck); } - private void handleEditPushGroup() { - Intent intent = new Intent(ConversationActivity.this, GroupCreateActivity.class); - intent.putExtra(GroupCreateActivity.GROUP_ID_EXTRA, recipient.get().requireGroupId().toString()); - startActivityForResult(intent, GROUP_EDIT); + private void handleEditPushGroupV1() { + startActivityForResult(GroupCreateActivity.newEditGroupIntent(ConversationActivity.this, recipient.get().requireGroupId().requireV1()), GROUP_EDIT); } private void handleManagePushGroup() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java index 267b14003b..9a37b7094d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java @@ -7,7 +7,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; -import org.signal.zkgroup.VerificationFailedException; import org.signal.zkgroup.groups.UuidCiphertext; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; @@ -15,10 +14,7 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException; -import org.whispersystems.signalservice.api.util.InvalidNumberException; import java.io.IOException; import java.util.Collection; @@ -45,26 +41,31 @@ public final class GroupManager { public static GroupActionResult updateGroup(@NonNull Context context, @NonNull GroupId groupId, @Nullable byte[] avatar, + boolean avatarChanged, @Nullable String name) - throws InvalidNumberException + throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, GroupChangeBusyException { + if (groupId.isV2()) { + try (GroupManagerV2.GroupEditor edit = new GroupManagerV2(context).edit(groupId.requireV2())) { + return edit.updateGroupTitleAndAvatar(name, avatarChanged ? avatar : null); + } + } else { + List members = DatabaseFactory.getGroupDatabase(context) + .getGroupMembers(groupId, GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF); - List members = DatabaseFactory.getGroupDatabase(context) - .getGroupMembers(groupId, GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF); - - return GroupManagerV1.updateGroup(context, groupId, getMemberIds(members), avatar, name); + return updateGroup(context, groupId.requireV1(), new HashSet<>(members), avatar, name); + } } - public static GroupActionResult updateGroup(@NonNull Context context, - @NonNull GroupId groupId, - @NonNull Set members, - @Nullable Bitmap avatar, - @Nullable String name) - throws InvalidNumberException + public static @Nullable GroupActionResult updateGroup(@NonNull Context context, + @NonNull GroupId.V1 groupId, + @NonNull Set members, + @Nullable byte[] avatar, + @Nullable String name) { Set addresses = getMemberIds(members); - return GroupManagerV1.updateGroup(context, groupId, addresses, BitmapUtil.toByteArray(avatar), name); + return GroupManagerV1.updateGroup(context, groupId, addresses, avatar, name); } private static Set getMemberIds(Collection recipients) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileRepository.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileRepository.java index ead215b7bd..4bf247dabc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileRepository.java @@ -15,7 +15,7 @@ interface EditProfileRepository { void getCurrentDisplayName(@NonNull Consumer displayNameConsumer); - void uploadProfile(@NonNull ProfileName profileName, @NonNull String displayName, @Nullable byte[] avatar, @NonNull Consumer uploadResultConsumer); + void uploadProfile(@NonNull ProfileName profileName, @Nullable String displayName, @Nullable byte[] avatar, boolean avatarChanged, @NonNull Consumer uploadResultConsumer); void getCurrentUsername(@NonNull Consumer> callback); diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileViewModel.java index e46d279161..db00afde20 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileViewModel.java @@ -1,7 +1,5 @@ package org.thoughtcrime.securesms.profiles.edit; -import android.view.animation.Transformation; - import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -17,6 +15,8 @@ import org.thoughtcrime.securesms.profiles.ProfileName; import org.thoughtcrime.securesms.util.livedata.LiveDataPair; import org.whispersystems.libsignal.util.guava.Optional; +import java.util.Objects; + class EditProfileViewModel extends ViewModel { private final MutableLiveData givenName = new MutableLiveData<>(); @@ -24,7 +24,9 @@ class EditProfileViewModel extends ViewModel { private final LiveData internalProfileName = Transformations.map(new LiveDataPair<>(givenName, familyName), pair -> ProfileName.fromParts(pair.first(), pair.second())); private final MutableLiveData internalAvatar = new MutableLiveData<>(); + private final MutableLiveData originalAvatar = new MutableLiveData<>(); private final MutableLiveData> internalUsername = new MutableLiveData<>(); + private final MutableLiveData originalDisplayName = new MutableLiveData<>(); private final LiveData isFormValid = Transformations.map(givenName, name -> !name.isEmpty()); private final EditProfileRepository repository; private final GroupId groupId; @@ -37,7 +39,10 @@ class EditProfileViewModel extends ViewModel { if (!hasInstanceState) { if (groupId != null) { - repository.getCurrentDisplayName(givenName::setValue); + repository.getCurrentDisplayName(value -> { + givenName.setValue(value); + originalDisplayName.setValue(value); + }); } else { repository.getCurrentProfileName(name -> { givenName.setValue(name.getGivenName()); @@ -45,7 +50,10 @@ class EditProfileViewModel extends ViewModel { }); } - repository.getCurrentAvatar(internalAvatar::setValue); + repository.getCurrentAvatar(value -> { + internalAvatar.setValue(value); + originalAvatar.setValue(value); + }); } } @@ -110,7 +118,15 @@ class EditProfileViewModel extends ViewModel { return; } - repository.uploadProfile(profileName, displayName, internalAvatar.getValue(), uploadResultConsumer); + byte[] oldAvatar = originalAvatar.getValue(); + byte[] newAvatar = internalAvatar.getValue(); + String oldDisplayName = isGroup() ? originalDisplayName.getValue() : null; + + repository.uploadProfile(profileName, + Objects.equals(oldDisplayName, displayName) ? null : displayName, + newAvatar, + oldAvatar != newAvatar, + uploadResultConsumer); } static class Factory implements ViewModelProvider.Factory { diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditPushGroupProfileRepository.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditPushGroupProfileRepository.java index 88bc36c37b..c3ee820d73 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditPushGroupProfileRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditPushGroupProfileRepository.java @@ -8,8 +8,12 @@ import androidx.annotation.WorkerThread; import androidx.core.util.Consumer; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.groups.GroupChangeBusyException; +import org.thoughtcrime.securesms.groups.GroupChangeFailedException; import org.thoughtcrime.securesms.groups.GroupId; +import org.thoughtcrime.securesms.groups.GroupInsufficientRightsException; import org.thoughtcrime.securesms.groups.GroupManager; +import org.thoughtcrime.securesms.groups.GroupNotAMemberException; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.ProfileName; @@ -18,7 +22,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.util.InvalidNumberException; import java.io.IOException; @@ -64,16 +67,17 @@ class EditPushGroupProfileRepository implements EditProfileRepository { @Override public void uploadProfile(@NonNull ProfileName profileName, - @NonNull String displayName, + @Nullable String displayName, @Nullable byte[] avatar, + boolean avatarChanged, @NonNull Consumer uploadResultConsumer) { SimpleTask.run(() -> { try { - GroupManager.updateGroup(context, groupId, avatar, displayName); + GroupManager.updateGroup(context, groupId, avatar, avatarChanged, displayName); return UploadResult.SUCCESS; - } catch (InvalidNumberException e) { + } catch (GroupChangeFailedException | GroupInsufficientRightsException | IOException | GroupNotAMemberException | GroupChangeBusyException e) { return UploadResult.ERROR_IO; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditSelfProfileRepository.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditSelfProfileRepository.java index 9e504e1764..1c52f46a1c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditSelfProfileRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditSelfProfileRepository.java @@ -108,14 +108,16 @@ class EditSelfProfileRepository implements EditProfileRepository { } @Override - public void uploadProfile(@NonNull ProfileName profileName, @NonNull String displayName, @Nullable byte[] avatar, @NonNull Consumer uploadResultConsumer) { + public void uploadProfile(@NonNull ProfileName profileName, @Nullable String displayName, @Nullable byte[] avatar, boolean avatarChanged, @NonNull Consumer uploadResultConsumer) { SimpleTask.run(() -> { DatabaseFactory.getRecipientDatabase(context).setProfileName(Recipient.self().getId(), profileName); - try { - AvatarHelper.setAvatar(context, Recipient.self().getId(), avatar != null ? new ByteArrayInputStream(avatar) : null); - } catch (IOException e) { - return UploadResult.ERROR_IO; + if (avatarChanged) { + try { + AvatarHelper.setAvatar(context, Recipient.self().getId(), avatar != null ? new ByteArrayInputStream(avatar) : null); + } catch (IOException e) { + return UploadResult.ERROR_IO; + } } ApplicationDependencies.getJobManager()