diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java b/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java
index f4f137bc78..91a8f8e921 100644
--- a/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java
+++ b/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java
@@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.contacts;
import android.accounts.Account;
import android.annotation.SuppressLint;
import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
@@ -101,22 +102,27 @@ public class ContactsDatabase {
boolean remove)
throws RemoteException, OperationApplicationException
{
- Set
registeredAddressSet = new HashSet<>();
+ Set registeredAddressSet = new HashSet<>(registeredAddressList);
ArrayList operations = new ArrayList<>();
Map currentContacts = getSignalRawContacts(account);
+ List> registeredChunks = Util.chunk(registeredAddressList, 50);
- for (Address registeredAddress : registeredAddressList) {
- registeredAddressSet.add(registeredAddress);
+ for (List registeredChunk : registeredChunks) {
+ for (Address registeredAddress : registeredChunk) {
+ if (!currentContacts.containsKey(registeredAddress)) {
+ Optional systemContactInfo = getSystemContactInfo(registeredAddress);
- if (!currentContacts.containsKey(registeredAddress)) {
- Optional systemContactInfo = getSystemContactInfo(registeredAddress);
-
- if (systemContactInfo.isPresent()) {
- Log.i(TAG, "Adding number: " + registeredAddress);
- addTextSecureRawContact(operations, account, systemContactInfo.get().number,
- systemContactInfo.get().name, systemContactInfo.get().id);
+ if (systemContactInfo.isPresent()) {
+ Log.i(TAG, "Adding number: " + registeredAddress);
+ addTextSecureRawContact(operations, account, systemContactInfo.get().number,
+ systemContactInfo.get().name, systemContactInfo.get().id);
+ }
}
}
+ if (!operations.isEmpty()) {
+ context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
+ operations.clear();
+ }
}
for (Map.Entry currentContactEntry : currentContacts.entrySet()) {
@@ -137,7 +143,7 @@ public class ContactsDatabase {
}
if (!operations.isEmpty()) {
- context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
+ applyOperationsInBatches(context.getContentResolver(), ContactsContract.AUTHORITY, operations, 50);
}
}
@@ -532,6 +538,18 @@ public class ContactsDatabase {
}
}
+ private void applyOperationsInBatches(@NonNull ContentResolver contentResolver,
+ @NonNull String authority,
+ @NonNull List operations,
+ int batchSize)
+ throws OperationApplicationException, RemoteException
+ {
+ List> batches = Util.chunk(operations, batchSize);
+ for (List batch : batches) {
+ contentResolver.applyBatch(authority, new ArrayList<>(batch));
+ }
+ }
+
private static class ProjectionMappingCursor extends CursorWrapper {
private final Map projectionMap;
diff --git a/src/org/thoughtcrime/securesms/util/DirectoryHelper.java b/src/org/thoughtcrime/securesms/util/DirectoryHelper.java
index da18f23e81..15647b6857 100644
--- a/src/org/thoughtcrime/securesms/util/DirectoryHelper.java
+++ b/src/org/thoughtcrime/securesms/util/DirectoryHelper.java
@@ -218,7 +218,7 @@ public class DirectoryHelper {
}
}
} catch (RemoteException | OperationApplicationException e) {
- Log.w(TAG, e);
+ Log.w(TAG, "Failed to update contacts.", e);
}
}
}
diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java
index 46fa48d432..c9fc0a4f5d 100644
--- a/src/org/thoughtcrime/securesms/util/Util.java
+++ b/src/org/thoughtcrime/securesms/util/Util.java
@@ -63,6 +63,7 @@ import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.DecimalFormat;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -77,7 +78,7 @@ import java.util.concurrent.TimeUnit;
public class Util {
private static final String TAG = Util.class.getSimpleName();
- public static Handler handler = new Handler(Looper.getMainLooper());
+ private static volatile Handler handler;
public static List asList(T... elements) {
List result = new LinkedList<>();
@@ -141,6 +142,17 @@ public class Util {
return map.containsKey(key) ? map.get(key) : defaultValue;
}
+ public static List> chunk(@NonNull List list, int chunkSize) {
+ List> chunks = new ArrayList<>(list.size() / chunkSize);
+
+ for (int i = 0; i < list.size(); i += chunkSize) {
+ List chunk = list.subList(i, Math.min(list.size(), i + chunkSize));
+ chunks.add(chunk);
+ }
+
+ return chunks;
+ }
+
public static CharSequence getBoldedString(String value) {
SpannableString spanned = new SpannableString(value);
spanned.setSpan(new StyleSpan(Typeface.BOLD), 0,
@@ -394,20 +406,20 @@ public class Util {
}
public static void postToMain(final @NonNull Runnable runnable) {
- handler.post(runnable);
+ getHandler().post(runnable);
}
public static void runOnMain(final @NonNull Runnable runnable) {
if (isMainThread()) runnable.run();
- else handler.post(runnable);
+ else getHandler().post(runnable);
}
public static void runOnMainDelayed(final @NonNull Runnable runnable, long delayMillis) {
- handler.postDelayed(runnable, delayMillis);
+ getHandler().postDelayed(runnable, delayMillis);
}
public static void cancelRunnableOnMain(@NonNull Runnable runnable) {
- handler.removeCallbacks(runnable);
+ getHandler().removeCallbacks(runnable);
}
public static void runOnMainSync(final @NonNull Runnable runnable) {
@@ -510,4 +522,15 @@ public class Util {
return new DecimalFormat("#,##0.#").format(sizeBytes/Math.pow(1024, digitGroups)) + " " + units[digitGroups];
}
+
+ private static Handler getHandler() {
+ if (handler == null) {
+ synchronized (Util.class) {
+ if (handler == null) {
+ handler = new Handler(Looper.getMainLooper());
+ }
+ }
+ }
+ return handler;
+ }
}
diff --git a/test/unitTest/java/org/thoughtcrime/securesms/util/UtilTest.java b/test/unitTest/java/org/thoughtcrime/securesms/util/UtilTest.java
new file mode 100644
index 0000000000..b34fc5d95a
--- /dev/null
+++ b/test/unitTest/java/org/thoughtcrime/securesms/util/UtilTest.java
@@ -0,0 +1,52 @@
+package org.thoughtcrime.securesms.util;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+public class UtilTest {
+
+ @Test
+ public void chunk_oneChunk() {
+ List input = Arrays.asList("A", "B", "C");
+
+ List> output = Util.chunk(input, 3);
+ assertEquals(1, output.size());
+ assertEquals(input, output.get(0));
+
+ output = Util.chunk(input, 4);
+ assertEquals(1, output.size());
+ assertEquals(input, output.get(0));
+
+ output = Util.chunk(input, 100);
+ assertEquals(1, output.size());
+ assertEquals(input, output.get(0));
+ }
+
+ @Test
+ public void chunk_multipleChunks() {
+ List input = Arrays.asList("A", "B", "C", "D", "E");
+
+ List> output = Util.chunk(input, 4);
+ assertEquals(2, output.size());
+ assertEquals(Arrays.asList("A", "B", "C", "D"), output.get(0));
+ assertEquals(Arrays.asList("E"), output.get(1));
+
+ output = Util.chunk(input, 2);
+ assertEquals(3, output.size());
+ assertEquals(Arrays.asList("A", "B"), output.get(0));
+ assertEquals(Arrays.asList("C", "D"), output.get(1));
+ assertEquals(Arrays.asList("E"), output.get(2));
+
+ output = Util.chunk(input, 1);
+ assertEquals(5, output.size());
+ assertEquals(Arrays.asList("A"), output.get(0));
+ assertEquals(Arrays.asList("B"), output.get(1));
+ assertEquals(Arrays.asList("C"), output.get(2));
+ assertEquals(Arrays.asList("D"), output.get(3));
+ assertEquals(Arrays.asList("E"), output.get(4));
+ }
+}