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)); + } +}