diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index b47ac43c6..0b2b98b85 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -1,6 +1,11 @@
name: Android CI
-on: [push, pull_request]
+on:
+ pull_request:
+ push:
+ branches:
+ - 'master'
+ - '4.**'
jobs:
build:
diff --git a/app/build.gradle b/app/build.gradle
index 76dd51f74..4ef2a8dba 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -80,8 +80,8 @@ protobuf {
}
}
-def canonicalVersionCode = 613
-def canonicalVersionName = "4.57.2"
+def canonicalVersionCode = 614
+def canonicalVersionName = "4.58.0"
def postFixSize = 10
def abiPostFix = ['universal' : 0,
@@ -160,6 +160,7 @@ android {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/proguard/androidx-annotations.pro'
+ exclude 'lib/*/libzkgroup.so' // TODO: GV2 Remove line to include .so when used
}
buildTypes {
@@ -268,8 +269,10 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0'
- implementation "androidx.camera:camera-core:1.0.0-alpha06"
- implementation "androidx.camera:camera-camera2:1.0.0-alpha06"
+ implementation "androidx.camera:camera-core:1.0.0-beta01"
+ implementation "androidx.camera:camera-camera2:1.0.0-beta01"
+ implementation "androidx.camera:camera-lifecycle:1.0.0-beta01"
+ implementation "androidx.concurrent:concurrent-futures:1.0.0"
implementation('com.google.firebase:firebase-messaging:17.3.4') {
exclude group: 'com.google.firebase', module: 'firebase-core'
@@ -287,10 +290,11 @@ dependencies {
implementation 'org.signal:aesgcmprovider:0.0.3'
implementation project(':libsignal-service')
+ implementation 'org.signal:zkgroup-android:0.4.1'
implementation 'org.signal:argon2:13.1@aar'
- implementation 'org.signal:ringrtc-android:1.0.2'
+ implementation 'org.signal:ringrtc-android:1.1.0'
implementation "me.leolin:ShortcutBadger:1.1.16"
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
@@ -350,7 +354,9 @@ dependencies {
testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1'
testImplementation 'androidx.test:core:1.2.0'
- testImplementation 'org.robolectric:robolectric:4.2'
+ testImplementation ('org.robolectric:robolectric:4.2') {
+ exclude group: 'com.google.protobuf', module: 'protobuf-java'
+ }
testImplementation 'org.robolectric:shadows-multidex:4.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1b6f8620b..519568c40 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms">
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
index 29056202d..6ded0a044 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
@@ -19,7 +19,7 @@ package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import androidx.appcompat.app.AppCompatDelegate;
-import androidx.camera.camera2.Camera2AppConfig;
+import androidx.camera.camera2.Camera2Config;
import androidx.camera.core.CameraX;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
@@ -71,6 +71,7 @@ import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
+import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
@@ -136,7 +137,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
FeatureFlags.init();
NotificationChannels.create(this);
RefreshPreKeysJob.scheduleIfNecessary();
- StorageSyncJob.scheduleIfNecessary();
+ StorageSyncHelper.scheduleRoutineSync();
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
if (Build.VERSION.SDK_INT < 21) {
@@ -385,7 +386,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
if (CameraXUtil.isSupported()) {
new Thread(() -> {
try {
- CameraX.init(this, Camera2AppConfig.create(this));
+ CameraX.initialize(this, Camera2Config.defaultConfig());
} catch (Throwable t) {
Log.w(TAG, "Failed to initialize CameraX.");
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/BasicIntroFragment.java b/app/src/main/java/org/thoughtcrime/securesms/BasicIntroFragment.java
deleted file mode 100644
index d73a67802..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/BasicIntroFragment.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.thoughtcrime.securesms;
-
-import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-public class BasicIntroFragment extends Fragment {
-
- private static final String ARG_DRAWABLE = "drawable";
- private static final String ARG_TEXT = "text";
- private static final String ARG_SUBTEXT = "subtext";
-
- private int drawable;
- private int text;
- private int subtext;
-
- public static BasicIntroFragment newInstance(int drawable, int text, int subtext) {
- BasicIntroFragment fragment = new BasicIntroFragment();
- Bundle args = new Bundle();
- args.putInt(ARG_DRAWABLE, drawable);
- args.putInt(ARG_TEXT, text);
- args.putInt(ARG_SUBTEXT, subtext);
- fragment.setArguments(args);
- return fragment;
- }
-
- public BasicIntroFragment() {}
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (getArguments() != null) {
- drawable = getArguments().getInt(ARG_DRAWABLE);
- text = getArguments().getInt(ARG_TEXT);
- subtext = getArguments().getInt(ARG_SUBTEXT);
- }
- }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.color_fragment, container, false);
-
- ((ImageView)v.findViewById(R.id.watermark)).setImageResource(drawable);
- ((TextView)v.findViewById(R.id.blurb)).setText(text);
- ((TextView)v.findViewById(R.id.subblurb)).setText(subtext);
-
- return v;
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ExperienceUpgradeActivity.java b/app/src/main/java/org/thoughtcrime/securesms/ExperienceUpgradeActivity.java
deleted file mode 100644
index 71f412d8d..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/ExperienceUpgradeActivity.java
+++ /dev/null
@@ -1,322 +0,0 @@
-package org.thoughtcrime.securesms;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-import androidx.core.app.NotificationCompat;
-import androidx.viewpager.widget.ViewPager;
-
-import com.melnykov.fab.FloatingActionButton;
-
-import org.thoughtcrime.securesms.IntroPagerAdapter.IntroPage;
-import org.thoughtcrime.securesms.experienceupgrades.StickersIntroFragment;
-import org.thoughtcrime.securesms.logging.Log;
-import org.thoughtcrime.securesms.notifications.NotificationChannels;
-import org.thoughtcrime.securesms.notifications.NotificationIds;
-import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
-import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
-import org.thoughtcrime.securesms.util.DynamicTheme;
-import org.thoughtcrime.securesms.util.ServiceUtil;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.thoughtcrime.securesms.util.Util;
-import org.thoughtcrime.securesms.util.ViewUtil;
-import org.whispersystems.libsignal.util.guava.Optional;
-
-import java.util.Collections;
-import java.util.List;
-
-public class ExperienceUpgradeActivity extends BaseActionBarActivity
- implements TypingIndicatorIntroFragment.Controller,
- LinkPreviewsIntroFragment.Controller,
- StickersIntroFragment.Controller
-{
- private static final String TAG = ExperienceUpgradeActivity.class.getSimpleName();
- private static final String DISMISS_ACTION = "org.thoughtcrime.securesms.ExperienceUpgradeActivity.DISMISS_ACTION";
-
- private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
-
- private enum ExperienceUpgrade {
- SIGNAL_REBRANDING(157,
- new IntroPage(0xFF2090EA,
- BasicIntroFragment.newInstance(R.drawable.splash_logo,
- R.string.ExperienceUpgradeActivity_welcome_to_signal_dgaf,
- R.string.ExperienceUpgradeActivity_textsecure_is_now_called_signal)),
- R.string.ExperienceUpgradeActivity_welcome_to_signal_excited,
- R.string.ExperienceUpgradeActivity_textsecure_is_now_signal,
- R.string.ExperienceUpgradeActivity_textsecure_is_now_signal_long,
- null,
- false),
- VIDEO_CALLS(245,
- new IntroPage(0xFF2090EA,
- BasicIntroFragment.newInstance(R.drawable.video_splash,
- R.string.ExperienceUpgradeActivity_say_hello_to_video_calls,
- R.string.ExperienceUpgradeActivity_signal_now_supports_secure_video_calls)),
- R.string.ExperienceUpgradeActivity_say_hello_to_video_calls,
- R.string.ExperienceUpgradeActivity_signal_now_supports_secure_video_calling,
- R.string.ExperienceUpgradeActivity_signal_now_supports_secure_video_calling_long,
- null,
- false),
- PROFILES(286,
- new IntroPage(0xFF2090EA,
- BasicIntroFragment.newInstance(R.drawable.profile_splash,
- R.string.ExperienceUpgradeActivity_ready_for_your_closeup,
- R.string.ExperienceUpgradeActivity_now_you_can_share_a_profile_photo_and_name_with_friends_on_signal)),
- R.string.ExperienceUpgradeActivity_signal_profiles_are_here,
- R.string.ExperienceUpgradeActivity_now_you_can_share_a_profile_photo_and_name_with_friends_on_signal,
- R.string.ExperienceUpgradeActivity_now_you_can_share_a_profile_photo_and_name_with_friends_on_signal,
- EditProfileActivity.class,
- false),
- READ_RECEIPTS(299,
- new IntroPage(0xFF2090EA,
- ReadReceiptsIntroFragment.newInstance()),
- R.string.experience_upgrade_preference_fragment__read_receipts_are_here,
- R.string.experience_upgrade_preference_fragment__optionally_see_and_share_when_messages_have_been_read,
- R.string.experience_upgrade_preference_fragment__optionally_see_and_share_when_messages_have_been_read,
- null,
- false),
- TYPING_INDICATORS(432,
- new IntroPage(0xFF2090EA,
- TypingIndicatorIntroFragment.newInstance()),
- R.string.ExperienceUpgradeActivity_introducing_typing_indicators,
- R.string.ExperienceUpgradeActivity_now_you_can_optionally_see_and_share_when_messages_are_being_typed,
- R.string.ExperienceUpgradeActivity_now_you_can_optionally_see_and_share_when_messages_are_being_typed,
- null,
- true),
- LINK_PREVIEWS(449,
- new IntroPage(0xFF2090EA, LinkPreviewsIntroFragment.newInstance()),
- R.string.ExperienceUpgradeActivity_introducing_link_previews,
- R.string.ExperienceUpgradeActivity_optional_link_previews_are_now_supported,
- R.string.ExperienceUpgradeActivity_optional_link_previews_are_now_supported,
- null,
- true),
- STICKERS(580,
- new IntroPage(0xFF2090EA, StickersIntroFragment.newInstance()),
- R.string.ExperienceUpgradeActivity_introducing_stickers,
- R.string.ExperienceUpgradeActivity_why_use_words_when_you_can_use_stickers,
- R.string.ExperienceUpgradeActivity_why_use_words_when_you_can_use_stickers,
- null,
- true);
-
- private int version;
- private List pages;
- private @StringRes int notificationTitle;
- private @StringRes int notificationText;
- private @StringRes int notificationBigText;
- private @Nullable Class nextIntent;
- private boolean handlesNavigation;
-
- ExperienceUpgrade(int version,
- @NonNull List pages,
- @StringRes int notificationTitle,
- @StringRes int notificationText,
- @StringRes int notificationBigText,
- @Nullable Class nextIntent,
- boolean handlesNavigation)
- {
- this.version = version;
- this.pages = pages;
- this.notificationTitle = notificationTitle;
- this.notificationText = notificationText;
- this.notificationBigText = notificationBigText;
- this.nextIntent = nextIntent;
- this.handlesNavigation = handlesNavigation;
- }
-
- ExperienceUpgrade(int version,
- @NonNull IntroPage page,
- @StringRes int notificationTitle,
- @StringRes int notificationText,
- @StringRes int notificationBigText,
- @Nullable Class nextIntent,
- boolean handlesNavigation)
- {
- this(version, Collections.singletonList(page), notificationTitle, notificationText, notificationBigText, nextIntent, handlesNavigation);
- }
-
- public int getVersion() {
- return version;
- }
-
- public List getPages() {
- return pages;
- }
-
- public IntroPage getPage(int i) {
- return pages.get(i);
- }
-
- public int getNotificationTitle() {
- return notificationTitle;
- }
-
- public int getNotificationText() {
- return notificationText;
- }
-
- public int getNotificationBigText() {
- return notificationBigText;
- }
-
- public boolean handlesNavigation() {
- return handlesNavigation;
- }
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- dynamicTheme.onCreate(this);
-
- final Optional upgrade = getExperienceUpgrade(this);
- if (!upgrade.isPresent()) {
- onContinue(upgrade);
- return;
- }
-
- setContentView(R.layout.experience_upgrade_activity);
- final ViewPager pager = ViewUtil.findById(this, R.id.pager);
- final FloatingActionButton fab = ViewUtil.findById(this, R.id.fab);
-
- pager.setAdapter(new IntroPagerAdapter(getSupportFragmentManager(), upgrade.get().getPages()));
-
- if (upgrade.get().handlesNavigation()) {
- fab.setVisibility(View.GONE);
- } else {
- fab.setVisibility(View.VISIBLE);
- fab.setOnClickListener(v -> onContinue(upgrade));
- }
-
- getWindow().setBackgroundDrawable(new ColorDrawable(upgrade.get().getPage(0).backgroundColor));
- ServiceUtil.getNotificationManager(this).cancel(NotificationIds.EXPERIENCE_UPGRADE);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- dynamicTheme.onResume(this);
- }
-
- private void onContinue(Optional seenUpgrade) {
- ServiceUtil.getNotificationManager(this).cancel(NotificationIds.EXPERIENCE_UPGRADE);
- int latestVersion = seenUpgrade.isPresent() ? seenUpgrade.get().getVersion()
- : Util.getCanonicalVersionCode();
- TextSecurePreferences.setLastExperienceVersionCode(this, latestVersion);
- if (seenUpgrade.isPresent() && seenUpgrade.get().nextIntent != null) {
- Intent intent = new Intent(this, seenUpgrade.get().nextIntent);
- // TODO [greyson] Navigation
- Intent nextIntent = new Intent(this, MainActivity.class);
- intent.putExtra("next_intent", nextIntent);
- startActivity(intent);
- } else {
- startActivity(getIntent().getParcelableExtra("next_intent"));
- }
-
- finish();
- }
-
- public static boolean isUpdate(Context context) {
- return getExperienceUpgrade(context).isPresent();
- }
-
- public static Optional getExperienceUpgrade(Context context) {
- final int currentVersionCode = Util.getCanonicalVersionCode();
- final int lastSeenVersion = TextSecurePreferences.getLastExperienceVersionCode(context);
- Log.i(TAG, "getExperienceUpgrade(" + lastSeenVersion + ")");
-
- if (lastSeenVersion >= currentVersionCode) {
- TextSecurePreferences.setLastExperienceVersionCode(context, currentVersionCode);
- return Optional.absent();
- }
-
- Optional eligibleUpgrade = Optional.absent();
- for (ExperienceUpgrade upgrade : ExperienceUpgrade.values()) {
- if (lastSeenVersion < upgrade.getVersion()) eligibleUpgrade = Optional.of(upgrade);
- }
-
- return eligibleUpgrade;
- }
-
- @Override
- public void onTypingIndicatorsFinished() {
- onContinue(Optional.of(ExperienceUpgrade.TYPING_INDICATORS));
- }
-
- @Override
- public void onLinkPreviewsFinished() {
- onContinue(Optional.of(ExperienceUpgrade.LINK_PREVIEWS));
- }
-
- @Override
- public void onStickersFinished() {
- onContinue(Optional.of(ExperienceUpgrade.STICKERS));
- }
-
- public static class AppUpgradeReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction()) &&
- intent.getData().getSchemeSpecificPart().equals(context.getPackageName()))
- {
- if (TextSecurePreferences.getLastExperienceVersionCode(context) < 339 &&
- !TextSecurePreferences.isPasswordDisabled(context))
- {
- Notification notification = new NotificationCompat.Builder(context, NotificationChannels.OTHER)
- .setSmallIcon(R.drawable.icon_notification)
- .setColor(context.getResources().getColor(R.color.signal_primary))
- .setContentTitle(context.getString(R.string.ExperienceUpgradeActivity_unlock_to_complete_update))
- .setContentText(context.getString(R.string.ExperienceUpgradeActivity_please_unlock_signal_to_complete_update))
- .setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.ExperienceUpgradeActivity_please_unlock_signal_to_complete_update)))
- .setAutoCancel(true)
- .setContentIntent(PendingIntent.getActivity(context, 0,
- context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()),
- PendingIntent.FLAG_UPDATE_CURRENT))
- .build();
-
- ServiceUtil.getNotificationManager(context).notify(NotificationIds.EXPERIENCE_UPGRADE, notification);
- }
-
- Optional experienceUpgrade = getExperienceUpgrade(context);
-
- if (!experienceUpgrade.isPresent()) {
- return;
- }
-
- if (experienceUpgrade.get().getVersion() == TextSecurePreferences.getExperienceDismissedVersionCode(context)) {
- return;
- }
-
- Intent targetIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
- Intent dismissIntent = new Intent(context, AppUpgradeReceiver.class);
- dismissIntent.setAction(DISMISS_ACTION);
-
- Notification notification = new NotificationCompat.Builder(context, NotificationChannels.OTHER)
- .setSmallIcon(R.drawable.icon_notification)
- .setColor(context.getResources().getColor(R.color.signal_primary))
- .setContentTitle(context.getString(experienceUpgrade.get().getNotificationTitle()))
- .setContentText(context.getString(experienceUpgrade.get().getNotificationText()))
- .setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(experienceUpgrade.get().getNotificationBigText())))
- .setAutoCancel(true)
- .setContentIntent(PendingIntent.getActivity(context, 0,
- targetIntent,
- PendingIntent.FLAG_UPDATE_CURRENT))
-
- .setDeleteIntent(PendingIntent.getBroadcast(context, 0,
- dismissIntent,
- PendingIntent.FLAG_UPDATE_CURRENT))
- .build();
- ServiceUtil.getNotificationManager(context).notify(NotificationIds.EXPERIENCE_UPGRADE, notification);
- } else if (DISMISS_ACTION.equals(intent.getAction())) {
- TextSecurePreferences.setExperienceDismissedVersionCode(context, Util.getCanonicalVersionCode());
- }
- }
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java b/app/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java
index bd5f68dba..f5a827f64 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java
@@ -54,6 +54,7 @@ import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
+import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult;
import org.thoughtcrime.securesms.logging.Log;
@@ -208,7 +209,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
}
private void initializeExistingGroup() {
- final String groupId = getIntent().getStringExtra(GROUP_ID_EXTRA);
+ final GroupId groupId = GroupId.parseNullable(getIntent().getStringExtra(GROUP_ID_EXTRA));
if (groupId != null) {
new FillExistingGroupInfoAsyncTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, groupId);
@@ -361,7 +362,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
}
memberAddresses.add(Recipient.self().getId());
- String groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateGroupForMembers(memberAddresses, true);
+ GroupId groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateGroupForMembers(memberAddresses, true);
RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(activity).getOrInsertFromGroupId(groupId);
Recipient groupRecipient = Recipient.resolved(groupRecipientId);
long threadId = DatabaseFactory.getThreadDatabase(activity).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.DEFAULT);
@@ -443,9 +444,9 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
}
private static class UpdateSignalGroupTask extends SignalGroupTask {
- private String groupId;
+ private final GroupId groupId;
- public UpdateSignalGroupTask(GroupCreateActivity activity, String groupId,
+ public UpdateSignalGroupTask(GroupCreateActivity activity, GroupId groupId,
Bitmap avatar, String name, Set members)
{
super(activity, avatar, name, members);
@@ -467,7 +468,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
if (!activity.isFinishing()) {
Intent intent = activity.getIntent();
intent.putExtra(GROUP_THREAD_EXTRA, result.get().getThreadId());
- intent.putExtra(GROUP_ID_EXTRA, result.get().getGroupRecipient().requireGroupId());
+ intent.putExtra(GROUP_ID_EXTRA, result.get().getGroupRecipient().requireGroupId().toString());
activity.setResult(RESULT_OK, intent);
activity.finish();
}
@@ -534,7 +535,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) {
@@ -545,7 +546,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
}
@Override
- protected Optional doInBackground(String... groupIds) {
+ protected Optional doInBackground(GroupId... groupIds) {
final GroupDatabase db = DatabaseFactory.getGroupDatabase(activity);
final List recipients = db.getGroupMembers(groupIds[0], false);
final Optional group = db.getGroup(groupIds[0]);
@@ -593,13 +594,13 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
}
private static class GroupData {
- String id;
+ GroupId id;
Set recipients;
Bitmap avatarBmp;
byte[] avatarBytes;
String name;
- public GroupData(String id, Set recipients, Bitmap avatarBmp, byte[] avatarBytes, String name) {
+ GroupData(GroupId 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/IntroPagerAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/IntroPagerAdapter.java
deleted file mode 100644
index a176c28bb..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/IntroPagerAdapter.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.thoughtcrime.securesms;
-
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentStatePagerAdapter;
-
-import java.util.List;
-
-public class IntroPagerAdapter extends FragmentStatePagerAdapter {
-
- public static class IntroPage {
- final int backgroundColor;
- final Fragment fragment;
-
- public IntroPage(int backgroundColor, Fragment fragment) {
- this.backgroundColor = backgroundColor;
- this.fragment = fragment;
- }
- }
-
- private List pages;
-
- public IntroPagerAdapter(FragmentManager fm, List pages) {
- super(fm);
- this.pages = pages;
- }
-
- @Override
- public Fragment getItem(int i) {
- IntroPage page = pages.get(i);
- return page.fragment;
- }
-
- @Override
- public int getCount() {
- return pages.size();
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java b/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java
index ab4ab8431..088579204 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/InviteActivity.java
@@ -175,17 +175,17 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
}
private void setPrimaryColorsToolbarForSms() {
- primaryToolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.signal_primary));
+ primaryToolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.core_ultramarine));
primaryToolbar.getNavigationIcon().setColorFilter(ThemeUtil.getThemedColor(this, R.attr.conversation_subtitle_color), PorterDuff.Mode.SRC_IN);
primaryToolbar.setTitleTextColor(ThemeUtil.getThemedColor(this, R.attr.conversation_title_color));
if (Build.VERSION.SDK_INT >= 23) {
- getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.signal_primary));
+ getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.core_ultramarine));
WindowUtil.clearLightStatusBar(getWindow());
}
if (Build.VERSION.SDK_INT >= 27) {
- getWindow().setNavigationBarColor(ContextCompat.getColor(this, R.color.signal_primary));
+ getWindow().setNavigationBarColor(ContextCompat.getColor(this, R.color.core_ultramarine));
WindowUtil.clearLightNavigationBar(getWindow());
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/LinkPreviewsIntroFragment.java b/app/src/main/java/org/thoughtcrime/securesms/LinkPreviewsIntroFragment.java
deleted file mode 100644
index 3ebb914b6..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/LinkPreviewsIntroFragment.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.thoughtcrime.securesms;
-
-
-import android.content.Context;
-import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-
-public class LinkPreviewsIntroFragment extends Fragment {
-
- private Controller controller;
-
- public static LinkPreviewsIntroFragment newInstance() {
- LinkPreviewsIntroFragment fragment = new LinkPreviewsIntroFragment();
- Bundle args = new Bundle();
- fragment.setArguments(args);
- return fragment;
- }
-
- public LinkPreviewsIntroFragment() {}
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
-
- if (!(getActivity() instanceof Controller)) {
- throw new IllegalStateException("Parent activity must implement the Controller interface.");
- }
-
- controller = (Controller) getActivity();
- }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.experience_upgrade_link_previews_fragment, container, false);
-
- view.findViewById(R.id.experience_ok_button).setOnClickListener(v -> {
- ApplicationDependencies.getJobManager().add(new MultiDeviceConfigurationUpdateJob(TextSecurePreferences.isReadReceiptsEnabled(requireContext()),
- TextSecurePreferences.isTypingIndicatorsEnabled(requireContext()),
- TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(requireContext()),
- TextSecurePreferences.isLinkPreviewsEnabled(requireContext())));
- controller.onLinkPreviewsFinished();
- });
-
- return view;
- }
-
- public interface Controller {
- void onLinkPreviewsFinished();
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java
index 51df05ed2..8a047f071 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java
@@ -238,7 +238,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
EditorInfo.IME_ACTION_DONE);
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
- fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
+ fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
lockScreenButton.setOnClickListener(v -> resumeScreenLock());
}
@@ -358,7 +358,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
handleAuthenticated();
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
- fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
+ fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
}
}).start();
}
@@ -381,7 +381,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
@Override
public void onAnimationEnd(Animation animation) {
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
- fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
+ fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
}
@Override
diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java
index 5daa8d181..0f8cbf89d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java
@@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
+import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.CensorshipUtil;
@@ -39,10 +40,9 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
private static final int STATE_CREATE_PASSPHRASE = 1;
private static final int STATE_PROMPT_PASSPHRASE = 2;
private static final int STATE_UI_BLOCKING_UPGRADE = 3;
- private static final int STATE_EXPERIENCE_UPGRADE = 4;
- private static final int STATE_WELCOME_PUSH_SCREEN = 5;
- private static final int STATE_CREATE_PROFILE_NAME = 6;
- private static final int STATE_CREATE_KBS_PIN = 7;
+ private static final int STATE_WELCOME_PUSH_SCREEN = 4;
+ private static final int STATE_CREATE_PROFILE_NAME = 5;
+ private static final int STATE_CREATE_KBS_PIN = 6;
private SignalServiceNetworkAccess networkAccess;
private BroadcastReceiver clearKeyReceiver;
@@ -157,7 +157,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
case STATE_UI_BLOCKING_UPGRADE: return getUiBlockingUpgradeIntent();
case STATE_WELCOME_PUSH_SCREEN: return getPushRegistrationIntent();
- case STATE_EXPERIENCE_UPGRADE: return getExperienceUpgradeIntent();
case STATE_CREATE_KBS_PIN: return getCreateKbsPinIntent();
case STATE_CREATE_PROFILE_NAME: return getCreateProfileNameIntent();
default: return null;
@@ -173,8 +172,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
return STATE_UI_BLOCKING_UPGRADE;
} else if (!TextSecurePreferences.hasPromptedPushRegistration(this)) {
return STATE_WELCOME_PUSH_SCREEN;
- } else if (ExperienceUpgradeActivity.isUpdate(this)) {
- return STATE_EXPERIENCE_UPGRADE;
} else if (userMustSetProfileName()) {
return STATE_CREATE_PROFILE_NAME;
} else if (userMustSetKbsPin()) {
@@ -191,7 +188,7 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
}
private boolean userMustSetProfileName() {
- return !SignalStore.registrationValues().isRegistrationComplete() && TextSecurePreferences.getProfileName(this) == ProfileName.EMPTY;
+ return !SignalStore.registrationValues().isRegistrationComplete() && Recipient.self().getProfileName() == ProfileName.EMPTY;
}
private Intent getCreatePassphraseIntent() {
@@ -209,10 +206,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
: getPushRegistrationIntent());
}
- private Intent getExperienceUpgradeIntent() {
- return getRoutedIntent(ExperienceUpgradeActivity.class, getIntent());
- }
-
private Intent getPushRegistrationIntent() {
return RegistrationNavigationActivity.newIntentForNewRegistration(this);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ReadReceiptsIntroFragment.java b/app/src/main/java/org/thoughtcrime/securesms/ReadReceiptsIntroFragment.java
deleted file mode 100644
index 3c1daa232..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/ReadReceiptsIntroFragment.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.thoughtcrime.securesms;
-
-
-import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import androidx.appcompat.widget.SwitchCompat;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.thoughtcrime.securesms.util.ViewUtil;
-
-public class ReadReceiptsIntroFragment extends Fragment {
-
- public static ReadReceiptsIntroFragment newInstance() {
- ReadReceiptsIntroFragment fragment = new ReadReceiptsIntroFragment();
- Bundle args = new Bundle();
- fragment.setArguments(args);
- return fragment;
- }
-
- public ReadReceiptsIntroFragment() {}
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.experience_upgrade_preference_fragment, container, false);
- SwitchCompat preference = ViewUtil.findById(v, R.id.preference);
-
- preference.setChecked(TextSecurePreferences.isReadReceiptsEnabled(getContext()));
- preference.setOnCheckedChangeListener((buttonView, isChecked) -> {
- TextSecurePreferences.setReadReceiptsEnabled(getContext(), isChecked);
- ApplicationDependencies.getJobManager()
- .add(new MultiDeviceConfigurationUpdateJob(isChecked,
- TextSecurePreferences.isTypingIndicatorsEnabled(requireContext()),
- TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext()),
- TextSecurePreferences.isLinkPreviewsEnabled(getContext())));
- });
-
- return v;
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/RecipientPreferenceActivity.java b/app/src/main/java/org/thoughtcrime/securesms/RecipientPreferenceActivity.java
index 764d8295a..27221a7fb 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/RecipientPreferenceActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/RecipientPreferenceActivity.java
@@ -221,7 +221,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
}
private void setHeader(@NonNull Recipient recipient) {
- ContactPhoto contactPhoto = recipient.isLocalNumber() ? new ProfileContactPhoto(recipient.getId(), String.valueOf(TextSecurePreferences.getProfileAvatarId(this)))
+ ContactPhoto contactPhoto = recipient.isLocalNumber() ? new ProfileContactPhoto(recipient, recipient.getProfileAvatar())
: recipient.getContactPhoto();
FallbackContactPhoto fallbackPhoto = recipient.isLocalNumber() ? new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_person_large)
: recipient.getFallbackContactPhoto();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/TransportOptions.java b/app/src/main/java/org/thoughtcrime/securesms/TransportOptions.java
index d4ed37c42..5a5b3fae3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/TransportOptions.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/TransportOptions.java
@@ -109,7 +109,7 @@ public class TransportOptions {
public static @NonNull TransportOption getPushTransportOption(@NonNull Context context) {
return new TransportOption(Type.TEXTSECURE,
R.drawable.ic_send_lock_24,
- context.getResources().getColor(R.color.textsecure_primary),
+ context.getResources().getColor(R.color.core_ultramarine),
context.getString(R.string.ConversationActivity_transport_signal),
context.getString(R.string.conversation_activity__type_message_push),
new PushCharacterCalculator());
diff --git a/app/src/main/java/org/thoughtcrime/securesms/TypingIndicatorIntroFragment.java b/app/src/main/java/org/thoughtcrime/securesms/TypingIndicatorIntroFragment.java
deleted file mode 100644
index f04ae2372..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/TypingIndicatorIntroFragment.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.thoughtcrime.securesms;
-
-
-import android.content.Context;
-import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.thoughtcrime.securesms.components.TypingIndicatorView;
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-
-public class TypingIndicatorIntroFragment extends Fragment {
-
- private Controller controller;
-
- public static TypingIndicatorIntroFragment newInstance() {
- TypingIndicatorIntroFragment fragment = new TypingIndicatorIntroFragment();
- Bundle args = new Bundle();
- fragment.setArguments(args);
- return fragment;
- }
-
- public TypingIndicatorIntroFragment() {}
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
-
- if (!(getActivity() instanceof Controller)) {
- throw new IllegalStateException("Parent activity must implement the Controller interface.");
- }
-
- controller = (Controller) getActivity();
- }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.experience_upgrade_typing_indicators_fragment, container, false);
- View yesButton = view.findViewById(R.id.experience_yes_button);
- View noButton = view.findViewById(R.id.experience_no_button);
-
- ((TypingIndicatorView) view.findViewById(R.id.typing_indicator)).startAnimation();
-
- yesButton.setOnClickListener(v -> onButtonClicked(true));
- noButton.setOnClickListener(v -> onButtonClicked(false));
-
- return view;
- }
-
- private void onButtonClicked(boolean typingEnabled) {
- TextSecurePreferences.setTypingIndicatorsEnabled(getContext(), typingEnabled);
- ApplicationDependencies.getJobManager().add(new MultiDeviceConfigurationUpdateJob(TextSecurePreferences.isReadReceiptsEnabled(requireContext()),
- typingEnabled,
- TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext()),
- TextSecurePreferences.isLinkPreviewsEnabled(getContext())));
-
- controller.onTypingIndicatorsFinished();
- }
-
- public interface Controller {
- void onTypingIndicatorsFinished();
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java
index 1f4acd054..ec304aaaa 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java
@@ -77,6 +77,7 @@ import org.thoughtcrime.securesms.qr.ScanningThread;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
+import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.DynamicDarkActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
@@ -605,7 +606,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
remoteIdentity,
isChecked ? VerifiedStatus.VERIFIED :
VerifiedStatus.DEFAULT));
- ApplicationDependencies.getJobManager().add(new StorageSyncJob());
+ StorageSyncHelper.scheduleSyncForDataChange();
IdentityUtil.markIdentityVerified(getActivity(), recipient.get(), isChecked, false);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiUtil.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiUtil.java
new file mode 100644
index 000000000..7a2e06d90
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiUtil.java
@@ -0,0 +1,70 @@
+package org.thoughtcrime.securesms.components.emoji;
+
+import androidx.annotation.NonNull;
+
+import org.whispersystems.libsignal.util.Pair;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public final class EmojiUtil {
+
+ private static final Map VARIATION_MAP = new HashMap<>();
+
+ static {
+ for (EmojiPageModel page : EmojiPages.DATA_PAGES) {
+ for (Emoji emoji : page.getDisplayEmoji()) {
+ for (String variation : emoji.getVariations()) {
+ VARIATION_MAP.put(variation, emoji.getValue());
+ }
+ }
+ }
+ }
+
+ public static final int MAX_EMOJI_LENGTH;
+ static {
+ int max = 0;
+ for (EmojiPageModel page : EmojiPages.DATA_PAGES) {
+ for (String emoji : page.getEmoji()) {
+ max = Math.max(max, emoji.length());
+ }
+ }
+ MAX_EMOJI_LENGTH = max;
+ }
+
+ private EmojiUtil() {}
+
+ /**
+ * This will return all ways we know of expressing a singular emoji. This is to aid in search,
+ * where some platforms may send an emoji we've locally marked as 'obsolete'.
+ */
+ public static @NonNull Set getAllRepresentations(@NonNull String emoji) {
+ Set out = new HashSet<>();
+
+ out.add(emoji);
+
+ for (Pair pair : EmojiPages.OBSOLETE) {
+ if (pair.first().equals(emoji)) {
+ out.add(pair.second());
+ } else if (pair.second().equals(emoji)) {
+ out.add(pair.first());
+ }
+ }
+
+ return out;
+ }
+
+ /**
+ * When provided an emoji that is a skin variation of another, this will return the default yellow
+ * version. This is to aid in search, so using a variation will still find all emojis tagged with
+ * the default version.
+ *
+ * If the emoji has no skin variations, this function will return the original emoji.
+ */
+ public static @NonNull String getCanonicalRepresentation(@NonNull String emoji) {
+ String canonical = VARIATION_MAP.get(emoji);
+ return canonical != null ? canonical : emoji;
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java
index b9450e635..973561cc9 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java
@@ -202,7 +202,7 @@ public class ContactAccessor {
reader = DatabaseFactory.getGroupDatabase(context).getGroupsFilteredByTitle(constraint, true);
while ((record = reader.getNext()) != null) {
- numberList.add(record.getEncodedId());
+ numberList.add(record.getId().toString());
}
} finally {
if (reader != null)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
index 30407e2ac..7b07b2122 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
@@ -235,7 +235,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter Util.getSecretBytes(16);
-
- private static KeyGenerator testKeyGenerator = null;
-
- /**
- * Given the local state of pending storage mutations, this will generate a result that will
- * include that data that needs to be written to the storage service, as well as any changes you
- * need to write back to local storage (like storage keys that might have changed for updated
- * contacts).
- *
- * @param currentManifestVersion What you think the version is locally.
- * @param currentLocalKeys All local keys you have. This assumes that 'inserts' were given keys
- * already, and that deletes still have keys.
- * @param updates Contacts that have been altered.
- * @param inserts Contacts that have been inserted (or newly marked as registered).
- * @param deletes Contacts that are no longer registered.
- *
- * @return If changes need to be written, then it will return those changes. If no changes need
- * to be written, this will return {@link Optional#absent()}.
- */
- public static @NonNull Optional buildStorageUpdatesForLocal(long currentManifestVersion,
- @NonNull List currentLocalKeys,
- @NonNull List updates,
- @NonNull List inserts,
- @NonNull List deletes)
- {
- Set completeKeys = new LinkedHashSet<>(Stream.of(currentLocalKeys).map(ByteBuffer::wrap).toList());
- Set storageInserts = new LinkedHashSet<>();
- Set storageDeletes = new LinkedHashSet<>();
- Map storageKeyUpdates = new HashMap<>();
-
- for (RecipientSettings insert : inserts) {
- storageInserts.add(localToRemoteRecord(insert));
- }
-
- for (RecipientSettings delete : deletes) {
- byte[] key = Objects.requireNonNull(delete.getStorageKey());
- storageDeletes.add(ByteBuffer.wrap(key));
- completeKeys.remove(ByteBuffer.wrap(key));
- }
-
- for (RecipientSettings update : updates) {
- byte[] oldKey = Objects.requireNonNull(update.getStorageKey());
- byte[] newKey = generateKey();
-
- storageInserts.add(localToRemoteRecord(update, newKey));
- storageDeletes.add(ByteBuffer.wrap(oldKey));
- completeKeys.remove(ByteBuffer.wrap(oldKey));
- completeKeys.add(ByteBuffer.wrap(newKey));
- storageKeyUpdates.put(update.getId(), newKey);
- }
-
- if (storageInserts.isEmpty() && storageDeletes.isEmpty()) {
- return Optional.absent();
- } else {
- List contactDeleteBytes = Stream.of(storageDeletes).map(ByteBuffer::array).toList();
- List completeKeysBytes = Stream.of(completeKeys).map(ByteBuffer::array).toList();
- SignalStorageManifest manifest = new SignalStorageManifest(currentManifestVersion + 1, completeKeysBytes);
- WriteOperationResult writeOperationResult = new WriteOperationResult(manifest, new ArrayList<>(storageInserts), contactDeleteBytes);
-
- return Optional.of(new LocalWriteResult(writeOperationResult, storageKeyUpdates));
- }
- }
-
- /**
- * Given a list of all the local and remote keys you know about, this will return a result telling
- * you which keys are exclusively remote and which are exclusively local.
- *
- * @param remoteKeys All remote keys available.
- * @param localKeys All local keys available.
- *
- * @return An object describing which keys are exclusive to the remote data set and which keys are
- * exclusive to the local data set.
- */
- public static @NonNull KeyDifferenceResult findKeyDifference(@NonNull List remoteKeys,
- @NonNull List localKeys)
- {
- Set allRemoteKeys = Stream.of(remoteKeys).map(ByteBuffer::wrap).collect(LinkedHashSet::new, HashSet::add);
- Set allLocalKeys = Stream.of(localKeys).map(ByteBuffer::wrap).collect(LinkedHashSet::new, HashSet::add);
-
- Set remoteOnlyKeys = SetUtil.difference(allRemoteKeys, allLocalKeys);
- Set localOnlyKeys = SetUtil.difference(allLocalKeys, allRemoteKeys);
-
- return new KeyDifferenceResult(Stream.of(remoteOnlyKeys).map(ByteBuffer::array).toList(),
- Stream.of(localOnlyKeys).map(ByteBuffer::array).toList());
- }
-
- /**
- * Given two sets of storage records, this will resolve the data into a set of actions that need
- * to be applied to resolve the differences. This will handle discovering which records between
- * the two collections refer to the same contacts and are actually updates, which are brand new,
- * etc.
- *
- * @param remoteOnlyRecords Records that are only present remotely.
- * @param localOnlyRecords Records that are only present locally.
- *
- * @return A set of actions that should be applied to resolve the conflict.
- */
- public static @NonNull MergeResult resolveConflict(@NonNull Collection remoteOnlyRecords,
- @NonNull Collection localOnlyRecords)
- {
- List remoteOnlyContacts = Stream.of(remoteOnlyRecords).filter(r -> r.getContact().isPresent()).map(r -> r.getContact().get()).toList();
- List localOnlyContacts = Stream.of(localOnlyRecords).filter(r -> r.getContact().isPresent()).map(r -> r.getContact().get()).toList();
-
- List remoteOnlyGroupV1 = Stream.of(remoteOnlyRecords).filter(r -> r.getGroupV1().isPresent()).map(r -> r.getGroupV1().get()).toList();
- List localOnlyGroupV1 = Stream.of(localOnlyRecords).filter(r -> r.getGroupV1().isPresent()).map(r -> r.getGroupV1().get()).toList();
-
- List remoteOnlyUnknowns = Stream.of(remoteOnlyRecords).filter(SignalStorageRecord::isUnknown).toList();
- List localOnlyUnknowns = Stream.of(localOnlyRecords).filter(SignalStorageRecord::isUnknown).toList();
-
- ContactRecordMergeResult contactMergeResult = resolveContactConflict(remoteOnlyContacts, localOnlyContacts);
- GroupV1RecordMergeResult groupV1MergeResult = resolveGroupV1Conflict(remoteOnlyGroupV1, localOnlyGroupV1);
-
- Set remoteInserts = new HashSet<>();
- remoteInserts.addAll(Stream.of(contactMergeResult.remoteInserts).map(SignalStorageRecord::forContact).toList());
- remoteInserts.addAll(Stream.of(groupV1MergeResult.remoteInserts).map(SignalStorageRecord::forGroupV1).toList());
-
- Set remoteUpdates = new HashSet<>();
- remoteUpdates.addAll(Stream.of(contactMergeResult.remoteUpdates)
- .map(c -> new RecordUpdate(SignalStorageRecord.forContact(c.getOld()), SignalStorageRecord.forContact(c.getNew())))
- .toList());
- remoteUpdates.addAll(Stream.of(groupV1MergeResult.remoteUpdates)
- .map(c -> new RecordUpdate(SignalStorageRecord.forGroupV1(c.getOld()), SignalStorageRecord.forGroupV1(c.getNew())))
- .toList());
-
- return new MergeResult(contactMergeResult.localInserts,
- contactMergeResult.localUpdates,
- groupV1MergeResult.localInserts,
- groupV1MergeResult.localUpdates,
- new LinkedHashSet<>(remoteOnlyUnknowns),
- new LinkedHashSet<>(localOnlyUnknowns),
- remoteInserts,
- remoteUpdates);
- }
-
- /**
- * Assumes that the merge result has *not* yet been applied to the local data. That means that
- * this method will handle generating the correct final key set based on the merge result.
- */
- public static @NonNull WriteOperationResult createWriteOperation(long currentManifestVersion,
- @NonNull List currentLocalStorageKeys,
- @NonNull MergeResult mergeResult)
- {
- Set completeKeys = new LinkedHashSet<>(Stream.of(currentLocalStorageKeys).map(ByteBuffer::wrap).toList());
-
- for (SignalContactRecord insert : mergeResult.getLocalContactInserts()) {
- completeKeys.add(ByteBuffer.wrap(insert.getKey()));
- }
-
- for (SignalGroupV1Record insert : mergeResult.getLocalGroupV1Inserts()) {
- completeKeys.add(ByteBuffer.wrap(insert.getKey()));
- }
-
- for (SignalStorageRecord insert : mergeResult.getRemoteInserts()) {
- completeKeys.add(ByteBuffer.wrap(insert.getKey()));
- }
-
- for (SignalStorageRecord insert : mergeResult.getLocalUnknownInserts()) {
- completeKeys.add(ByteBuffer.wrap(insert.getKey()));
- }
-
- for (ContactUpdate update : mergeResult.getLocalContactUpdates()) {
- completeKeys.remove(ByteBuffer.wrap(update.getOld().getKey()));
- completeKeys.add(ByteBuffer.wrap(update.getNew().getKey()));
- }
-
- for (GroupV1Update update : mergeResult.getLocalGroupV1Updates()) {
- completeKeys.remove(ByteBuffer.wrap(update.getOld().getKey()));
- completeKeys.add(ByteBuffer.wrap(update.getNew().getKey()));
- }
-
- for (RecordUpdate update : mergeResult.getRemoteUpdates()) {
- completeKeys.remove(ByteBuffer.wrap(update.getOld().getKey()));
- completeKeys.add(ByteBuffer.wrap(update.getNew().getKey()));
- }
-
- SignalStorageManifest manifest = new SignalStorageManifest(currentManifestVersion + 1, Stream.of(completeKeys).map(ByteBuffer::array).toList());
-
- List inserts = new ArrayList<>();
- inserts.addAll(mergeResult.getRemoteInserts());
- inserts.addAll(Stream.of(mergeResult.getRemoteUpdates()).map(RecordUpdate::getNew).toList());
-
- List deletes = Stream.of(mergeResult.getRemoteUpdates()).map(RecordUpdate::getOld).map(SignalStorageRecord::getKey).toList();
-
- return new WriteOperationResult(manifest, inserts, deletes);
- }
-
- public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings) {
- if (settings.getStorageKey() == null) {
- throw new AssertionError("Must have a storage key!");
- }
-
- return localToRemoteRecord(settings, settings.getStorageKey());
- }
-
- public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings, @NonNull byte[] key) {
- if (settings.getGroupType() == RecipientDatabase.GroupType.NONE) {
- return SignalStorageRecord.forContact(localToRemoteContact(settings, key));
- } else if (settings.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V1) {
- return SignalStorageRecord.forGroupV1(localToRemoteGroupV1(settings, key));
- } else {
- throw new AssertionError("Unsupported type!");
- }
- }
-
- private static @NonNull SignalContactRecord localToRemoteContact(@NonNull RecipientSettings recipient, byte[] storageKey) {
- if (recipient.getUuid() == null && recipient.getE164() == null) {
- throw new AssertionError("Must have either a UUID or a phone number!");
- }
-
- return new SignalContactRecord.Builder(storageKey, new SignalServiceAddress(recipient.getUuid(), recipient.getE164()))
- .setProfileKey(recipient.getProfileKey())
- .setGivenName(recipient.getProfileName().getGivenName())
- .setFamilyName(recipient.getProfileName().getFamilyName())
- .setBlocked(recipient.isBlocked())
- .setProfileSharingEnabled(recipient.isProfileSharing())
- .setIdentityKey(recipient.getIdentityKey())
- .setIdentityState(localToRemoteIdentityState(recipient.getIdentityStatus()))
- .build();
- }
-
- private static @NonNull SignalGroupV1Record localToRemoteGroupV1(@NonNull RecipientSettings recipient, byte[] storageKey) {
- if (recipient.getGroupId() == null) {
- throw new AssertionError("Must have a groupId!");
- }
-
- return new SignalGroupV1Record.Builder(storageKey, GroupUtil.getDecodedIdOrThrow(recipient.getGroupId()))
- .setBlocked(recipient.isBlocked())
- .setProfileSharingEnabled(recipient.isProfileSharing())
- .build();
- }
-
- public static @NonNull IdentityDatabase.VerifiedStatus remoteToLocalIdentityStatus(@NonNull IdentityState identityState) {
- switch (identityState) {
- case VERIFIED: return IdentityDatabase.VerifiedStatus.VERIFIED;
- case UNVERIFIED: return IdentityDatabase.VerifiedStatus.UNVERIFIED;
- default: return IdentityDatabase.VerifiedStatus.DEFAULT;
- }
- }
-
- public static @NonNull byte[] generateKey() {
- if (testKeyGenerator != null) {
- return testKeyGenerator.generate();
- } else {
- return KEY_GENERATOR.generate();
- }
- }
-
- @VisibleForTesting
- static @NonNull SignalContactRecord mergeContacts(@NonNull SignalContactRecord remote,
- @NonNull SignalContactRecord local)
- {
- UUID uuid = remote.getAddress().getUuid().or(local.getAddress().getUuid()).orNull();
- String e164 = remote.getAddress().getNumber().or(local.getAddress().getNumber()).orNull();
- SignalServiceAddress address = new SignalServiceAddress(uuid, e164);
- String givenName = remote.getGivenName().or(local.getGivenName()).or("");
- String familyName = remote.getFamilyName().or(local.getFamilyName()).or("");
- byte[] profileKey = remote.getProfileKey().or(local.getProfileKey()).orNull();
- String username = remote.getUsername().or(local.getUsername()).or("");
- IdentityState identityState = remote.getIdentityState();
- byte[] identityKey = remote.getIdentityKey().or(local.getIdentityKey()).orNull();
- String nickname = local.getNickname().or(""); // TODO [greyson] Update this when we add real nickname support
- boolean blocked = remote.isBlocked();
- boolean profileSharing = remote.isProfileSharingEnabled() || local.isProfileSharingEnabled();
- boolean matchesRemote = doParamsMatchContact(remote, address, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, nickname);
- boolean matchesLocal = doParamsMatchContact(local, address, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, nickname);
-
- if (remote.getProtoVersion() > 0) {
- Log.w(TAG, "Inbound model has version " + remote.getProtoVersion() + ", but our version is 0.");
- }
-
- if (matchesRemote) {
- return remote;
- } else if (matchesLocal) {
- return local;
- } else {
- return new SignalContactRecord.Builder(generateKey(), address)
- .setGivenName(givenName)
- .setFamilyName(familyName)
- .setProfileKey(profileKey)
- .setUsername(username)
- .setIdentityState(identityState)
- .setIdentityKey(identityKey)
- .setBlocked(blocked)
- .setProfileSharingEnabled(profileSharing)
- .setNickname(nickname)
- .build();
- }
- }
-
- @VisibleForTesting
- static @NonNull SignalGroupV1Record mergeGroupV1(@NonNull SignalGroupV1Record remote,
- @NonNull SignalGroupV1Record local)
- {
- boolean blocked = remote.isBlocked();
- boolean profileSharing = remote.isProfileSharingEnabled() || local.isProfileSharingEnabled();
-
- boolean matchesRemote = blocked == remote.isBlocked() && profileSharing == remote.isProfileSharingEnabled();
- boolean matchesLocal = blocked == local.isBlocked() && profileSharing == local.isProfileSharingEnabled();
-
- if (matchesRemote) {
- return remote;
- } else if (matchesLocal) {
- return local;
- } else {
- return new SignalGroupV1Record.Builder(generateKey(), remote.getGroupId())
- .setBlocked(blocked)
- .setProfileSharingEnabled(blocked)
- .build();
- }
- }
-
- @VisibleForTesting
- static void setTestKeyGenerator(@Nullable KeyGenerator keyGenerator) {
- testKeyGenerator = keyGenerator;
- }
-
- private static IdentityState localToRemoteIdentityState(@NonNull IdentityDatabase.VerifiedStatus local) {
- switch (local) {
- case VERIFIED: return IdentityState.VERIFIED;
- case UNVERIFIED: return IdentityState.UNVERIFIED;
- default: return IdentityState.DEFAULT;
- }
- }
-
- private static boolean doParamsMatchContact(@NonNull SignalContactRecord contact,
- @NonNull SignalServiceAddress address,
- @Nullable String givenName,
- @Nullable String familyName,
- @Nullable byte[] profileKey,
- @Nullable String username,
- @Nullable IdentityState identityState,
- @Nullable byte[] identityKey,
- boolean blocked,
- boolean profileSharing,
- @Nullable String nickname)
- {
- return Objects.equals(contact.getAddress(), address) &&
- Objects.equals(contact.getGivenName().or(""), givenName) &&
- Objects.equals(contact.getFamilyName().or(""), familyName) &&
- Arrays.equals(contact.getProfileKey().orNull(), profileKey) &&
- Objects.equals(contact.getUsername().or(""), username) &&
- Objects.equals(contact.getIdentityState(), identityState) &&
- Arrays.equals(contact.getIdentityKey().orNull(), identityKey) &&
- contact.isBlocked() == blocked &&
- contact.isProfileSharingEnabled() == profileSharing &&
- Objects.equals(contact.getNickname().or(""), nickname);
- }
-
- private static @NonNull ContactRecordMergeResult resolveContactConflict(@NonNull Collection remoteOnlyRecords,
- @NonNull Collection localOnlyRecords)
- {
- Map localByUuid = new HashMap<>();
- Map localByE164 = new HashMap<>();
-
- for (SignalContactRecord contact : localOnlyRecords) {
- if (contact.getAddress().getUuid().isPresent()) {
- localByUuid.put(contact.getAddress().getUuid().get(), contact);
- }
- if (contact.getAddress().getNumber().isPresent()) {
- localByE164.put(contact.getAddress().getNumber().get(), contact);
- }
- }
-
- Set localInserts = new LinkedHashSet<>(remoteOnlyRecords);
- Set remoteInserts = new LinkedHashSet<>(localOnlyRecords);
- Set localUpdates = new LinkedHashSet<>();
- Set remoteUpdates = new LinkedHashSet<>();
-
- for (SignalContactRecord remote : remoteOnlyRecords) {
- SignalContactRecord localUuid = remote.getAddress().getUuid().isPresent() ? localByUuid.get(remote.getAddress().getUuid().get()) : null;
- SignalContactRecord localE164 = remote.getAddress().getNumber().isPresent() ? localByE164.get(remote.getAddress().getNumber().get()) : null;
-
- Optional local = Optional.fromNullable(localUuid).or(Optional.fromNullable(localE164));
-
- if (local.isPresent()) {
- SignalContactRecord merged = mergeContacts(remote, local.get());
-
- if (!merged.equals(remote)) {
- remoteUpdates.add(new ContactUpdate(remote, merged));
- }
-
- if (!merged.equals(local.get())) {
- localUpdates.add(new ContactUpdate(local.get(), merged));
- }
-
- localInserts.remove(remote);
- remoteInserts.remove(local.get());
- }
- }
-
- return new ContactRecordMergeResult(localInserts, localUpdates, remoteInserts, remoteUpdates);
- }
-
- private static @NonNull GroupV1RecordMergeResult resolveGroupV1Conflict(@NonNull Collection remoteOnlyRecords,
- @NonNull Collection localOnlyRecords)
- {
- Map remoteByGroupId = Stream.of(remoteOnlyRecords).collect(Collectors.toMap(g -> GroupUtil.getEncodedId(g.getGroupId(), false), g -> g));
- Map localByGroupId = Stream.of(localOnlyRecords).collect(Collectors.toMap(g -> GroupUtil.getEncodedId(g.getGroupId(), false), g -> g));
-
- Set localInserts = new LinkedHashSet<>(remoteOnlyRecords);
- Set remoteInserts = new LinkedHashSet<>(localOnlyRecords);
- Set localUpdates = new LinkedHashSet<>();
- Set remoteUpdates = new LinkedHashSet<>();
-
- for (Map.Entry entry : remoteByGroupId.entrySet()) {
- SignalGroupV1Record remote = entry.getValue();
- SignalGroupV1Record local = localByGroupId.get(entry.getKey());
-
- if (local != null) {
- SignalGroupV1Record merged = mergeGroupV1(remote, local);
-
- if (!merged.equals(remote)) {
- remoteUpdates.add(new GroupV1Update(remote, merged));
- }
-
- if (!merged.equals(local)) {
- localUpdates.add(new GroupV1Update(local, merged));
- }
-
- localInserts.remove(remote);
- remoteInserts.remove(local);
- }
- }
-
- return new GroupV1RecordMergeResult(localInserts, localUpdates, remoteInserts, remoteUpdates);
- }
-
- public static final class ContactUpdate {
- private final SignalContactRecord oldContact;
- private final SignalContactRecord newContact;
-
- ContactUpdate(@NonNull SignalContactRecord oldContact, @NonNull SignalContactRecord newContact) {
- this.oldContact = oldContact;
- this.newContact = newContact;
- }
-
- public @NonNull SignalContactRecord getOld() {
- return oldContact;
- }
-
- public @NonNull SignalContactRecord getNew() {
- return newContact;
- }
-
- public boolean profileKeyChanged() {
- return !OptionalUtil.byteArrayEquals(oldContact.getProfileKey(), newContact.getProfileKey());
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- ContactUpdate that = (ContactUpdate) o;
- return oldContact.equals(that.oldContact) &&
- newContact.equals(that.newContact);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(oldContact, newContact);
- }
- }
-
- public static final class GroupV1Update {
- private final SignalGroupV1Record oldGroup;
- private final SignalGroupV1Record newGroup;
-
-
- public GroupV1Update(@NonNull SignalGroupV1Record oldGroup, @NonNull SignalGroupV1Record newGroup) {
- this.oldGroup = oldGroup;
- this.newGroup = newGroup;
- }
-
- public @NonNull SignalGroupV1Record getOld() {
- return oldGroup;
- }
-
- public @NonNull SignalGroupV1Record getNew() {
- return newGroup;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- GroupV1Update that = (GroupV1Update) o;
- return oldGroup.equals(that.oldGroup) &&
- newGroup.equals(that.newGroup);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(oldGroup, newGroup);
- }
- }
-
- @VisibleForTesting
- static class RecordUpdate {
- private final SignalStorageRecord oldRecord;
- private final SignalStorageRecord newRecord;
-
- RecordUpdate(@NonNull SignalStorageRecord oldRecord, @NonNull SignalStorageRecord newRecord) {
- this.oldRecord = oldRecord;
- this.newRecord = newRecord;
- }
-
- public @NonNull SignalStorageRecord getOld() {
- return oldRecord;
- }
-
- public @NonNull SignalStorageRecord getNew() {
- return newRecord;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- RecordUpdate that = (RecordUpdate) o;
- return oldRecord.equals(that.oldRecord) &&
- newRecord.equals(that.newRecord);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(oldRecord, newRecord);
- }
- }
-
- public static final class KeyDifferenceResult {
- private final List remoteOnlyKeys;
- private final List localOnlyKeys;
-
- private KeyDifferenceResult(@NonNull List remoteOnlyKeys, @NonNull List localOnlyKeys) {
- this.remoteOnlyKeys = remoteOnlyKeys;
- this.localOnlyKeys = localOnlyKeys;
- }
-
- public @NonNull List getRemoteOnlyKeys() {
- return remoteOnlyKeys;
- }
-
- public @NonNull List getLocalOnlyKeys() {
- return localOnlyKeys;
- }
-
- public boolean isEmpty() {
- return remoteOnlyKeys.isEmpty() && localOnlyKeys.isEmpty();
- }
- }
-
- public static final class MergeResult {
- private final Set localContactInserts;
- private final Set localContactUpdates;
- private final Set localGroupV1Inserts;
- private final Set localGroupV1Updates;
- private final Set localUnknownInserts;
- private final Set localUnknownDeletes;
- private final Set remoteInserts;
- private final Set remoteUpdates;
-
- @VisibleForTesting
- MergeResult(@NonNull Set localContactInserts,
- @NonNull Set localContactUpdates,
- @NonNull Set localGroupV1Inserts,
- @NonNull Set localGroupV1Updates,
- @NonNull Set localUnknownInserts,
- @NonNull Set localUnknownDeletes,
- @NonNull Set remoteInserts,
- @NonNull Set remoteUpdates)
- {
- this.localContactInserts = localContactInserts;
- this.localContactUpdates = localContactUpdates;
- this.localGroupV1Inserts = localGroupV1Inserts;
- this.localGroupV1Updates = localGroupV1Updates;
- this.localUnknownInserts = localUnknownInserts;
- this.localUnknownDeletes = localUnknownDeletes;
- this.remoteInserts = remoteInserts;
- this.remoteUpdates = remoteUpdates;
- }
-
- public @NonNull Set getLocalContactInserts() {
- return localContactInserts;
- }
-
- public @NonNull Set getLocalContactUpdates() {
- return localContactUpdates;
- }
-
- public @NonNull Set getLocalGroupV1Inserts() {
- return localGroupV1Inserts;
- }
-
- public @NonNull Set getLocalGroupV1Updates() {
- return localGroupV1Updates;
- }
-
- public @NonNull Set getLocalUnknownInserts() {
- return localUnknownInserts;
- }
-
- public @NonNull Set getLocalUnknownDeletes() {
- return localUnknownDeletes;
- }
-
- public @NonNull Set getRemoteInserts() {
- return remoteInserts;
- }
-
- public @NonNull Set getRemoteUpdates() {
- return remoteUpdates;
- }
-
- @Override
- public @NonNull String toString() {
- return String.format(Locale.ENGLISH,
- "localContactInserts: %d, localContactUpdates: %d, localGroupInserts: %d, localGroupUpdates: %d, localUnknownInserts: %d, localUnknownDeletes: %d, remoteInserts: %d, remoteUpdates: %d",
- localContactInserts.size(), localContactUpdates.size(), localGroupV1Inserts.size(), localGroupV1Updates.size(), localUnknownInserts.size(), localUnknownDeletes.size(), remoteInserts.size(), remoteUpdates.size());
- }
- }
-
- public static final class WriteOperationResult {
- private final SignalStorageManifest manifest;
- private final List inserts;
- private final List deletes;
-
- private WriteOperationResult(@NonNull SignalStorageManifest manifest,
- @NonNull List inserts,
- @NonNull List deletes)
- {
- this.manifest = manifest;
- this.inserts = inserts;
- this.deletes = deletes;
- }
-
- public @NonNull SignalStorageManifest getManifest() {
- return manifest;
- }
-
- public @NonNull List getInserts() {
- return inserts;
- }
-
- public @NonNull List getDeletes() {
- return deletes;
- }
-
- public boolean isEmpty() {
- return inserts.isEmpty() && deletes.isEmpty();
- }
-
- @Override
- public @NonNull String toString() {
- return String.format(Locale.ENGLISH,
- "ManifestVersion: %d, Total Keys: %d, Inserts: %d, Deletes: %d",
- manifest.getVersion(),
- manifest.getStorageKeys().size(),
- inserts.size(),
- deletes.size());
- }
- }
-
- public static class LocalWriteResult {
- private final WriteOperationResult writeResult;
- private final Map storageKeyUpdates;
-
- private LocalWriteResult(WriteOperationResult writeResult, Map storageKeyUpdates) {
- this.writeResult = writeResult;
- this.storageKeyUpdates = storageKeyUpdates;
- }
-
- public @NonNull WriteOperationResult getWriteResult() {
- return writeResult;
- }
-
- public @NonNull Map getStorageKeyUpdates() {
- return storageKeyUpdates;
- }
- }
-
- private static final class ContactRecordMergeResult {
- final Set localInserts;
- final Set localUpdates;
- final Set remoteInserts;
- final Set remoteUpdates;
-
- ContactRecordMergeResult(@NonNull Set localInserts,
- @NonNull Set localUpdates,
- @NonNull Set remoteInserts,
- @NonNull Set remoteUpdates)
- {
- this.localInserts = localInserts;
- this.localUpdates = localUpdates;
- this.remoteInserts = remoteInserts;
- this.remoteUpdates = remoteUpdates;
- }
- }
-
- private static final class GroupV1RecordMergeResult {
- final Set localInserts;
- final Set localUpdates;
- final Set remoteInserts;
- final Set remoteUpdates;
-
- GroupV1RecordMergeResult(@NonNull Set localInserts,
- @NonNull Set localUpdates,
- @NonNull Set remoteInserts,
- @NonNull Set remoteUpdates)
- {
- this.localInserts = localInserts;
- this.localUpdates = localUpdates;
- this.remoteInserts = remoteInserts;
- this.remoteUpdates = remoteUpdates;
- }
- }
-
- interface KeyGenerator {
- @NonNull byte[] generate();
- }
-}
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 4dc0e81e0..8c2e864dd 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
@@ -28,10 +28,10 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.hardware.Camera;
import android.net.Uri;
import android.os.AsyncTask;
@@ -73,6 +73,8 @@ import androidx.core.graphics.drawable.IconCompat;
import androidx.lifecycle.ViewModelProviders;
import com.annimon.stream.Stream;
+import com.bumptech.glide.request.target.CustomTarget;
+import com.bumptech.glide.request.transition.Transition;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
@@ -207,7 +209,7 @@ import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
import org.thoughtcrime.securesms.util.CommunicationActions;
-import org.thoughtcrime.securesms.util.Dialogs;
+import org.thoughtcrime.securesms.util.DrawableUtil;
import org.thoughtcrime.securesms.util.DynamicDarkToolbarTheme;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
@@ -261,6 +263,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
StickerKeyboardProvider.StickerEventListener,
AttachmentKeyboard.Callback
{
+
+ private static final int SHORTCUT_ICON_SIZE = Build.VERSION.SDK_INT >= 26 ? ViewUtil.dpToPx(72) : ViewUtil.dpToPx(48 + 16 * 2);
+
private static final String TAG = ConversationActivity.class.getSimpleName();
public static final String RECIPIENT_EXTRA = "recipient_id";
@@ -431,9 +436,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
return;
}
- if (!Util.isEmpty(composeText) || attachmentManager.isAttachmentPresent()) {
+ if (!Util.isEmpty(composeText) || attachmentManager.isAttachmentPresent() || inputPanel.getQuote().isPresent()) {
saveDraft();
attachmentManager.clear(glideRequests, false);
+ inputPanel.clearQuote();
silentlySetComposeText("");
}
@@ -1046,47 +1052,58 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void handleAddShortcut() {
Log.i(TAG, "Creating home screen shortcut for recipient " + recipient.get().getId());
- new AsyncTask() {
+ final Context context = getApplicationContext();
+ final Recipient recipient = this.recipient.get();
- @Override
- protected IconCompat doInBackground(Void... voids) {
- Context context = getApplicationContext();
- IconCompat icon = null;
+ GlideApp.with(this)
+ .asBitmap()
+ .load(recipient.getContactPhoto())
+ .error(recipient.getFallbackContactPhoto().asDrawable(this, recipient.getColor().toAvatarColor(this), false))
+ .into(new CustomTarget() {
+ @Override
+ public void onLoadFailed(@Nullable Drawable errorDrawable) {
+ if (errorDrawable == null) {
+ throw new AssertionError();
+ }
- if (recipient.get().getContactPhoto() != null) {
- try {
- Bitmap bitmap = BitmapFactory.decodeStream(recipient.get().getContactPhoto().openInputStream(context));
- bitmap = BitmapUtil.createScaledBitmap(bitmap, 300, 300);
- icon = IconCompat.createWithAdaptiveBitmap(bitmap);
- } catch (IOException e) {
- Log.w(TAG, "Failed to decode contact photo during shortcut creation. Falling back to generic icon.", e);
- }
- }
+ Log.w(TAG, "Utilizing fallback photo for shortcut for recipient " + recipient.getId());
- if (icon == null) {
- icon = IconCompat.createWithResource(context, recipient.get().isGroup() ? R.mipmap.ic_group_shortcut
- : R.mipmap.ic_person_shortcut);
- }
+ SimpleTask.run(() -> DrawableUtil.toBitmap(errorDrawable, SHORTCUT_ICON_SIZE, SHORTCUT_ICON_SIZE),
+ bitmap -> addIconToHomeScreen(context, bitmap, recipient));
+ }
- return icon;
- }
+ @Override
+ public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition super Bitmap> transition) {
+ SimpleTask.run(() -> BitmapUtil.createScaledBitmap(resource, SHORTCUT_ICON_SIZE, SHORTCUT_ICON_SIZE),
+ bitmap -> addIconToHomeScreen(context, bitmap, recipient));
+ }
- @Override
- protected void onPostExecute(IconCompat icon) {
- Context context = getApplicationContext();
- String name = recipient.get().getDisplayName(ConversationActivity.this);
+ @Override
+ public void onLoadCleared(@Nullable Drawable placeholder) {
+ }
+ });
- ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(context, recipient.get().getId().serialize() + '-' + System.currentTimeMillis())
- .setShortLabel(name)
- .setIcon(icon)
- .setIntent(ShortcutLauncherActivity.createIntent(context, recipient.getId()))
- .build();
+ }
- if (ShortcutManagerCompat.requestPinShortcut(context, shortcutInfo, null)) {
- Toast.makeText(context, getString(R.string.ConversationActivity_added_to_home_screen), Toast.LENGTH_LONG).show();
- }
- }
- }.execute();
+ private static void addIconToHomeScreen(@NonNull Context context,
+ @NonNull Bitmap bitmap,
+ @NonNull Recipient recipient)
+ {
+ IconCompat icon = IconCompat.createWithAdaptiveBitmap(bitmap);
+ String name = recipient.isLocalNumber() ? context.getString(R.string.note_to_self)
+ : recipient.getDisplayName(context);
+
+ ShortcutInfoCompat shortcutInfoCompat = new ShortcutInfoCompat.Builder(context, recipient.getId().serialize() + '-' + System.currentTimeMillis())
+ .setShortLabel(name)
+ .setIcon(icon)
+ .setIntent(ShortcutLauncherActivity.createIntent(context, recipient.getId()))
+ .build();
+
+ if (ShortcutManagerCompat.requestPinShortcut(context, shortcutInfoCompat, null)) {
+ Toast.makeText(context, context.getString(R.string.ConversationActivity_added_to_home_screen), Toast.LENGTH_LONG).show();
+ }
+
+ bitmap.recycle();
}
private void handleSearch() {
@@ -1123,7 +1140,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void handleEditPushGroup() {
Intent intent = new Intent(ConversationActivity.this, GroupCreateActivity.class);
- intent.putExtra(GroupCreateActivity.GROUP_ID_EXTRA, recipient.get().requireGroupId());
+ intent.putExtra(GroupCreateActivity.GROUP_ID_EXTRA, recipient.get().requireGroupId().toString());
startActivityForResult(intent, GROUP_EDIT);
}
@@ -1787,7 +1804,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
inputPanel.setMediaKeyboardToggleMode(true);
TooltipPopup.forTarget(inputPanel.getMediaKeyboardToggleAnchorView())
- .setBackgroundTint(getResources().getColor(R.color.core_blue))
+ .setBackgroundTint(getResources().getColor(R.color.core_ultramarine))
.setTextColor(getResources().getColor(R.color.core_white))
.setText(R.string.ConversationActivity_new_say_it_with_stickers)
.setOnDismissListener(() -> {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
index 5b09be243..ae6185788 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
@@ -336,10 +336,11 @@ public class ConversationFragment extends Fragment
return;
}
- Recipient recipient = recipientInfo.getRecipient();
- boolean isSelf = Recipient.self().equals(recipient);
- int memberCount = recipientInfo.getGroupMemberCount();
- List groups = recipientInfo.getSharedGroups();
+ Recipient recipient = recipientInfo.getRecipient();
+ boolean isSelf = Recipient.self().equals(recipient);
+ int memberCount = recipientInfo.getGroupMemberCount();
+ int pendingMemberCount = recipientInfo.getGroupPendingMemberCount();
+ List groups = recipientInfo.getSharedGroups();
if (recipient != null) {
conversationBanner.setAvatar(GlideApp.with(context), recipient);
@@ -348,7 +349,14 @@ public class ConversationFragment extends Fragment
conversationBanner.setTitle(title);
if (recipient.isGroup()) {
- conversationBanner.setSubtitle(context.getResources().getQuantityString(R.plurals.MessageRequestProfileView_members, memberCount, memberCount));
+ if (pendingMemberCount > 0) {
+ conversationBanner.setSubtitle(context.getResources()
+ .getQuantityString(R.plurals.MessageRequestProfileView_members_and_invited, memberCount,
+ memberCount, pendingMemberCount));
+ } else {
+ conversationBanner.setSubtitle(context.getResources().getQuantityString(R.plurals.MessageRequestProfileView_members, memberCount,
+ memberCount));
+ }
} else if (isSelf) {
conversationBanner.setSubtitle(context.getString(R.string.ConversationFragment__you_can_add_notes_for_yourself_in_this_conversation));
} else {
@@ -1015,7 +1023,7 @@ public class ConversationFragment extends Fragment
TooltipPopup.forTarget(requireActivity().findViewById(R.id.menu_context_reply))
.setText(text)
.setTextColor(getResources().getColor(R.color.core_white))
- .setBackgroundTint(getResources().getColor(R.color.core_blue))
+ .setBackgroundTint(getResources().getColor(R.color.core_ultramarine))
.show(TooltipPopup.POSITION_BELOW);
TextSecurePreferences.setHasSeenSwipeToReplyTooltip(requireContext(), true);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java
index c33e59c17..7ae320af1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java
@@ -408,10 +408,10 @@ public class ConversationItem extends LinearLayout implements BindableConversati
private void setAudioViewTint(MessageRecord messageRecord, Recipient recipient) {
if (messageRecord.isOutgoing()) {
- if (DynamicTheme.LIGHT.equals(TextSecurePreferences.getTheme(context))) {
- audioViewStub.get().setTint(getContext().getResources().getColor(R.color.core_grey_60), defaultBubbleColor);
- } else {
+ if (DynamicTheme.isDarkTheme(context)) {
audioViewStub.get().setTint(Color.WHITE, defaultBubbleColor);
+ } else {
+ audioViewStub.get().setTint(getContext().getResources().getColor(R.color.core_grey_60), defaultBubbleColor);
}
} else {
audioViewStub.get().setTint(Color.WHITE, recipient.getColor().toConversationColor(context));
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationStickerViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationStickerViewModel.java
index 8f8ff2a69..2403e4788 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationStickerViewModel.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationStickerViewModel.java
@@ -10,6 +10,7 @@ import android.os.Handler;
import androidx.annotation.NonNull;
import android.text.TextUtils;
+import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.database.CursorList;
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
import org.thoughtcrime.securesms.database.model.StickerRecord;
@@ -17,21 +18,21 @@ import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
import org.thoughtcrime.securesms.util.CloseableLiveData;
import org.thoughtcrime.securesms.util.Throttler;
+import java.util.List;
+
class ConversationStickerViewModel extends ViewModel {
- private static final int SEARCH_LIMIT = 10;
-
- private final Application application;
- private final StickerSearchRepository repository;
- private final CloseableLiveData> stickers;
- private final MutableLiveData stickersAvailable;
- private final Throttler availabilityThrottler;
- private final ContentObserver packObserver;
+ private final Application application;
+ private final StickerSearchRepository repository;
+ private final MutableLiveData> stickers;
+ private final MutableLiveData stickersAvailable;
+ private final Throttler availabilityThrottler;
+ private final ContentObserver packObserver;
private ConversationStickerViewModel(@NonNull Application application, @NonNull StickerSearchRepository repository) {
this.application = application;
this.repository = repository;
- this.stickers = new CloseableLiveData<>();
+ this.stickers = new MutableLiveData<>();
this.stickersAvailable = new MutableLiveData<>();
this.availabilityThrottler = new Throttler(500);
this.packObserver = new ContentObserver(new Handler()) {
@@ -44,7 +45,7 @@ class ConversationStickerViewModel extends ViewModel {
application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, packObserver);
}
- @NonNull LiveData> getStickerResults() {
+ @NonNull LiveData> getStickerResults() {
return stickers;
}
@@ -54,7 +55,7 @@ class ConversationStickerViewModel extends ViewModel {
}
void onInputTextUpdated(@NonNull String text) {
- if (TextUtils.isEmpty(text) || text.length() > SEARCH_LIMIT) {
+ if (TextUtils.isEmpty(text) || text.length() > EmojiUtil.MAX_EMOJI_LENGTH) {
stickers.setValue(CursorList.emptyList());
} else {
repository.searchByEmoji(text, stickers::postValue);
@@ -63,7 +64,6 @@ class ConversationStickerViewModel extends ViewModel {
@Override
protected void onCleared() {
- stickers.close();
application.getContentResolver().unregisterContentObserver(packObserver);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java
index d6a7ba707..ddc929612 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java
@@ -6,26 +6,27 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
+import android.text.TextUtils;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import android.text.TextUtils;
import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
+import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.BitmapUtil;
-import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import java.io.Closeable;
-import java.io.IOException;
import java.security.SecureRandom;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -94,9 +95,9 @@ public class GroupDatabase extends Database {
}
}
- public Optional getGroup(String groupId) {
+ public Optional getGroup(@NonNull GroupId groupId) {
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?",
- new String[] {groupId},
+ new String[] {groupId.toString()},
null, null, null))
{
if (cursor != null && cursor.moveToNext()) {
@@ -112,7 +113,7 @@ public class GroupDatabase extends Database {
return Optional.fromNullable(reader.getCurrent());
}
- public boolean isUnknownGroup(String groupId) {
+ public boolean isUnknownGroup(@NonNull GroupId groupId) {
Optional group = getGroup(groupId);
if (!group.isPresent()) {
@@ -142,7 +143,7 @@ public class GroupDatabase extends Database {
return new Reader(cursor);
}
- public String getOrCreateGroupForMembers(List members, boolean mms) {
+ public GroupId getOrCreateGroupForMembers(List members, boolean mms) {
Collections.sort(members);
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {GROUP_ID},
@@ -151,9 +152,9 @@ public class GroupDatabase extends Database {
null, null, null);
try {
if (cursor != null && cursor.moveToNext()) {
- return cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID));
+ return GroupId.parse(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)));
} else {
- String groupId = GroupUtil.getEncodedId(allocateGroupId(), mms);
+ GroupId groupId = allocateGroupId(mms);
create(groupId, null, members, null, null);
return groupId;
}
@@ -163,25 +164,31 @@ public class GroupDatabase extends Database {
}
public List getGroupNamesContainingMember(RecipientId recipientId) {
+ return Stream.of(getGroupsContainingMember(recipientId))
+ .map(GroupRecord::getTitle)
+ .toList();
+ }
+
+ public List getGroupsContainingMember(RecipientId recipientId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
String table = TABLE_NAME + " INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + RECIPIENT_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID;
- List groupNames = new LinkedList<>();
- String[] projection = new String[]{TITLE, MEMBERS};
String query = MEMBERS + " LIKE ?";
String[] args = new String[]{"%" + recipientId.serialize() + "%"};
String orderBy = ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.DATE + " DESC";
- try (Cursor cursor = database.query(table, projection, query, args, null, null, orderBy)) {
+ List groups = new LinkedList<>();
+
+ try (Cursor cursor = database.query(table, null, query, args, null, null, orderBy)) {
while (cursor != null && cursor.moveToNext()) {
List members = Util.split(cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)), ",");
if (members.contains(recipientId.serialize())) {
- groupNames.add(cursor.getString(cursor.getColumnIndexOrThrow(TITLE)));
+ groups.add(new Reader(cursor).getCurrent());
}
}
}
- return groupNames;
+ return groups;
}
public Reader getGroups() {
@@ -190,7 +197,7 @@ public class GroupDatabase extends Database {
return new Reader(cursor);
}
- public @NonNull List getGroupMembers(String groupId, boolean includeSelf) {
+ public @NonNull List getGroupMembers(@NonNull GroupId groupId, boolean includeSelf) {
List members = getCurrentMembers(groupId);
List recipients = new LinkedList<>();
@@ -205,14 +212,14 @@ public class GroupDatabase extends Database {
return recipients;
}
- public void create(@NonNull String groupId, @Nullable String title, @NonNull List members,
+ public void create(@NonNull GroupId groupId, @Nullable String title, @NonNull List members,
@Nullable SignalServiceAttachmentPointer avatar, @Nullable String relay)
{
Collections.sort(members);
ContentValues contentValues = new ContentValues();
contentValues.put(RECIPIENT_ID, DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId).serialize());
- contentValues.put(GROUP_ID, groupId);
+ contentValues.put(GROUP_ID, groupId.toString());
contentValues.put(TITLE, title);
contentValues.put(MEMBERS, RecipientId.toSerializedList(members));
@@ -226,7 +233,7 @@ public class GroupDatabase extends Database {
contentValues.put(AVATAR_RELAY, relay);
contentValues.put(TIMESTAMP, System.currentTimeMillis());
contentValues.put(ACTIVE, 1);
- contentValues.put(MMS, GroupUtil.isMmsGroup(groupId));
+ contentValues.put(MMS, groupId.isMmsGroup());
databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
@@ -236,7 +243,7 @@ public class GroupDatabase extends Database {
notifyConversationListListeners();
}
- public void update(String groupId, String title, SignalServiceAttachmentPointer avatar) {
+ public void update(@NonNull GroupId groupId, String title, SignalServiceAttachmentPointer avatar) {
ContentValues contentValues = new ContentValues();
if (title != null) contentValues.put(TITLE, title);
@@ -249,7 +256,7 @@ public class GroupDatabase extends Database {
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,
GROUP_ID + " = ?",
- new String[] {groupId});
+ new String[] {groupId.toString()});
RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
Recipient.live(groupRecipient).refresh();
@@ -257,21 +264,21 @@ public class GroupDatabase extends Database {
notifyConversationListListeners();
}
- public void updateTitle(String groupId, String title) {
+ public void updateTitle(@NonNull GroupId groupId, String title) {
ContentValues contentValues = new ContentValues();
contentValues.put(TITLE, title);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
- new String[] {groupId});
+ new String[] {groupId.toString()});
RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
Recipient.live(groupRecipient).refresh();
}
- public void updateAvatar(String groupId, Bitmap avatar) {
+ public void updateAvatar(@NonNull GroupId groupId, @Nullable Bitmap avatar) {
updateAvatar(groupId, BitmapUtil.toByteArray(avatar));
}
- public void updateAvatar(String groupId, byte[] avatar) {
+ public void updateAvatar(@NonNull GroupId groupId, @Nullable byte[] avatar) {
long avatarId;
if (avatar != null) avatarId = Math.abs(new SecureRandom().nextLong());
@@ -283,13 +290,13 @@ public class GroupDatabase extends Database {
contentValues.put(AVATAR_ID, avatarId);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
- new String[] {groupId});
+ new String[] {groupId.toString()});
RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
Recipient.live(groupRecipient).refresh();
}
- public void updateMembers(String groupId, List members) {
+ public void updateMembers(@NonNull GroupId groupId, List members) {
Collections.sort(members);
ContentValues contents = new ContentValues();
@@ -297,13 +304,13 @@ public class GroupDatabase extends Database {
contents.put(ACTIVE, 1);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
- new String[] {groupId});
+ new String[] {groupId.toString()});
RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
Recipient.live(groupRecipient).refresh();
}
- public void remove(String groupId, RecipientId source) {
+ public void remove(@NonNull GroupId groupId, RecipientId source) {
List currentMembers = getCurrentMembers(groupId);
currentMembers.remove(source);
@@ -311,19 +318,19 @@ public class GroupDatabase extends Database {
contents.put(MEMBERS, RecipientId.toSerializedList(currentMembers));
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
- new String[] {groupId});
+ new String[] {groupId.toString()});
RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
Recipient.live(groupRecipient).refresh();
}
- private List getCurrentMembers(String groupId) {
+ private List getCurrentMembers(@NonNull GroupId groupId) {
Cursor cursor = null;
try {
cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {MEMBERS},
GROUP_ID + " = ?",
- new String[] {groupId},
+ new String[] {groupId.toString()},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
@@ -338,23 +345,22 @@ public class GroupDatabase extends Database {
}
}
- public boolean isActive(String groupId) {
+ public boolean isActive(@NonNull GroupId groupId) {
Optional record = getGroup(groupId);
return record.isPresent() && record.get().isActive();
}
- public void setActive(String groupId, boolean active) {
+ public void setActive(@NonNull GroupId groupId, boolean active) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(ACTIVE, active ? 1 : 0);
- database.update(TABLE_NAME, values, GROUP_ID + " = ?", new String[] {groupId});
+ database.update(TABLE_NAME, values, GROUP_ID + " = ?", new String[] {groupId.toString()});
}
-
- public byte[] allocateGroupId() {
+ public static GroupId allocateGroupId(boolean mms) {
byte[] groupId = new byte[16];
new SecureRandom().nextBytes(groupId);
- return groupId;
+ return mms ? GroupId.mms(groupId) : GroupId.v1(groupId);
}
public static class Reader implements Closeable {
@@ -378,7 +384,7 @@ public class GroupDatabase extends Database {
return null;
}
- return new GroupRecord(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)),
+ return new GroupRecord(GroupId.parse(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID))),
RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID))),
cursor.getString(cursor.getColumnIndexOrThrow(TITLE)),
cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)),
@@ -401,7 +407,7 @@ public class GroupDatabase extends Database {
public static class GroupRecord {
- private final String id;
+ private final GroupId id;
private final RecipientId recipientId;
private final String title;
private final List members;
@@ -414,7 +420,7 @@ public class GroupDatabase extends Database {
private final boolean active;
private final boolean mms;
- public GroupRecord(String id, @NonNull RecipientId recipientId, String title, String members, byte[] avatar,
+ public GroupRecord(@NonNull GroupId id, @NonNull RecipientId recipientId, String title, String members, byte[] avatar,
long avatarId, byte[] avatarKey, String avatarContentType,
String relay, boolean active, byte[] avatarDigest, boolean mms)
{
@@ -434,22 +440,14 @@ public class GroupDatabase extends Database {
else this.members = new LinkedList<>();
}
- public byte[] getId() {
- try {
- return GroupUtil.getDecodedId(id);
- } catch (IOException ioe) {
- throw new AssertionError(ioe);
- }
+ public GroupId getId() {
+ return id;
}
public @NonNull RecipientId getRecipientId() {
return recipientId;
}
- public String getEncodedId() {
- return id;
- }
-
public String getTitle() {
return title;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java
index 37d12d224..ce8031b56 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java
@@ -17,29 +17,30 @@ import net.sqlcipher.database.SQLiteDatabase;
import org.signal.zkgroup.profiles.ProfileKey;
import org.signal.zkgroup.profiles.ProfileKeyCredential;
import org.thoughtcrime.securesms.color.MaterialColor;
-import org.thoughtcrime.securesms.contacts.sync.StorageSyncHelper;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.jobs.StorageSyncJob;
+import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
+import org.thoughtcrime.securesms.storage.StorageSyncHelper;
+import org.thoughtcrime.securesms.storage.StorageSyncHelper.RecordUpdate;
+import org.thoughtcrime.securesms.storage.StorageSyncModels;
import org.thoughtcrime.securesms.util.Base64;
-import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.SqlUtil;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
import org.whispersystems.signalservice.api.storage.SignalContactRecord;
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record;
-import org.whispersystems.signalservice.api.storage.StorageKey;
+import org.whispersystems.signalservice.api.storage.StorageId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.Closeable;
@@ -93,7 +94,7 @@ public class RecipientDatabase extends Database {
private static final String FORCE_SMS_SELECTION = "force_sms_selection";
private static final String UUID_CAPABILITY = "uuid_supported";
private static final String GROUPS_V2_CAPABILITY = "gv2_capability";
- private static final String STORAGE_SERVICE_KEY = "storage_service_key";
+ private static final String STORAGE_SERVICE_ID = "storage_service_key";
private static final String DIRTY = "dirty";
private static final String PROFILE_GIVEN_NAME = "signal_profile_name";
private static final String PROFILE_FAMILY_NAME = "profile_family_name";
@@ -114,7 +115,7 @@ public class RecipientDatabase extends Database {
UNIDENTIFIED_ACCESS_MODE,
FORCE_SMS_SELECTION,
UUID_CAPABILITY, GROUPS_V2_CAPABILITY,
- STORAGE_SERVICE_KEY, DIRTY
+ STORAGE_SERVICE_ID, DIRTY
};
private static final String[] RECIPIENT_FULL_PROJECTION = ArrayUtils.concat(
@@ -132,7 +133,7 @@ public class RecipientDatabase extends Database {
};
private static final String[] ID_PROJECTION = new String[]{ID};
- private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, "COALESCE(" + PROFILE_JOINED_NAME + ", " + PROFILE_GIVEN_NAME + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + SYSTEM_DISPLAY_NAME + ", " + PROFILE_JOINED_NAME + ", " + PROFILE_GIVEN_NAME + ", " + USERNAME + ") AS " + SORT_NAME};
+ private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME};
public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, SEARCH_PROFILE_NAME, SORT_NAME};
static final List TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
.map(columnName -> TABLE_NAME + "." + columnName)
@@ -214,7 +215,7 @@ public class RecipientDatabase extends Database {
}
}
- enum DirtyState {
+ public enum DirtyState {
CLEAN(0), UPDATE(1), INSERT(2), DELETE(3);
private final int id;
@@ -226,6 +227,10 @@ public class RecipientDatabase extends Database {
int getId() {
return id;
}
+
+ public static DirtyState fromId(int id) {
+ return values()[id];
+ }
}
public enum GroupType {
@@ -283,7 +288,7 @@ public class RecipientDatabase extends Database {
FORCE_SMS_SELECTION + " INTEGER DEFAULT 0, " +
UUID_CAPABILITY + " INTEGER DEFAULT " + Recipient.Capability.UNKNOWN.serialize() + ", " +
GROUPS_V2_CAPABILITY + " INTEGER DEFAULT " + Recipient.Capability.UNKNOWN.serialize() + ", " +
- STORAGE_SERVICE_KEY + " TEXT UNIQUE DEFAULT NULL, " +
+ STORAGE_SERVICE_ID + " TEXT UNIQUE DEFAULT NULL, " +
DIRTY + " INTEGER DEFAULT " + DirtyState.CLEAN.getId() + ");";
private static final String INSIGHTS_INVITEE_LIST = "SELECT " + TABLE_NAME + "." + ID +
@@ -345,18 +350,18 @@ public class RecipientDatabase extends Database {
return getOrInsertByColumn(EMAIL, email).recipientId;
}
- public @NonNull RecipientId getOrInsertFromGroupId(@NonNull String groupId) {
- GetOrInsertResult result = getOrInsertByColumn(GROUP_ID, groupId);
+ public @NonNull RecipientId getOrInsertFromGroupId(@NonNull GroupId groupId) {
+ GetOrInsertResult result = getOrInsertByColumn(GROUP_ID, groupId.toString());
if (result.neededInsert) {
ContentValues values = new ContentValues();
- if (GroupUtil.isMmsGroup(groupId)) {
+ if (groupId.isMmsGroup()) {
values.put(GROUP_TYPE, GroupType.MMS.getId());
} else {
values.put(GROUP_TYPE, GroupType.SIGNAL_V1.getId());
values.put(DIRTY, DirtyState.INSERT.getId());
- values.put(STORAGE_SERVICE_KEY, Base64.encodeBytes(StorageSyncHelper.generateKey()));
+ values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey()));
}
update(result.recipientId, values);
@@ -399,29 +404,41 @@ public class RecipientDatabase extends Database {
}
}
+ public @NonNull DirtyState getDirtyState(@NonNull RecipientId recipientId) {
+ SQLiteDatabase db = databaseHelper.getReadableDatabase();
+
+ try (Cursor cursor = db.query(TABLE_NAME, new String[] { DIRTY }, ID_WHERE, new String[] { recipientId.serialize() }, null, null, null)) {
+ if (cursor != null && cursor.moveToFirst()) {
+ return DirtyState.fromId(cursor.getInt(cursor.getColumnIndexOrThrow(DIRTY)));
+ }
+ }
+
+ return DirtyState.CLEAN;
+ }
+
public @NonNull List getPendingRecipientSyncUpdates() {
- String query = DIRTY + " = ? AND " + STORAGE_SERVICE_KEY + " NOT NULL";
- String[] args = new String[] { String.valueOf(DirtyState.UPDATE.getId()) };
+ String query = DIRTY + " = ? AND " + STORAGE_SERVICE_ID + " NOT NULL AND " + TABLE_NAME + "." + ID + " != ?";
+ String[] args = new String[] { String.valueOf(DirtyState.UPDATE.getId()), Recipient.self().getId().serialize() };
return getRecipientSettings(query, args);
}
public @NonNull List getPendingRecipientSyncInsertions() {
- String query = DIRTY + " = ? AND " + STORAGE_SERVICE_KEY + " NOT NULL";
- String[] args = new String[] { String.valueOf(DirtyState.INSERT.getId()) };
+ String query = DIRTY + " = ? AND " + STORAGE_SERVICE_ID + " NOT NULL AND " + TABLE_NAME + "." + ID + " != ?";
+ String[] args = new String[] { String.valueOf(DirtyState.INSERT.getId()), Recipient.self().getId().serialize() };
return getRecipientSettings(query, args);
}
public @NonNull List getPendingRecipientSyncDeletions() {
- String query = DIRTY + " = ? AND " + STORAGE_SERVICE_KEY + " NOT NULL";
- String[] args = new String[] { String.valueOf(DirtyState.DELETE.getId()) };
+ String query = DIRTY + " = ? AND " + STORAGE_SERVICE_ID + " NOT NULL AND " + TABLE_NAME + "." + ID + " != ?";
+ String[] args = new String[] { String.valueOf(DirtyState.DELETE.getId()), Recipient.self().getId().serialize() };
return getRecipientSettings(query, args);
}
- public @Nullable RecipientSettings getByStorageSyncKey(@NonNull byte[] key) {
- List result = getRecipientSettings(STORAGE_SERVICE_KEY + " = ?", new String[] { Base64.encodeBytes(key) });
+ public @Nullable RecipientSettings getByStorageId(@NonNull byte[] storageId) {
+ List result = getRecipientSettings(STORAGE_SERVICE_ID + " = ?", new String[] { Base64.encodeBytes(storageId) });
if (result.size() > 0) {
return result.get(0);
@@ -430,16 +447,20 @@ public class RecipientDatabase extends Database {
return null;
}
- public void applyStorageSyncKeyUpdates(@NonNull Map keys) {
+ public void markNeedsSync(@NonNull RecipientId recipientId) {
+ markDirty(recipientId, DirtyState.UPDATE);
+ }
+
+ public void applyStorageIdUpdates(@NonNull Map storageIds) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.beginTransaction();
try {
String query = ID + " = ?";
- for (Map.Entry entry : keys.entrySet()) {
+ for (Map.Entry entry : storageIds.entrySet()) {
ContentValues values = new ContentValues();
- values.put(STORAGE_SERVICE_KEY, Base64.encodeBytes(entry.getValue()));
+ values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(entry.getValue().getRaw()));
values.put(DIRTY, DirtyState.CLEAN.getId());
db.update(TABLE_NAME, values, query, new String[] { entry.getKey().serialize() });
@@ -450,13 +471,14 @@ public class RecipientDatabase extends Database {
}
}
- public void applyStorageSyncUpdates(@NonNull Collection contactInserts,
- @NonNull Collection contactUpdates,
- @NonNull Collection groupV1Inserts,
- @NonNull Collection groupV1Updates)
+ public void applyStorageSyncUpdates(@NonNull Collection contactInserts,
+ @NonNull Collection> contactUpdates,
+ @NonNull Collection groupV1Inserts,
+ @NonNull Collection> groupV1Updates)
{
SQLiteDatabase db = databaseHelper.getWritableDatabase();
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
+ ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
db.beginTransaction();
@@ -483,30 +505,29 @@ public class RecipientDatabase extends Database {
try {
IdentityKey identityKey = new IdentityKey(insert.getIdentityKey().get(), 0);
- DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(recipientId, identityKey, StorageSyncHelper.remoteToLocalIdentityStatus(insert.getIdentityState()));
+ DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.getIdentityState()));
IdentityUtil.markIdentityVerified(context, Recipient.resolved(recipientId), true, true);
} catch (InvalidKeyException e) {
Log.w(TAG, "Failed to process identity key during insert! Skipping.", e);
}
}
- if (Recipient.self().getId().equals(recipientId)) {
- TextSecurePreferences.setProfileName(context, ProfileName.fromParts(insert.getGivenName().orNull(), insert.getFamilyName().orNull()));
- }
+ threadDatabase.setArchived(recipientId, insert.isArchived());
+ Recipient.live(recipientId).refresh();
}
}
- for (StorageSyncHelper.ContactUpdate update : contactUpdates) {
+ for (RecordUpdate update : contactUpdates) {
ContentValues values = getValuesForStorageContact(update.getNew());
- int updateCount = db.update(TABLE_NAME, values, STORAGE_SERVICE_KEY + " = ?", new String[]{Base64.encodeBytes(update.getOld().getKey())});
+ int updateCount = db.update(TABLE_NAME, values, STORAGE_SERVICE_ID + " = ?", new String[]{Base64.encodeBytes(update.getOld().getId().getRaw())});
if (updateCount < 1) {
throw new AssertionError("Had an update, but it didn't match any rows!");
}
- RecipientId recipientId = getByStorageKeyOrThrow(update.getNew().getKey());
+ RecipientId recipientId = getByStorageKeyOrThrow(update.getNew().getId().getRaw());
- if (update.profileKeyChanged()) {
+ if (StorageSyncHelper.profileKeyChanged(update)) {
clearProfileKeyCredential(recipientId);
}
@@ -515,7 +536,7 @@ public class RecipientDatabase extends Database {
if (update.getNew().getIdentityKey().isPresent()) {
IdentityKey identityKey = new IdentityKey(update.getNew().getIdentityKey().get(), 0);
- DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(recipientId, identityKey, StorageSyncHelper.remoteToLocalIdentityStatus(update.getNew().getIdentityState()));
+ DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.getNew().getIdentityState()));
}
Optional newIdentityRecord = identityDatabase.getIdentity(recipientId);
@@ -532,19 +553,32 @@ public class RecipientDatabase extends Database {
} catch (InvalidKeyException e) {
Log.w(TAG, "Failed to process identity key during update! Skipping.", e);
}
+
+ threadDatabase.setArchived(recipientId, update.getNew().isArchived());
+ Recipient.live(recipientId).refresh();
}
for (SignalGroupV1Record insert : groupV1Inserts) {
db.insertOrThrow(TABLE_NAME, null, getValuesForStorageGroupV1(insert));
+
+ Recipient recipient = Recipient.externalGroup(context, GroupId.v1(insert.getGroupId()));
+
+ threadDatabase.setArchived(recipient.getId(), insert.isArchived());
+ recipient.live().refresh();
}
- for (StorageSyncHelper.GroupV1Update update : groupV1Updates) {
+ for (RecordUpdate update : groupV1Updates) {
ContentValues values = getValuesForStorageGroupV1(update.getNew());
- int updateCount = db.update(TABLE_NAME, values, STORAGE_SERVICE_KEY + " = ?", new String[]{Base64.encodeBytes(update.getOld().getKey())});
+ int updateCount = db.update(TABLE_NAME, values, STORAGE_SERVICE_ID + " = ?", new String[]{Base64.encodeBytes(update.getOld().getId().getRaw())});
if (updateCount < 1) {
throw new AssertionError("Had an update, but it didn't match any rows!");
}
+
+ Recipient recipient = Recipient.externalGroup(context, GroupId.v1(update.getOld().getGroupId()));
+
+ threadDatabase.setArchived(recipient.getId(), update.getNew().isArchived());
+ recipient.live().refresh();
}
db.setTransactionSuccessful();
@@ -553,6 +587,27 @@ public class RecipientDatabase extends Database {
}
}
+ public void applyStorageSyncUpdates(@NonNull StorageId storageId, SignalAccountRecord update) {
+ SQLiteDatabase db = databaseHelper.getWritableDatabase();
+
+ ContentValues values = new ContentValues();
+ ProfileName profileName = ProfileName.fromParts(update.getGivenName().orNull(), update.getFamilyName().orNull());
+
+ values.put(PROFILE_GIVEN_NAME, profileName.getGivenName());
+ values.put(PROFILE_FAMILY_NAME, profileName.getFamilyName());
+ values.put(PROFILE_JOINED_NAME, profileName.toString());
+ values.put(PROFILE_KEY, update.getProfileKey().transform(Base64::encodeBytes).orNull());
+ values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(update.getId().getRaw()));
+ values.put(DIRTY, DirtyState.CLEAN.getId());
+
+ int updateCount = db.update(TABLE_NAME, values, STORAGE_SERVICE_ID + " = ?", new String[]{Base64.encodeBytes(storageId.getRaw())});
+ if (updateCount < 1) {
+ throw new AssertionError("Account update didn't match any rows!");
+ }
+
+ Recipient.self().live().refresh();
+ }
+
public void updatePhoneNumbers(@NonNull Map mapping) {
if (mapping.isEmpty()) return;
@@ -577,7 +632,7 @@ public class RecipientDatabase extends Database {
private @NonNull RecipientId getByStorageKeyOrThrow(byte[] storageKey) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
- String query = STORAGE_SERVICE_KEY + " = ?";
+ String query = STORAGE_SERVICE_ID + " = ?";
String[] args = new String[]{Base64.encodeBytes(storageKey)};
try (Cursor cursor = db.query(TABLE_NAME, ID_PROJECTION, query, args, null, null, null)) {
@@ -598,27 +653,28 @@ public class RecipientDatabase extends Database {
}
ProfileName profileName = ProfileName.fromParts(contact.getGivenName().orNull(), contact.getFamilyName().orNull());
+ String username = contact.getUsername().orNull();
values.put(PHONE, contact.getAddress().getNumber().orNull());
values.put(PROFILE_GIVEN_NAME, profileName.getGivenName());
values.put(PROFILE_FAMILY_NAME, profileName.getFamilyName());
values.put(PROFILE_JOINED_NAME, profileName.toString());
values.put(PROFILE_KEY, contact.getProfileKey().transform(Base64::encodeBytes).orNull());
- values.put(USERNAME, contact.getUsername().orNull());
+ values.put(USERNAME, TextUtils.isEmpty(username) ? null : username);
values.put(PROFILE_SHARING, contact.isProfileSharingEnabled() ? "1" : "0");
values.put(BLOCKED, contact.isBlocked() ? "1" : "0");
- values.put(STORAGE_SERVICE_KEY, Base64.encodeBytes(contact.getKey()));
+ values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(contact.getId().getRaw()));
values.put(DIRTY, DirtyState.CLEAN.getId());
return values;
}
private static @NonNull ContentValues getValuesForStorageGroupV1(@NonNull SignalGroupV1Record groupV1) {
ContentValues values = new ContentValues();
- values.put(GROUP_ID, GroupUtil.getEncodedId(groupV1.getGroupId(), false));
+ values.put(GROUP_ID, GroupId.v1(groupV1.getGroupId()).toString());
values.put(GROUP_TYPE, GroupType.SIGNAL_V1.getId());
values.put(PROFILE_SHARING, groupV1.isProfileSharingEnabled() ? "1" : "0");
values.put(BLOCKED, groupV1.isBlocked() ? "1" : "0");
- values.put(STORAGE_SERVICE_KEY, Base64.encodeBytes(groupV1.getKey()));
+ values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(groupV1.getId().getRaw()));
values.put(DIRTY, DirtyState.CLEAN.getId());
return values;
}
@@ -638,40 +694,46 @@ public class RecipientDatabase extends Database {
}
/**
- * @return All storage keys, excluding the ones that need to be deleted.
+ * @return All storage ids for ContactRecords, excluding the ones that need to be deleted.
*/
- public List getAllStorageSyncKeys() {
- return new ArrayList<>(getAllStorageSyncKeysMap().values());
+ public List getContactStorageSyncIds() {
+ return new ArrayList<>(getContactStorageSyncIdsMap().values());
}
/**
- * @return All storage keys, excluding the ones that need to be deleted.
+ * @return All storage IDs for ContactRecords, excluding the ones that need to be deleted.
*/
- public Map getAllStorageSyncKeysMap() {
- SQLiteDatabase db = databaseHelper.getReadableDatabase();
- String query = STORAGE_SERVICE_KEY + " NOT NULL AND " + DIRTY + " != ?";
- String[] args = new String[]{String.valueOf(DirtyState.DELETE)};
- Map out = new HashMap<>();
+ public @NonNull Map getContactStorageSyncIdsMap() {
+ SQLiteDatabase db = databaseHelper.getReadableDatabase();
+ String query = STORAGE_SERVICE_ID + " NOT NULL AND " + DIRTY + " != ? AND " + ID + " != ?";
+ String[] args = new String[]{String.valueOf(DirtyState.DELETE), Recipient.self().getId().serialize() };
+ Map out = new HashMap<>();
- try (Cursor cursor = db.query(TABLE_NAME, new String[] { ID, STORAGE_SERVICE_KEY }, query, args, null, null, null)) {
+ try (Cursor cursor = db.query(TABLE_NAME, new String[] { ID, STORAGE_SERVICE_ID, GROUP_TYPE }, query, args, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
RecipientId id = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ID)));
- String encodedKey = cursor.getString(cursor.getColumnIndexOrThrow(STORAGE_SERVICE_KEY));
+ String encodedKey = cursor.getString(cursor.getColumnIndexOrThrow(STORAGE_SERVICE_ID));
+ GroupType groupType = GroupType.fromId(cursor.getInt(cursor.getColumnIndexOrThrow(GROUP_TYPE)));
+ byte[] key = Base64.decodeOrThrow(encodedKey);
- out.put(id, Base64.decodeOrThrow(encodedKey));
+ if (groupType == GroupType.NONE) {
+ out.put(id, StorageId.forContact(key));
+ } else {
+ out.put(id, StorageId.forGroupV1(key));
+ }
}
}
return out;
}
- private @NonNull RecipientSettings getRecipientSettings(@NonNull Cursor cursor) {
+ private static @NonNull RecipientSettings getRecipientSettings(@NonNull Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
UUID uuid = UuidUtil.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(UUID)));
String username = cursor.getString(cursor.getColumnIndexOrThrow(USERNAME));
String e164 = cursor.getString(cursor.getColumnIndexOrThrow(PHONE));
String email = cursor.getString(cursor.getColumnIndexOrThrow(EMAIL));
- String groupId = cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID));
+ GroupId groupId = GroupId.parseNullable(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)));
int groupType = cursor.getInt(cursor.getColumnIndexOrThrow(GROUP_TYPE));
boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKED)) == 1;
String messageRingtone = cursor.getString(cursor.getColumnIndexOrThrow(MESSAGE_RINGTONE));
@@ -699,7 +761,7 @@ public class RecipientDatabase extends Database {
boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1;
int uuidCapabilityValue = cursor.getInt(cursor.getColumnIndexOrThrow(UUID_CAPABILITY));
int groupsV2CapabilityValue = cursor.getInt(cursor.getColumnIndexOrThrow(GROUPS_V2_CAPABILITY));
- String storageKeyRaw = cursor.getString(cursor.getColumnIndexOrThrow(STORAGE_SERVICE_KEY));
+ String storageKeyRaw = cursor.getString(cursor.getColumnIndexOrThrow(STORAGE_SERVICE_ID));
String identityKeyRaw = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
int identityStatusRaw = cursor.getInt(cursor.getColumnIndexOrThrow(IDENTITY_STATUS));
@@ -911,7 +973,7 @@ public class RecipientDatabase extends Database {
if (update(updateQuery, valuesToSet)) {
markDirty(id, DirtyState.UPDATE);
Recipient.live(id).refresh();
- ApplicationDependencies.getJobManager().add(new StorageSyncJob());
+ StorageSyncHelper.scheduleSyncForDataChange();
return true;
} else {
return false;
@@ -957,7 +1019,7 @@ public class RecipientDatabase extends Database {
if (update(id, contentValues)) {
markDirty(id, DirtyState.UPDATE);
Recipient.live(id).refresh();
- ApplicationDependencies.getJobManager().add(new StorageSyncJob());
+ StorageSyncHelper.scheduleSyncForDataChange();
}
}
@@ -966,6 +1028,11 @@ public class RecipientDatabase extends Database {
contentValues.put(SIGNAL_PROFILE_AVATAR, profileAvatar);
if (update(id, contentValues)) {
Recipient.live(id).refresh();
+
+ if (id.equals(Recipient.self().getId())) {
+ markDirty(id, DirtyState.UPDATE);
+ StorageSyncHelper.scheduleSyncForDataChange();
+ }
}
}
@@ -975,7 +1042,7 @@ public class RecipientDatabase extends Database {
if (update(id, contentValues)) {
markDirty(id, DirtyState.UPDATE);
Recipient.live(id).refresh();
- ApplicationDependencies.getJobManager().add(new StorageSyncJob());
+ StorageSyncHelper.scheduleSyncForDataChange();
}
}
@@ -993,6 +1060,7 @@ public class RecipientDatabase extends Database {
if (update(id, contentValues)) {
markDirty(id, DirtyState.UPDATE);
Recipient.live(id).refresh();
+ StorageSyncHelper.scheduleSyncForDataChange();
}
}
@@ -1008,8 +1076,10 @@ public class RecipientDatabase extends Database {
ContentValues contentValues = new ContentValues(1);
contentValues.put(USERNAME, username);
- update(id, contentValues);
- Recipient.live(id).refresh();
+ if (update(id, contentValues)) {
+ Recipient.live(id).refresh();
+ StorageSyncHelper.scheduleSyncForDataChange();
+ }
}
public void clearUsernameIfExists(@NonNull String username) {
@@ -1106,7 +1176,7 @@ public class RecipientDatabase extends Database {
contentValues.put(REGISTERED, registeredState.getId());
if (registeredState == RegisteredState.REGISTERED) {
- contentValues.put(STORAGE_SERVICE_KEY, Base64.encodeBytes(StorageSyncHelper.generateKey()));
+ contentValues.put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey()));
}
if (update(id, contentValues)) {
@@ -1213,9 +1283,8 @@ public class RecipientDatabase extends Database {
String selection = BLOCKED + " = ? AND " +
REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
- "(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " +
"(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + SEARCH_PROFILE_NAME + " NOT NULL OR " + USERNAME + " NOT NULL)";
- String[] args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()), "1" };
+ String[] args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()) };
String orderBy = SORT_NAME + ", " + SYSTEM_DISPLAY_NAME + ", " + SEARCH_PROFILE_NAME + ", " + USERNAME + ", " + PHONE;
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy);
@@ -1228,14 +1297,13 @@ public class RecipientDatabase extends Database {
String selection = BLOCKED + " = ? AND " +
REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
- "(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ? OR " + USERNAME + " NOT NULL) AND " +
"(" +
PHONE + " LIKE ? OR " +
SYSTEM_DISPLAY_NAME + " LIKE ? OR " +
SEARCH_PROFILE_NAME + " LIKE ? OR " +
USERNAME + " LIKE ?" +
")";
- String[] args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()), "1", query, query, query, query };
+ String[] args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()), query, query, query, query };
String orderBy = SORT_NAME + ", " + SYSTEM_DISPLAY_NAME + ", " + SEARCH_PROFILE_NAME + ", " + PHONE;
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy);
@@ -1338,10 +1406,10 @@ public class RecipientDatabase extends Database {
db.update(TABLE_NAME, setBlocked, UUID + " = ?", new String[] { uuid });
}
- List groupIdStrings = Stream.of(groupIds).map(g -> GroupUtil.getEncodedId(g, false)).toList();
+ List groupIdStrings = Stream.of(groupIds).map(GroupId::v1).toList();
- for (String groupId : groupIdStrings) {
- db.update(TABLE_NAME, setBlocked, GROUP_ID + " = ?", new String[] { groupId });
+ for (GroupId groupId : groupIdStrings) {
+ db.update(TABLE_NAME, setBlocked, GROUP_ID + " = ?", new String[] { groupId.toString() });
}
db.setTransactionSuccessful();
@@ -1359,7 +1427,7 @@ public class RecipientDatabase extends Database {
try {
for (Map.Entry entry : keys.entrySet()) {
ContentValues values = new ContentValues();
- values.put(STORAGE_SERVICE_KEY, Base64.encodeBytes(entry.getValue()));
+ values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(entry.getValue()));
db.update(TABLE_NAME, values, ID_WHERE, new String[] { entry.getKey().serialize() });
}
@@ -1399,7 +1467,7 @@ public class RecipientDatabase extends Database {
query += "(" + DIRTY + " < ? OR " + DIRTY + " = ?)";
args = SqlUtil.appendArg(args, String.valueOf(DirtyState.DELETE.getId()));
- contentValues.put(STORAGE_SERVICE_KEY, Base64.encodeBytes(StorageSyncHelper.generateKey()));
+ contentValues.put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey()));
break;
case DELETE:
query += "(" + DIRTY + " < ? OR " + DIRTY + " = ?)";
@@ -1528,7 +1596,7 @@ public class RecipientDatabase extends Database {
}
private void markAllRelevantEntriesDirty() {
- String query = SYSTEM_INFO_PENDING + " = ? AND " + STORAGE_SERVICE_KEY + " NOT NULL AND " + DIRTY + " < ?";
+ String query = SYSTEM_INFO_PENDING + " = ? AND " + STORAGE_SERVICE_ID + " NOT NULL AND " + DIRTY + " < ?";
String[] args = new String[] { "1", String.valueOf(DirtyState.UPDATE.getId()) };
ContentValues values = new ContentValues(1);
@@ -1553,6 +1621,10 @@ public class RecipientDatabase extends Database {
}
}
+ private static @NonNull String nullIfEmpty(String column) {
+ return "NULLIF(" + column + ", '')";
+ }
+
public interface ColorUpdater {
MaterialColor update(@NonNull String name, @Nullable String color);
}
@@ -1563,7 +1635,7 @@ public class RecipientDatabase extends Database {
private final String username;
private final String e164;
private final String email;
- private final String groupId;
+ private final GroupId groupId;
private final GroupType groupType;
private final boolean blocked;
private final long muteUntil;
@@ -1590,7 +1662,7 @@ public class RecipientDatabase extends Database {
private final Recipient.Capability uuidCapability;
private final Recipient.Capability groupsV2Capability;
private final InsightsBannerTier insightsBannerTier;
- private final byte[] storageKey;
+ private final byte[] storageId;
private final byte[] identityKey;
private final IdentityDatabase.VerifiedStatus identityStatus;
@@ -1599,7 +1671,7 @@ public class RecipientDatabase extends Database {
@Nullable String username,
@Nullable String e164,
@Nullable String email,
- @Nullable String groupId,
+ @Nullable GroupId groupId,
@NonNull GroupType groupType,
boolean blocked,
long muteUntil,
@@ -1626,7 +1698,7 @@ public class RecipientDatabase extends Database {
Recipient.Capability uuidCapability,
Recipient.Capability groupsV2Capability,
@NonNull InsightsBannerTier insightsBannerTier,
- @Nullable byte[] storageKey,
+ @Nullable byte[] storageId,
@Nullable byte[] identityKey,
@NonNull IdentityDatabase.VerifiedStatus identityStatus)
{
@@ -1662,7 +1734,7 @@ public class RecipientDatabase extends Database {
this.uuidCapability = uuidCapability;
this.groupsV2Capability = groupsV2Capability;
this.insightsBannerTier = insightsBannerTier;
- this.storageKey = storageKey;
+ this.storageId = storageId;
this.identityKey = identityKey;
this.identityStatus = identityStatus;
}
@@ -1687,7 +1759,7 @@ public class RecipientDatabase extends Database {
return email;
}
- public @Nullable String getGroupId() {
+ public @Nullable GroupId getGroupId() {
return groupId;
}
@@ -1795,8 +1867,8 @@ public class RecipientDatabase extends Database {
return groupsV2Capability;
}
- public @Nullable byte[] getStorageKey() {
- return storageKey;
+ public @Nullable byte[] getStorageId() {
+ return storageId;
}
public @Nullable byte[] getIdentityKey() {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsMigrator.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsMigrator.java
index 0ba794ab2..01e47b2a0 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsMigrator.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsMigrator.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
+
import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
@@ -27,6 +28,7 @@ import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteStatement;
+import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -231,7 +233,7 @@ public class SmsMigrator {
List recipientIds = Stream.of(ourRecipients).map(Recipient::getId).toList();
- String ourGroupId = DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(recipientIds, true);
+ GroupId ourGroupId = DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(recipientIds, true);
RecipientId ourGroupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(ourGroupId);
Recipient ourGroupRecipient = Recipient.resolved(ourGroupRecipientId);
long ourThreadId = threadDatabase.getThreadIdFor(ourGroupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/StickerDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/StickerDatabase.java
index c77d90f27..871f25b9e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/StickerDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/StickerDatabase.java
@@ -30,6 +30,8 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
import java.util.List;
public class StickerDatabase extends Database {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/StorageKeyDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/StorageKeyDatabase.java
index f80b3a86b..c94fae16b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/StorageKeyDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/StorageKeyDatabase.java
@@ -12,11 +12,11 @@ import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.util.Base64;
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
+import org.whispersystems.signalservice.api.storage.StorageId;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
/**
@@ -28,11 +28,11 @@ public class StorageKeyDatabase extends Database {
private static final String TABLE_NAME = "storage_key";
private static final String ID = "_id";
private static final String TYPE = "type";
- private static final String KEY = "key";
+ private static final String STORAGE_ID = "key";
- public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
- TYPE + " INTEGER, " +
- KEY + " TEXT UNIQUE)";
+ public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
+ TYPE + " INTEGER, " +
+ STORAGE_ID + " TEXT UNIQUE)";
public static final String[] CREATE_INDEXES = new String[] {
"CREATE INDEX IF NOT EXISTS storage_key_type_index ON " + TABLE_NAME + " (" + TYPE + ");"
@@ -42,14 +42,15 @@ public class StorageKeyDatabase extends Database {
super(context, databaseHelper);
}
- public List getAllKeys() {
- List keys = new ArrayList<>();
+ public List getAllKeys() {
+ List keys = new ArrayList<>();
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
- String keyEncoded = cursor.getString(cursor.getColumnIndexOrThrow(KEY));
+ String keyEncoded = cursor.getString(cursor.getColumnIndexOrThrow(STORAGE_ID));
+ int type = cursor.getInt(cursor.getColumnIndexOrThrow(TYPE));
try {
- keys.add(Base64.decode(keyEncoded));
+ keys.add(StorageId.forType(Base64.decode(keyEncoded), type));
} catch (IOException e) {
throw new AssertionError(e);
}
@@ -59,14 +60,14 @@ public class StorageKeyDatabase extends Database {
return keys;
}
- public @Nullable SignalStorageRecord getByKey(@NonNull byte[] key) {
- String query = KEY + " = ?";
- String[] args = new String[] { Base64.encodeBytes(key) };
+ public @Nullable SignalStorageRecord getById(@NonNull byte[] rawId) {
+ String query = STORAGE_ID + " = ?";
+ String[] args = new String[] { Base64.encodeBytes(rawId) };
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, query, args, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
int type = cursor.getInt(cursor.getColumnIndexOrThrow(TYPE));
- return SignalStorageRecord.forUnknown(key, type);
+ return SignalStorageRecord.forUnknown(StorageId.forType(rawId, type));
} else {
return null;
}
@@ -83,15 +84,15 @@ public class StorageKeyDatabase extends Database {
for (SignalStorageRecord insert : inserts) {
ContentValues values = new ContentValues();
values.put(TYPE, insert.getType());
- values.put(KEY, Base64.encodeBytes(insert.getKey()));
+ values.put(STORAGE_ID, Base64.encodeBytes(insert.getId().getRaw()));
db.insert(TABLE_NAME, null, values);
}
- String deleteQuery = KEY + " = ?";
+ String deleteQuery = STORAGE_ID + " = ?";
for (SignalStorageRecord delete : deletes) {
- String[] args = new String[] { Base64.encodeBytes(delete.getKey()) };
+ String[] args = new String[] { Base64.encodeBytes(delete.getId().getRaw()) };
db.delete(TABLE_NAME, deleteQuery, args);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
index 1091bdd22..4dee70da1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
@@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
+import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
@@ -53,6 +54,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
import java.io.Closeable;
import java.io.IOException;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@@ -422,10 +424,48 @@ public class ThreadDatabase extends Database {
return getConversationList("1");
}
+ public boolean isArchived(@NonNull RecipientId recipientId) {
+ SQLiteDatabase db = databaseHelper.getReadableDatabase();
+ String query = RECIPIENT_ID + " = ?";
+ String[] args = new String[]{ recipientId.serialize() };
+
+ try (Cursor cursor = db.query(TABLE_NAME, new String[] { ARCHIVED }, query, args, null, null, null)) {
+ if (cursor != null && cursor.moveToFirst()) {
+ return cursor.getInt(cursor.getColumnIndexOrThrow(ARCHIVED)) == 1;
+ }
+ }
+
+ return false;
+ }
+
+ public void setArchived(@NonNull RecipientId recipientId, boolean status) {
+ setArchived(Collections.singletonMap(recipientId, status));
+ }
+
+ public void setArchived(@NonNull Map status) {
+ SQLiteDatabase db = databaseHelper.getReadableDatabase();
+
+ db.beginTransaction();
+ try {
+ String query = RECIPIENT_ID + " = ?";
+
+ for (Map.Entry entry : status.entrySet()) {
+ ContentValues values = new ContentValues(1);
+ values.put(ARCHIVED, entry.getValue() ? "1" : "0");
+ db.update(TABLE_NAME, values, query, new String[] { entry.getKey().serialize() });
+ }
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ notifyConversationListListeners();
+ }
+ }
+
public @NonNull Set getArchivedRecipients() {
Set archived = new HashSet<>();
- try (Cursor cursor = DatabaseFactory.getThreadDatabase(context).getArchivedConversationList()) {
+ try (Cursor cursor = getArchivedConversationList()) {
while (cursor != null && cursor.moveToNext()) {
archived.add(RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_ID))));
}
@@ -488,6 +528,12 @@ public class ThreadDatabase extends Database {
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""});
notifyConversationListListeners();
+
+ Recipient recipient = getRecipientForThreadId(threadId);
+ if (recipient != null) {
+ DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipient.getId());
+ StorageSyncHelper.scheduleSyncForDataChange();
+ }
}
public void unarchiveConversation(long threadId) {
@@ -497,6 +543,12 @@ public class ThreadDatabase extends Database {
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""});
notifyConversationListListeners();
+
+ Recipient recipient = getRecipientForThreadId(threadId);
+ if (recipient != null) {
+ DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipient.getId());
+ StorageSyncHelper.scheduleSyncForDataChange();
+ }
}
public void setLastSeen(long threadId) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java
index 1cd078486..f0f91150f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java
@@ -10,9 +10,9 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.provider.ContactsContract;
-import androidx.annotation.Nullable;
import android.text.TextUtils;
-import org.thoughtcrime.securesms.logging.Log;
+
+import androidx.annotation.Nullable;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.i18n.phonenumbers.NumberParseException;
@@ -35,13 +35,14 @@ import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
+import org.thoughtcrime.securesms.groups.GroupId;
+import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.DelimiterUtil;
-import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.MediaUtil;
@@ -1274,7 +1275,7 @@ public class ClassicOpenHelper extends SQLiteOpenHelper {
while (cursor != null && cursor.moveToNext()) {
String address = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"));
- if (!TextUtils.isEmpty(address) && !GroupUtil.isEncodedGroup(address) && !NumberUtil.isValidEmail(address)) {
+ if (!TextUtils.isEmpty(address) && !GroupId.isEncodedGroup(address) && !NumberUtil.isValidEmail(address)) {
Uri lookup = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address));
try (Cursor contactCursor = context.getContentResolver().query(lookup, new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME,
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java
index a1e4e6610..eed375d65 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java
@@ -9,10 +9,10 @@ import androidx.annotation.Nullable;
import net.sqlcipher.database.SQLiteDatabase;
+import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
import org.thoughtcrime.securesms.util.DelimiterUtil;
-import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Util;
import java.util.HashSet;
@@ -153,7 +153,7 @@ public class RecipientIdMigrationHelper {
try (Cursor cursor = db.query("recipient_preferences", null, null, null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
String address = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"));
- boolean isGroup = GroupUtil.isEncodedGroup(address);
+ boolean isGroup = GroupId.isEncodedGroup(address);
boolean isEmail = !isGroup && NumberUtil.isValidEmail(address);
boolean isPhone = !isGroup && !isEmail;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
index 4e6992480..f4ae251b5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
@@ -21,7 +21,8 @@ import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteDatabaseHook;
import net.sqlcipher.database.SQLiteOpenHelper;
-import org.thoughtcrime.securesms.contacts.sync.StorageSyncHelper;
+import org.thoughtcrime.securesms.profiles.ProfileName;
+import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
@@ -44,20 +45,20 @@ import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.StorageKeyDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
+import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.service.KeyCachingService;
+import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.Base64;
-import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.SqlUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import java.io.File;
-import java.io.FilenameFilter;
import java.util.List;
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
@@ -116,8 +117,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int STORAGE_SERVICE_ACTIVE = 50;
private static final int GROUPS_V2_RECIPIENT_CAPABILITY = 51;
private static final int TRANSFER_FILE_CLEANUP = 52;
+ private static final int PROFILE_DATA_MIGRATION = 53;
- private static final int DATABASE_VERSION = 52;
+ private static final int DATABASE_VERSION = 53;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@@ -350,7 +352,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
String displayName = NotificationChannels.getChannelDisplayNameFor(context, systemName, profileName, null, address);
boolean vibrateEnabled = vibrateState == 0 ? TextSecurePreferences.isNotificationVibrateEnabled(context) : vibrateState == 1;
- if (GroupUtil.isEncodedGroup(address)) {
+ if (GroupId.isEncodedGroup(address)) {
try(Cursor groupCursor = db.rawQuery("SELECT title FROM groups WHERE group_id = ?", new String[] { address })) {
if (groupCursor != null && groupCursor.moveToFirst()) {
String title = groupCursor.getString(groupCursor.getColumnIndexOrThrow("title"));
@@ -546,11 +548,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
values.put("phone", localNumber);
values.put("registered", 1);
values.put("profile_sharing", 1);
- values.put("signal_profile_name", TextSecurePreferences.getProfileName(context).getGivenName());
db.insert("recipient", null, values);
} else {
- db.execSQL("UPDATE recipient SET registered = ?, profile_sharing = ?, signal_profile_name = ? WHERE phone = ?",
- new String[] { "1", "1", TextSecurePreferences.getProfileName(context).getGivenName(), localNumber });
+ db.execSQL("UPDATE recipient SET registered = ?, profile_sharing = ? WHERE phone = ?",
+ new String[] { "1", "1", localNumber });
}
}
}
@@ -790,6 +791,17 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
}
}
+ if (oldVersion < PROFILE_DATA_MIGRATION) {
+ String localNumber = TextSecurePreferences.getLocalNumber(context);
+ if (localNumber != null) {
+ String encodedProfileName = PreferenceManager.getDefaultSharedPreferences(context).getString("pref_profile_name", null);
+ ProfileName profileName = ProfileName.fromSerialized(encodedProfileName);
+
+ db.execSQL("UPDATE recipient SET signal_profile_name = ?, profile_family_name = ?, profile_joined_name = ? WHERE phone = ?",
+ new String[] { profileName.getGivenName(), profileName.getFamilyName(), profileName.toString(), localNumber });
+ }
+ }
+
db.setTransactionSuccessful();
} finally {
db.endTransaction();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/experienceupgrades/StickersIntroFragment.java b/app/src/main/java/org/thoughtcrime/securesms/experienceupgrades/StickersIntroFragment.java
deleted file mode 100644
index 917867ef7..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/experienceupgrades/StickersIntroFragment.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.thoughtcrime.securesms.experienceupgrades;
-
-
-import android.content.Context;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-
-import com.airbnb.lottie.LottieAnimationView;
-
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.components.TypingIndicatorView;
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-
-public class StickersIntroFragment extends Fragment {
-
- private Controller controller;
-
- public static StickersIntroFragment newInstance() {
- return new StickersIntroFragment();
- }
-
- public StickersIntroFragment() {}
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
-
- if (!(getActivity() instanceof Controller)) {
- throw new IllegalStateException("Parent activity must implement the Controller interface.");
- }
-
- controller = (Controller) getActivity();
- }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.experience_upgrade_stickers_fragment, container, false);
- View goButton = view.findViewById(R.id.stickers_experience_go_button);
-
- ((LottieAnimationView) view.findViewById(R.id.stickers_experience_animation)).playAnimation();
-
- goButton.setOnClickListener(v -> controller.onStickersFinished());
-
- return view;
- }
-
- public interface Controller {
- void onStickersFinished();
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/gcm/RestStrategy.java b/app/src/main/java/org/thoughtcrime/securesms/gcm/RestStrategy.java
index 1df1ff899..c3822975b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/gcm/RestStrategy.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/gcm/RestStrategy.java
@@ -5,6 +5,7 @@ import androidx.annotation.WorkerThread;
import org.thoughtcrime.securesms.IncomingMessageProcessor;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
+import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.JobTracker;
import org.thoughtcrime.securesms.jobs.MarkerJob;
@@ -75,7 +76,7 @@ public class RestStrategy implements MessageRetriever.Strategy {
jobManager.addListener(markerJob.getId(), new JobTracker.JobListener() {
@Override
- public void onStateChanged(@NonNull JobTracker.JobState jobState) {
+ public void onStateChanged(@NonNull Job job, @NonNull JobTracker.JobState jobState) {
if (jobState.isComplete()) {
jobManager.removeListener(this);
latch.countDown();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java
index 681cf8cf3..6a8a76ff4 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java
@@ -108,7 +108,7 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity
}
private @ColorInt int getConversationColor() {
- return getIntent().getIntExtra(EXTRA_COLOR, ActivityCompat.getColor(this, R.color.signal_primary));
+ return getIntent().getIntExtra(EXTRA_COLOR, ActivityCompat.getColor(this, R.color.core_ultramarine));
}
@Override
diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/cache/EncryptedCacheEncoder.java b/app/src/main/java/org/thoughtcrime/securesms/glide/cache/EncryptedCacheEncoder.java
index 361f7bba9..1d546ccf5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/glide/cache/EncryptedCacheEncoder.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/glide/cache/EncryptedCacheEncoder.java
@@ -13,6 +13,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.SocketException;
public class EncryptedCacheEncoder extends EncryptedCoder implements Encoder {
@@ -42,12 +43,14 @@ public class EncryptedCacheEncoder extends EncryptedCoder implements Encoder members,
@Nullable Bitmap avatar,
@Nullable String name)
@@ -51,7 +51,7 @@ public final class GroupManager {
@WorkerThread
public static boolean leaveGroup(@NonNull Context context, @NonNull Recipient groupRecipient) {
- String groupId = groupRecipient.requireGroupId();
+ GroupId groupId = groupRecipient.requireGroupId();
return V1GroupManager.leaveGroup(context, groupId, groupRecipient);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
index c4e32eb1f..b257b6773 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
@@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.FeatureFlags;
-import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
@@ -63,7 +62,7 @@ public class GroupMessageProcessor {
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
SignalServiceGroup group = message.getGroupInfo().get();
- String id = GroupUtil.getEncodedId(group.getGroupId(), false);
+ GroupId id = GroupId.v1(group.getGroupId());
Optional record = database.getGroup(id);
if (record.isPresent() && group.getType() == Type.UPDATE) {
@@ -73,7 +72,7 @@ public class GroupMessageProcessor {
} else if (record.isPresent() && group.getType() == Type.QUIT) {
return handleGroupLeave(context, content, group, record.get(), outgoing);
} else if (record.isPresent() && group.getType() == Type.REQUEST_INFO) {
- return handleGroupInfoRequest(context, content, group, record.get());
+ return handleGroupInfoRequest(context, content, record.get());
} else {
Log.w(TAG, "Received unknown type, ignoring...");
return null;
@@ -86,7 +85,7 @@ public class GroupMessageProcessor {
boolean outgoing)
{
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
- String id = GroupUtil.getEncodedId(group.getGroupId(), false);
+ GroupId id = GroupId.v1(group.getGroupId());
GroupContext.Builder builder = createGroupContext(group);
builder.setType(GroupContext.Type.UPDATE);
@@ -106,7 +105,7 @@ public class GroupMessageProcessor {
if (FeatureFlags.messageRequests() && (sender.isSystemContact() || sender.isProfileSharing())) {
Log.i(TAG, "Auto-enabling profile sharing because 'adder' is trusted. contact: " + sender.isSystemContact() + ", profileSharing: " + sender.isProfileSharing());
- DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.external(context, id).getId(), true);
+ DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.externalGroup(context, id).getId(), true);
}
return storeMessage(context, content, group, builder.build(), outgoing);
@@ -120,7 +119,7 @@ public class GroupMessageProcessor {
{
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
- String id = GroupUtil.getEncodedId(group.getGroupId(), false);
+ GroupId id = GroupId.v1(group.getGroupId());
Set recordMembers = new HashSet<>(groupRecord.getMembers());
Set messageMembers = new HashSet<>();
@@ -178,13 +177,12 @@ public class GroupMessageProcessor {
private static Long handleGroupInfoRequest(@NonNull Context context,
@NonNull SignalServiceContent content,
- @NonNull SignalServiceGroup group,
@NonNull GroupRecord record)
{
Recipient sender = Recipient.externalPush(context, content.getSender());
if (record.getMembers().contains(sender.getId())) {
- ApplicationDependencies.getJobManager().add(new PushGroupUpdateJob(sender.getId(), group.getGroupId()));
+ ApplicationDependencies.getJobManager().add(new PushGroupUpdateJob(sender.getId(), record.getId()));
}
return null;
@@ -197,7 +195,7 @@ public class GroupMessageProcessor {
boolean outgoing)
{
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
- String id = GroupUtil.getEncodedId(group.getGroupId(), false);
+ GroupId id = GroupId.v1(group.getGroupId());
List members = record.getMembers();
GroupContext.Builder builder = createGroupContext(group);
@@ -222,13 +220,13 @@ public class GroupMessageProcessor {
{
if (group.getAvatar().isPresent()) {
ApplicationDependencies.getJobManager()
- .add(new AvatarDownloadJob(group.getGroupId()));
+ .add(new AvatarDownloadJob(GroupId.v1(group.getGroupId())));
}
try {
if (outgoing) {
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
- RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(group.getGroupId(), false));
+ RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupId.v1(group.getGroupId()));
Recipient recipient = Recipient.resolved(recipientId);
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, false, null, Collections.emptyList(), Collections.emptyList());
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
@@ -240,7 +238,7 @@ public class GroupMessageProcessor {
} else {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
String body = Base64.encodeBytes(storage.toByteArray());
- IncomingTextMessage incoming = new IncomingTextMessage(Recipient.externalPush(context, content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(GroupUtil.getEncodedId(group.getGroupId(), false)), 0, content.isNeedsReceipt());
+ IncomingTextMessage incoming = new IncomingTextMessage(Recipient.externalPush(context, content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(GroupId.v1(group.getGroupId())), 0, content.isNeedsReceipt());
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body);
Optional insertResult = smsDatabase.insertMessageInbox(groupMessage);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/V1GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/V1GroupManager.java
index ae01f6479..451ea29a3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/V1GroupManager.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/V1GroupManager.java
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.groups;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
@@ -11,7 +12,6 @@ import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment;
-import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
@@ -28,12 +28,10 @@ import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
-import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -49,7 +47,7 @@ final class V1GroupManager {
{
final byte[] avatarBytes = BitmapUtil.toByteArray(avatar);
final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
- final String groupId = GroupUtil.getEncodedId(groupDatabase.allocateGroupId(), mms);
+ final GroupId groupId = GroupDatabase.allocateGroupId(mms);
final RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
final Recipient groupRecipient = Recipient.resolved(groupRecipientId);
@@ -67,7 +65,7 @@ final class V1GroupManager {
}
static GroupActionResult updateGroup(@NonNull Context context,
- @NonNull String groupId,
+ @NonNull GroupId groupId,
@NonNull Set memberAddresses,
@Nullable Bitmap avatar,
@Nullable String name)
@@ -81,7 +79,7 @@ final class V1GroupManager {
groupDatabase.updateTitle(groupId, name);
groupDatabase.updateAvatar(groupId, avatarBytes);
- if (!GroupUtil.isMmsGroup(groupId)) {
+ if (!groupId.isMmsGroup()) {
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes);
} else {
RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
@@ -92,48 +90,44 @@ final class V1GroupManager {
}
private static GroupActionResult sendGroupUpdate(@NonNull Context context,
- @NonNull String groupId,
+ @NonNull GroupId groupId,
@NonNull Set members,
@Nullable String groupName,
@Nullable byte[] avatar)
{
- try {
- Attachment avatarAttachment = null;
- RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
- Recipient groupRecipient = Recipient.resolved(groupRecipientId);
+ Attachment avatarAttachment = null;
+ RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
+ Recipient groupRecipient = Recipient.resolved(groupRecipientId);
- List uuidMembers = new LinkedList<>();
- List e164Members = new LinkedList<>();
+ List uuidMembers = new LinkedList<>();
+ List e164Members = new LinkedList<>();
- for (RecipientId member : members) {
- Recipient recipient = Recipient.resolved(member);
- uuidMembers.add(GroupMessageProcessor.createMember(RecipientUtil.toSignalServiceAddress(context, recipient)));
- }
-
- GroupContext.Builder groupContextBuilder = GroupContext.newBuilder()
- .setId(ByteString.copyFrom(GroupUtil.getDecodedId(groupId)))
- .setType(GroupContext.Type.UPDATE)
- .addAllMembersE164(e164Members)
- .addAllMembers(uuidMembers);
- if (groupName != null) groupContextBuilder.setName(groupName);
- GroupContext groupContext = groupContextBuilder.build();
-
- if (avatar != null) {
- Uri avatarUri = BlobProvider.getInstance().forData(avatar).createForSingleUseInMemory();
- avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null, null, null);
- }
-
- OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, false, null, Collections.emptyList(), Collections.emptyList());
- long threadId = MessageSender.send(context, outgoingMessage, -1, false, null);
-
- return new GroupActionResult(groupRecipient, threadId);
- } catch (IOException e) {
- throw new AssertionError(e);
+ for (RecipientId member : members) {
+ Recipient recipient = Recipient.resolved(member);
+ uuidMembers.add(GroupMessageProcessor.createMember(RecipientUtil.toSignalServiceAddress(context, recipient)));
}
+
+ GroupContext.Builder groupContextBuilder = GroupContext.newBuilder()
+ .setId(ByteString.copyFrom(groupId.getDecodedId()))
+ .setType(GroupContext.Type.UPDATE)
+ .addAllMembersE164(e164Members)
+ .addAllMembers(uuidMembers);
+ if (groupName != null) groupContextBuilder.setName(groupName);
+ GroupContext groupContext = groupContextBuilder.build();
+
+ if (avatar != null) {
+ Uri avatarUri = BlobProvider.getInstance().forData(avatar).createForSingleUseInMemory();
+ avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null, null, null);
+ }
+
+ OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, false, null, Collections.emptyList(), Collections.emptyList());
+ long threadId = MessageSender.send(context, outgoingMessage, -1, false, null);
+
+ return new GroupActionResult(groupRecipient, threadId);
}
@WorkerThread
- static boolean leaveGroup(@NonNull Context context, @NonNull String groupId, @NonNull Recipient groupRecipient) {
+ static boolean leaveGroup(@NonNull Context context, @NonNull GroupId groupId, @NonNull Recipient groupRecipient) {
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
Optional leaveMessage = GroupUtil.createGroupLeaveMessage(context, groupRecipient);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java
index c66908fc0..750ae7ae6 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java
@@ -320,7 +320,7 @@ public final class ImageEditorView extends FrameLayout {
private EditSession startADrawingSession(@NonNull PointF point) {
BezierDrawingRenderer renderer = new BezierDrawingRenderer(color, thickness * Bounds.FULL_BOUNDS.width(), cap, model.findCropRelativeToRoot());
- EditorElement element = new EditorElement(renderer);
+ EditorElement element = new EditorElement(renderer, EditorModel.Z_DRAWING);
model.addElementCentered(element, 1);
Matrix elementInverseMatrix = model.findElementInverseMatrix(element, viewMatrix);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java
index 9460907f9..cb30c3106 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java
@@ -3,12 +3,15 @@ package org.thoughtcrime.securesms.imageeditor.model;
import android.graphics.Matrix;
import android.os.Parcel;
import android.os.Parcelable;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.imageeditor.Renderer;
import org.thoughtcrime.securesms.imageeditor.RendererContext;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -32,10 +35,13 @@ import java.util.UUID;
*/
public final class EditorElement implements Parcelable {
+ private static final Comparator Z_ORDER_COMPARATOR = (e1, e2) -> Integer.compare(e1.zOrder, e2.zOrder);
+
private final UUID id;
private final EditorFlags flags;
private final Matrix localMatrix = new Matrix();
private final Matrix editorMatrix = new Matrix();
+ private final int zOrder;
@Nullable
private final Renderer renderer;
@@ -54,9 +60,14 @@ public final class EditorElement implements Parcelable {
private AlphaAnimation alphaAnimation = AlphaAnimation.NULL_1;
public EditorElement(@Nullable Renderer renderer) {
+ this(renderer, 0);
+ }
+
+ public EditorElement(@Nullable Renderer renderer, int zOrder) {
this.id = UUID.randomUUID();
this.flags = new EditorFlags();
this.renderer = renderer;
+ this.zOrder = zOrder;
}
private EditorElement(Parcel in) {
@@ -64,6 +75,7 @@ public final class EditorElement implements Parcelable {
flags = new EditorFlags(in.readInt());
ParcelUtils.readMatrix(localMatrix, in);
renderer = in.readParcelable(Renderer.class.getClassLoader());
+ zOrder = in.readInt();
in.readTypedList(children, EditorElement.CREATOR);
}
@@ -127,6 +139,7 @@ public final class EditorElement implements Parcelable {
public void addElement(@NonNull EditorElement element) {
children.add(element);
+ Collections.sort(children, Z_ORDER_COMPARATOR);
}
public Matrix getLocalMatrix() {
@@ -328,6 +341,7 @@ public final class EditorElement implements Parcelable {
dest.writeInt(this.flags.asInt());
ParcelUtils.writeMatrix(dest, localMatrix);
dest.writeParcelable(renderer, flags);
+ dest.writeInt(zOrder);
dest.writeTypedList(children);
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java
index a102642d9..79cca5bab 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java
@@ -35,6 +35,10 @@ import java.util.UUID;
*/
public final class EditorModel implements Parcelable, RendererContext.Ready {
+ public static final int Z_DRAWING = 0;
+ public static final int Z_STICKERS = 0;
+ public static final int Z_TEXT = 1;
+
private static final Runnable NULL_RUNNABLE = () -> {
};
@@ -545,9 +549,17 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
*/
@WorkerThread
public @NonNull Bitmap render(@NonNull Context context) {
+ return render(context, null);
+ }
+
+ /**
+ * Blocking render of the model.
+ */
+ @WorkerThread
+ public @NonNull Bitmap render(@NonNull Context context, @Nullable Point size) {
EditorElement image = editorElementHierarchy.getFlipRotate();
RectF cropRect = editorElementHierarchy.getCropRect();
- Point outputSize = getOutputSize();
+ Point outputSize = size != null ? size : getOutputSize();
Bitmap bitmap = Bitmap.createBitmap(outputSize.x, outputSize.y, Bitmap.Config.ARGB_8888);
try {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java
index dee0b3968..e02ff8496 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java
@@ -67,14 +67,14 @@ public class InsightsRepository implements InsightsDashboardViewModel.Repository
public void getUserAvatar(@NonNull Consumer avatarConsumer) {
SimpleTask.run(() -> {
Recipient self = Recipient.self().resolve();
- String name = Optional.fromNullable(self.getName(context)).or(Optional.fromNullable(TextSecurePreferences.getProfileName(context).toString())).or("");
+ String name = Optional.fromNullable(self.getName(context)).or("");
MaterialColor fallbackColor = self.getColor();
if (fallbackColor == ContactColors.UNKNOWN_COLOR && !TextUtils.isEmpty(name)) {
fallbackColor = ContactColors.generateFor(name);
}
- return new InsightsUserAvatar(new ProfileContactPhoto(self.getId(), String.valueOf(TextSecurePreferences.getProfileAvatarId(context))),
+ return new InsightsUserAvatar(new ProfileContactPhoto(self, self.getProfileAvatar()),
fallbackColor,
new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40));
}, avatarConsumer::accept);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java
index 539677d25..340fad3af 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java
@@ -85,7 +85,7 @@ class JobController {
if (chainExceedsMaximumInstances(chain)) {
Job solo = chain.get(0).get(0);
- jobTracker.onStateChange(solo.getId(), JobTracker.JobState.IGNORED);
+ jobTracker.onStateChange(solo, JobTracker.JobState.IGNORED);
Log.w(TAG, JobLogger.format(solo, "Already at the max instance count of " + solo.getParameters().getMaxInstances() + ". Skipping."));
return;
}
@@ -101,7 +101,7 @@ class JobController {
List> chain = Collections.singletonList(Collections.singletonList(job));
if (chainExceedsMaximumInstances(chain)) {
- jobTracker.onStateChange(job.getId(), JobTracker.JobState.IGNORED);
+ jobTracker.onStateChange(job, JobTracker.JobState.IGNORED);
Log.w(TAG, JobLogger.format(job, "Already at the max instance count of " + job.getParameters().getMaxInstances() + ". Skipping."));
return;
}
@@ -149,7 +149,7 @@ class JobController {
String serializedData = dataSerializer.serialize(job.serialize());
jobStorage.updateJobAfterRetry(job.getId(), false, nextRunAttempt, nextRunAttemptTime, serializedData);
- jobTracker.onStateChange(job.getId(), JobTracker.JobState.PENDING);
+ jobTracker.onStateChange(job, JobTracker.JobState.PENDING);
List constraints = Stream.of(jobStorage.getConstraintSpecs(job.getId()))
.map(ConstraintSpec::getFactoryKey)
@@ -172,7 +172,7 @@ class JobController {
@WorkerThread
synchronized void onSuccess(@NonNull Job job) {
jobStorage.deleteJob(job.getId());
- jobTracker.onStateChange(job.getId(), JobTracker.JobState.SUCCESS);
+ jobTracker.onStateChange(job, JobTracker.JobState.SUCCESS);
notifyAll();
}
@@ -196,7 +196,7 @@ class JobController {
all.addAll(dependents);
jobStorage.deleteJobs(Stream.of(all).map(Job::getId).toList());
- Stream.of(all).forEach(j -> jobTracker.onStateChange(j.getId(), JobTracker.JobState.FAILURE));
+ Stream.of(all).forEach(j -> jobTracker.onStateChange(j, JobTracker.JobState.FAILURE));
return dependents;
}
@@ -224,7 +224,7 @@ class JobController {
jobStorage.updateJobRunningState(job.getId(), true);
runningJobs.put(job.getId(), job);
- jobTracker.onStateChange(job.getId(), JobTracker.JobState.RUNNING);
+ jobTracker.onStateChange(job, JobTracker.JobState.RUNNING);
return job;
} catch (InterruptedException e) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java
index 47ef72424..c0cbec208 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java
@@ -100,13 +100,21 @@ public class JobManager implements ConstraintObserver.Notifier {
});
}
+ /**
+ * Convenience method for {@link #addListener(JobTracker.JobFilter, JobTracker.JobListener)} that
+ * takes in an ID to filter on.
+ */
+ public void addListener(@NonNull String id, @NonNull JobTracker.JobListener listener) {
+ jobTracker.addListener(new JobIdFilter(id), listener);
+ }
+
/**
* Add a listener to subscribe to job state updates. Listeners will be invoked on an arbitrary
* background thread. You must eventually call {@link #removeListener(JobTracker.JobListener)} to avoid
* memory leaks.
*/
- public void addListener(@NonNull String id, @NonNull JobTracker.JobListener listener) {
- jobTracker.addListener(id, listener);
+ public void addListener(@NonNull JobTracker.JobFilter filter, @NonNull JobTracker.JobListener listener) {
+ jobTracker.addListener(filter, listener);
}
/**
@@ -127,7 +135,7 @@ public class JobManager implements ConstraintObserver.Notifier {
* Enqueues a single job that depends on a collection of job ID's.
*/
public void add(@NonNull Job job, @NonNull Collection dependsOn) {
- jobTracker.onStateChange(job.getId(), JobTracker.JobState.PENDING);
+ jobTracker.onStateChange(job, JobTracker.JobState.PENDING);
executor.execute(() -> {
jobController.submitJobWithExistingDependencies(job, dependsOn);
@@ -177,7 +185,7 @@ public class JobManager implements ConstraintObserver.Notifier {
addListener(job.getId(), new JobTracker.JobListener() {
@Override
- public void onStateChanged(@NonNull JobTracker.JobState jobState) {
+ public void onStateChanged(@NonNull Job job, @NonNull JobTracker.JobState jobState) {
if (jobState.isComplete()) {
removeListener(this);
resultState.set(jobState);
@@ -248,7 +256,7 @@ public class JobManager implements ConstraintObserver.Notifier {
private void enqueueChain(@NonNull Chain chain) {
for (List jobList : chain.getJobListChain()) {
for (Job job : jobList) {
- jobTracker.onStateChange(job.getId(), JobTracker.JobState.PENDING);
+ jobTracker.onStateChange(job, JobTracker.JobState.PENDING);
}
}
@@ -270,6 +278,19 @@ public class JobManager implements ConstraintObserver.Notifier {
void onQueueEmpty();
}
+ public static class JobIdFilter implements JobTracker.JobFilter {
+ private final String id;
+
+ public JobIdFilter(@NonNull String id) {
+ this.id = id;
+ }
+
+ @Override
+ public boolean matches(@NonNull Job job) {
+ return id.equals(job.getId());
+ }
+ }
+
/**
* Allows enqueuing work that depends on each other. Jobs that appear later in the chain will
* only run after all jobs earlier in the chain have been completed. If a job fails, all jobs
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobTracker.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobTracker.java
index 1b51c9bb5..d00509462 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobTracker.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobTracker.java
@@ -3,13 +3,15 @@ package org.thoughtcrime.securesms.jobmanager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.annimon.stream.Stream;
+
import org.thoughtcrime.securesms.util.LRUCache;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
-import java.util.Collection;
+import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;
/**
@@ -17,11 +19,13 @@ import java.util.concurrent.Executor;
*/
public class JobTracker {
- private final Map trackingStates;
- private final Executor listenerExecutor;
+ private final Map jobInfos;
+ private final List jobListeners;
+ private final Executor listenerExecutor;
JobTracker() {
- this.trackingStates = new LRUCache<>(1000);
+ this.jobInfos = new LRUCache<>(1000);
+ this.jobListeners = new ArrayList<>();
this.listenerExecutor = SignalExecutors.BOUNDED;
}
@@ -30,54 +34,63 @@ public class JobTracker {
* background thread. You must eventually call {@link #removeListener(JobListener)} to avoid
* memory leaks.
*/
- synchronized void addListener(@NonNull String id, @NonNull JobListener jobListener) {
- TrackingState state = getOrCreateTrackingState(id);
- JobState currentJobState = state.getJobState();
+ synchronized void addListener(@NonNull JobFilter filter, @NonNull JobListener listener) {
+ jobListeners.add(new ListenerInfo(filter, listener));
- state.addListener(jobListener);
-
- if (currentJobState != null) {
- listenerExecutor.execute(() -> jobListener.onStateChanged(currentJobState));
- }
+ Stream.of(jobInfos.values())
+ .filter(info -> info.getJobState() != null)
+ .filter(info -> filter.matches(info.getJob()))
+ .forEach(state-> {
+ //noinspection ConstantConditions We already filter for nulls above
+ listenerExecutor.execute(() -> listener.onStateChanged(state.getJob(), state.getJobState()));
+ });
}
/**
* Unsubscribe the provided listener from all job updates.
*/
- synchronized void removeListener(@NonNull JobListener jobListener) {
- Collection allTrackingState = trackingStates.values();
+ synchronized void removeListener(@NonNull JobListener listener) {
+ Iterator iter = jobListeners.iterator();
- for (TrackingState state : allTrackingState) {
- state.removeListener(jobListener);
+ while (iter.hasNext()) {
+ if (listener.equals(iter.next().getListener())) {
+ iter.remove();
+ }
}
}
/**
* Update the state of a job with the associated ID.
*/
- synchronized void onStateChange(@NonNull String id, @NonNull JobState jobState) {
- TrackingState trackingState = getOrCreateTrackingState(id);
- trackingState.setJobState(jobState);
+ synchronized void onStateChange(@NonNull Job job, @NonNull JobState state) {
+ getOrCreateJobInfo(job).setJobState(state);
- for (JobListener listener : trackingState.getListeners()) {
- listenerExecutor.execute(() -> listener.onStateChanged(jobState));
- }
+ Stream.of(jobListeners)
+ .filter(info -> info.getFilter().matches(job))
+ .map(ListenerInfo::getListener)
+ .forEach(listener -> {
+ listenerExecutor.execute(() -> listener.onStateChanged(job, state));
+ });
}
- private @NonNull TrackingState getOrCreateTrackingState(@NonNull String id) {
- TrackingState state = trackingStates.get(id);
+ private @NonNull JobInfo getOrCreateJobInfo(@NonNull Job job) {
+ JobInfo jobInfo = jobInfos.get(job.getId());
- if (state == null) {
- state = new TrackingState();
+ if (jobInfo == null) {
+ jobInfo = new JobInfo(job);
}
- trackingStates.put(id, state);
+ jobInfos.put(job.getId(), jobInfo);
- return state;
+ return jobInfo;
+ }
+
+ public interface JobFilter {
+ boolean matches(@NonNull Job job);
}
public interface JobListener {
- void onStateChanged(@NonNull JobState jobState);
+ void onStateChanged(@NonNull Job job, @NonNull JobState jobState);
}
public enum JobState {
@@ -94,21 +107,34 @@ public class JobTracker {
}
}
- private static class TrackingState {
- private JobState jobState;
+ private static class ListenerInfo {
+ private final JobFilter filter;
+ private final JobListener listener;
- private final CopyOnWriteArraySet listeners = new CopyOnWriteArraySet<>();
-
- void addListener(@NonNull JobListener jobListener) {
- listeners.add(jobListener);
+ private ListenerInfo(JobFilter filter, JobListener listener) {
+ this.filter = filter;
+ this.listener = listener;
}
- void removeListener(@NonNull JobListener jobListener) {
- listeners.remove(jobListener);
+ @NonNull JobFilter getFilter() {
+ return filter;
}
- @NonNull Collection getListeners() {
- return listeners;
+ @NonNull JobListener getListener() {
+ return listener;
+ }
+ }
+
+ private static class JobInfo {
+ private final Job job;
+ private JobState jobState;
+
+ private JobInfo(Job job) {
+ this.job = job;
+ }
+
+ @NonNull Job getJob() {
+ return job;
}
void setJobState(@NonNull JobState jobState) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java
index aae85b7eb..90b113d86 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java
@@ -1,12 +1,14 @@
package org.thoughtcrime.securesms.jobs;
import android.graphics.Bitmap;
+
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
+import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
@@ -14,7 +16,6 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
-import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Hex;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -36,9 +37,9 @@ public class AvatarDownloadJob extends BaseJob {
private static final String KEY_GROUP_ID = "group_id";
- private byte[] groupId;
+ private @NonNull GroupId groupId;
- public AvatarDownloadJob(@NonNull byte[] groupId) {
+ public AvatarDownloadJob(@NonNull GroupId groupId) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setMaxAttempts(10)
@@ -46,14 +47,14 @@ public class AvatarDownloadJob extends BaseJob {
groupId);
}
- private AvatarDownloadJob(@NonNull Job.Parameters parameters, @NonNull byte[] groupId) {
+ private AvatarDownloadJob(@NonNull Job.Parameters parameters, @NonNull GroupId groupId) {
super(parameters);
this.groupId = groupId;
}
@Override
public @NonNull Data serialize() {
- return new Data.Builder().putString(KEY_GROUP_ID, GroupUtil.getEncodedId(groupId, false)).build();
+ return new Data.Builder().putString(KEY_GROUP_ID, groupId.toString()).build();
}
@Override
@@ -63,9 +64,8 @@ public class AvatarDownloadJob extends BaseJob {
@Override
public void onRun() throws IOException {
- String encodeId = GroupUtil.getEncodedId(groupId, false);
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
- Optional record = database.getGroup(encodeId);
+ Optional record = database.getGroup(groupId);
File attachment = null;
try {
@@ -93,7 +93,7 @@ public class AvatarDownloadJob extends BaseJob {
InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE);
Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500);
- database.updateAvatar(encodeId, avatar);
+ database.updateAvatar(groupId, avatar);
inputStream.close();
}
} catch (BitmapDecodingException | NonSuccessfulResponseCodeException | InvalidMessageException e) {
@@ -116,11 +116,7 @@ public class AvatarDownloadJob extends BaseJob {
public static final class Factory implements Job.Factory {
@Override
public @NonNull AvatarDownloadJob create(@NonNull Parameters parameters, @NonNull Data data) {
- try {
- return new AvatarDownloadJob(parameters, GroupUtil.getDecodedId(data.getString(KEY_GROUP_ID)));
- } catch (IOException e) {
- throw new AssertionError(e);
- }
+ return new AvatarDownloadJob(parameters, GroupId.parse(data.getString(KEY_GROUP_ID)));
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java
index 0dae4d868..ea2637df4 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java
@@ -125,7 +125,7 @@ public class FcmRefreshJob extends BaseJob {
PendingIntent pendingIntent = PendingIntent.getActivity(context, 1122, intent, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationChannels.FAILURES);
- builder.setSmallIcon(R.drawable.icon_notification);
+ builder.setSmallIcon(R.drawable.ic_notification);
builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(),
R.drawable.ic_action_warning_red));
builder.setContentTitle(context.getString(R.string.GcmRefreshJob_Permanent_Signal_communication_failure));
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java
index 0a172bf00..afbe60cae 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java
@@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMi
import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration2;
import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdJobMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.SendReadReceiptsJobMigration;
+import org.thoughtcrime.securesms.migrations.AvatarIdRemovalMigrationJob;
import org.thoughtcrime.securesms.migrations.PassingMigrationJob;
import org.thoughtcrime.securesms.migrations.AvatarMigrationJob;
import org.thoughtcrime.securesms.migrations.CachedAttachmentsMigrationJob;
@@ -85,6 +86,7 @@ public final class JobManagerFactories {
put(RefreshPreKeysJob.KEY, new RefreshPreKeysJob.Factory());
put(RemoteConfigRefreshJob.KEY, new RemoteConfigRefreshJob.Factory());
put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory());
+ put(StorageAccountRestoreJob.KEY, new StorageAccountRestoreJob.Factory());
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory());
put(RetrieveProfileJob.KEY, new RetrieveProfileJob.Factory());
put(RotateCertificateJob.KEY, new RotateCertificateJob.Factory());
@@ -107,6 +109,7 @@ public final class JobManagerFactories {
put(ProfileUploadJob.KEY, new ProfileUploadJob.Factory());
// Migrations
+ put(AvatarIdRemovalMigrationJob.KEY, new AvatarIdRemovalMigrationJob.Factory());
put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory());
put(CachedAttachmentsMigrationJob.KEY, new CachedAttachmentsMigrationJob.Factory());
put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory());
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupJob.java
index 2d7d99063..be14b8086 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupJob.java
@@ -3,12 +3,12 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import androidx.annotation.NonNull;
-import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
+import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
@@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.util.Base64;
-import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
@@ -27,8 +26,6 @@ import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
-import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import java.io.IOException;
import java.util.Collections;
@@ -54,7 +51,7 @@ public class LeaveGroupJob extends BaseJob {
private static final String KEY_MEMBERS = "members";
private static final String KEY_RECIPIENTS = "recipients";
- private final byte[] groupId;
+ private final GroupId groupId;
private final String name;
private final List members;
private final List recipients;
@@ -63,7 +60,7 @@ public class LeaveGroupJob extends BaseJob {
List members = Stream.of(group.resolve().getParticipants()).map(Recipient::getId).toList();
members.remove(Recipient.self().getId());
- return new LeaveGroupJob(GroupUtil.getDecodedIdOrThrow(group.getGroupId().get()),
+ return new LeaveGroupJob(group.getGroupId().get(),
group.resolve().getDisplayName(ApplicationDependencies.getApplication()),
members,
members,
@@ -75,7 +72,7 @@ public class LeaveGroupJob extends BaseJob {
.build());
}
- private LeaveGroupJob(@NonNull byte[] groupId,
+ private LeaveGroupJob(@NonNull GroupId groupId,
@NonNull String name,
@NonNull List members,
@NonNull List recipients,
@@ -90,7 +87,7 @@ public class LeaveGroupJob extends BaseJob {
@Override
public @NonNull Data serialize() {
- return new Data.Builder().putString(KEY_GROUP_ID, Base64.encodeBytes(groupId))
+ return new Data.Builder().putString(KEY_GROUP_ID, Base64.encodeBytes(groupId.getDecodedId()))
.putString(KEY_GROUP_NAME, name)
.putString(KEY_MEMBERS, RecipientId.toSerializedList(members))
.putString(KEY_RECIPIENTS, RecipientId.toSerializedList(recipients))
@@ -128,7 +125,7 @@ public class LeaveGroupJob extends BaseJob {
}
private static @NonNull List deliver(@NonNull Context context,
- @NonNull byte[] groupId,
+ @NonNull GroupId groupId,
@NonNull String name,
@NonNull List members,
@NonNull List destinations)
@@ -138,7 +135,7 @@ public class LeaveGroupJob extends BaseJob {
List addresses = Stream.of(destinations).map(Recipient::resolved).map(t -> RecipientUtil.toSignalServiceAddress(context, t)).toList();
List memberAddresses = Stream.of(members).map(Recipient::resolved).map(t -> RecipientUtil.toSignalServiceAddress(context, t)).toList();
List> unidentifiedAccess = Stream.of(destinations).map(Recipient::resolved).map(recipient -> UnidentifiedAccessUtil.getAccessFor(context, recipient)).toList();
- SignalServiceGroup serviceGroup = new SignalServiceGroup(SignalServiceGroup.Type.QUIT, groupId, name, memberAddresses, null);
+ SignalServiceGroup serviceGroup = new SignalServiceGroup(SignalServiceGroup.Type.QUIT, groupId.getDecodedId(), name, memberAddresses, null);
SignalServiceDataMessage.Builder dataMessage = SignalServiceDataMessage.newBuilder()
.withTimestamp(System.currentTimeMillis())
.asGroupMessage(serviceGroup);
@@ -169,7 +166,7 @@ public class LeaveGroupJob extends BaseJob {
public static class Factory implements Job.Factory {
@Override
public @NonNull LeaveGroupJob create(@NonNull Parameters parameters, @NonNull Data data) {
- return new LeaveGroupJob(Base64.decodeOrThrow(data.getString(KEY_GROUP_ID)),
+ return new LeaveGroupJob(GroupId.v1(Base64.decodeOrThrow(data.getString(KEY_GROUP_ID))),
data.getString(KEY_GROUP_NAME),
RecipientId.fromSerializedList(data.getString(KEY_MEMBERS)),
RecipientId.fromSerializedList(data.getString(KEY_RECIPIENTS)),
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java
index bb3bdf0fd..5468db6da 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.jobs;
import android.net.Uri;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
import org.thoughtcrime.securesms.database.MmsDatabase;
+import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.logging.Log;
@@ -177,11 +179,11 @@ public class MmsDownloadJob extends BaseJob {
int subscriptionId, @Nullable RecipientId notificationFrom)
throws MmsException
{
- MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
- Optional group = Optional.absent();
- Set members = new HashSet<>();
- String body = null;
- List attachments = new LinkedList<>();
+ MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
+ Optional group = Optional.absent();
+ Set members = new HashSet<>();
+ String body = null;
+ List attachments = new LinkedList<>();
RecipientId from = null;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java
index d848a647f..cc70898eb 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java
@@ -13,7 +13,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
-import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
@@ -76,7 +75,7 @@ public class MultiDeviceBlockedUpdateJob extends BaseJob {
while ((recipient = reader.getNext()) != null) {
if (recipient.isPushGroup()) {
- blockedGroups.add(GroupUtil.getDecodedId(recipient.requireGroupId()));
+ blockedGroups.add(recipient.requireGroupId().getDecodedId());
} else if (recipient.hasServiceIdentifier()) {
blockedIndividuals.add(RecipientUtil.toSignalServiceAddress(context, recipient));
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java
index 3d292d69d..ca4405acc 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java
@@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
-import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
@@ -92,13 +91,13 @@ public class MultiDeviceGroupUpdateJob extends BaseJob {
members.add(RecipientUtil.toSignalServiceAddress(context, Recipient.resolved(member)));
}
- RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(record.getId(), record.isMms()));
+ RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(record.getId());
Recipient recipient = Recipient.resolved(recipientId);
Optional expirationTimer = recipient.getExpireMessages() > 0 ? Optional.of(recipient.getExpireMessages()) : Optional.absent();
Map inboxPositions = DatabaseFactory.getThreadDatabase(context).getInboxPositions();
Set