Internal change

Bug: 79169954
Test: added tests in BlockingTest.java
PiperOrigin-RevId: 198950042
Change-Id: I380bc93276223db74b87f94140bd6c5c29c3f3cb
diff --git a/java/com/android/dialer/blocking/Blocking.java b/java/com/android/dialer/blocking/Blocking.java
index e86d0a6..a1d8b4f 100644
--- a/java/com/android/dialer/blocking/Blocking.java
+++ b/java/com/android/dialer/blocking/Blocking.java
@@ -16,14 +16,27 @@
 
 package com.android.dialer.blocking;
 
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.os.RemoteException;
+import android.provider.BlockedNumberContract;
 import android.provider.BlockedNumberContract.BlockedNumbers;
 import android.support.annotation.Nullable;
 import android.telephony.PhoneNumberUtils;
+import android.util.ArrayMap;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
 import com.android.dialer.common.database.Selection;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 /** Blocks and unblocks number. */
 public final class Blocking {
@@ -47,7 +60,7 @@
   }
 
   /**
-   * Block a number.
+   * Block a list of numbers.
    *
    * @param countryIso the current location used to guess the country code of the number if not
    *     available. If {@code null} and {@code number} does not have a country code, only the
@@ -55,29 +68,31 @@
    * @throws BlockingFailedException in the returned future if the operation failed.
    */
   public static ListenableFuture<Void> block(
-      Context context,
-      ListeningExecutorService executorService,
-      String number,
-      @Nullable String countryIso) {
-    return executorService.submit(
-        () -> {
-          ContentValues values = new ContentValues();
-          values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number);
-          String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
-          if (e164Number != null) {
-            values.put(BlockedNumbers.COLUMN_E164_NUMBER, e164Number);
-          }
-          try {
-            context.getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
-          } catch (SecurityException e) {
-            throw new BlockingFailedException(e);
-          }
-          return null;
-        });
+      Context context, ImmutableCollection<String> numbers, @Nullable String countryIso) {
+    return DialerExecutorComponent.get(context)
+        .backgroundExecutor()
+        .submit(
+            () -> {
+              ArrayList<ContentProviderOperation> operations = new ArrayList<>();
+              for (String number : numbers) {
+                ContentValues values = new ContentValues();
+                values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number);
+                String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
+                if (e164Number != null) {
+                  values.put(BlockedNumbers.COLUMN_E164_NUMBER, e164Number);
+                }
+                operations.add(
+                    ContentProviderOperation.newInsert(BlockedNumbers.CONTENT_URI)
+                        .withValues(values)
+                        .build());
+              }
+              applyBatchOps(context.getContentResolver(), operations);
+              return null;
+            });
   }
 
   /**
-   * Unblock a number.
+   * Unblock a list of number.
    *
    * @param countryIso the current location used to guess the country code of the number if not
    *     available. If {@code null} and {@code number} does not have a country code, only the
@@ -85,33 +100,95 @@
    * @throws BlockingFailedException in the returned future if the operation failed.
    */
   public static ListenableFuture<Void> unblock(
-      Context context,
-      ListeningExecutorService executorService,
-      String number,
-      @Nullable String countryIso) {
-    return executorService.submit(
-        () -> {
-          Selection selection =
-              Selection.column(BlockedNumbers.COLUMN_ORIGINAL_NUMBER).is("=", number);
-          String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
-          if (e164Number != null) {
-            selection =
-                selection
-                    .buildUpon()
-                    .or(Selection.column(BlockedNumbers.COLUMN_E164_NUMBER).is("=", e164Number))
-                    .build();
-          }
-          try {
-            context
-                .getContentResolver()
-                .delete(
-                    BlockedNumbers.CONTENT_URI,
-                    selection.getSelection(),
-                    selection.getSelectionArgs());
-          } catch (SecurityException e) {
-            throw new BlockingFailedException(e);
-          }
-          return null;
-        });
+      Context context, ImmutableCollection<String> numbers, @Nullable String countryIso) {
+    return DialerExecutorComponent.get(context)
+        .backgroundExecutor()
+        .submit(
+            () -> {
+              ArrayList<ContentProviderOperation> operations = new ArrayList<>();
+              for (String number : numbers) {
+                Selection selection =
+                    Selection.column(BlockedNumbers.COLUMN_ORIGINAL_NUMBER).is("=", number);
+                String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
+                if (e164Number != null) {
+                  selection =
+                      selection
+                          .buildUpon()
+                          .or(
+                              Selection.column(BlockedNumbers.COLUMN_E164_NUMBER)
+                                  .is("=", e164Number))
+                          .build();
+                }
+                operations.add(
+                    ContentProviderOperation.newDelete(BlockedNumbers.CONTENT_URI)
+                        .withSelection(selection.getSelection(), selection.getSelectionArgs())
+                        .build());
+              }
+              applyBatchOps(context.getContentResolver(), operations);
+              return null;
+            });
+  }
+
+  /**
+   * Get blocked numbers from a list of number.
+   *
+   * @param countryIso the current location used to guess the country code of the number if not
+   *     available. If {@code null} and {@code number} does not have a country code, only the
+   *     original number will be used to check blocked status.
+   * @throws BlockingFailedException in the returned future if the operation failed.
+   */
+  public static ListenableFuture<ImmutableMap<String, Boolean>> isBlocked(
+      Context context, ImmutableCollection<String> numbers, @Nullable String countryIso) {
+    return DialerExecutorComponent.get(context)
+        .backgroundExecutor()
+        .submit(
+            () -> {
+              Map<String, Boolean> blockedStatus = new ArrayMap<>();
+              List<String> e164Numbers = new ArrayList<>();
+
+              for (String number : numbers) {
+                // Initialize as unblocked
+                blockedStatus.put(number, false);
+                String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
+                if (e164Number != null) {
+                  e164Numbers.add(e164Number);
+                }
+              }
+
+              Selection selection =
+                  Selection.builder()
+                      .or(Selection.column(BlockedNumbers.COLUMN_ORIGINAL_NUMBER).in(numbers))
+                      .or(Selection.column(BlockedNumbers.COLUMN_E164_NUMBER).in(e164Numbers))
+                      .build();
+
+              try (Cursor cursor =
+                  context
+                      .getContentResolver()
+                      .query(
+                          BlockedNumbers.CONTENT_URI,
+                          new String[] {BlockedNumbers.COLUMN_ORIGINAL_NUMBER},
+                          selection.getSelection(),
+                          selection.getSelectionArgs(),
+                          null)) {
+                if (cursor == null) {
+                  return ImmutableMap.copyOf(blockedStatus);
+                }
+                while (cursor.moveToNext()) {
+                  // Update blocked status
+                  blockedStatus.put(cursor.getString(0), true);
+                }
+              }
+              return ImmutableMap.copyOf(blockedStatus);
+            });
+  }
+
+  private static ContentProviderResult[] applyBatchOps(
+      ContentResolver resolver, ArrayList<ContentProviderOperation> ops)
+      throws BlockingFailedException {
+    try {
+      return resolver.applyBatch(BlockedNumberContract.AUTHORITY, ops);
+    } catch (RemoteException | OperationApplicationException | SecurityException e) {
+      throw new BlockingFailedException(e);
+    }
   }
 }
diff --git a/java/com/android/dialer/blockreportspam/ShowBlockReportSpamDialogReceiver.java b/java/com/android/dialer/blockreportspam/ShowBlockReportSpamDialogReceiver.java
index fb0399d..28ac7bf 100644
--- a/java/com/android/dialer/blockreportspam/ShowBlockReportSpamDialogReceiver.java
+++ b/java/com/android/dialer/blockreportspam/ShowBlockReportSpamDialogReceiver.java
@@ -40,6 +40,7 @@
 import com.android.dialer.spam.Spam;
 import com.android.dialer.spam.SpamComponent;
 import com.android.dialer.spam.SpamSettings;
+import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 
@@ -225,8 +226,7 @@
     Futures.addCallback(
         Blocking.block(
             context,
-            DialerExecutorComponent.get(context).backgroundExecutor(),
-            dialogInfo.getNormalizedNumber(),
+            ImmutableList.of(dialogInfo.getNormalizedNumber()),
             dialogInfo.getCountryIso()),
         new FutureCallback<Void>() {
           @Override
@@ -252,8 +252,7 @@
     Futures.addCallback(
         Blocking.unblock(
             context,
-            DialerExecutorComponent.get(context).backgroundExecutor(),
-            dialogInfo.getNormalizedNumber(),
+            ImmutableList.of(dialogInfo.getNormalizedNumber()),
             dialogInfo.getCountryIso()),
         new FutureCallback<Void>() {
           @Override
diff --git a/java/com/android/dialer/commandline/impl/BlockingCommand.java b/java/com/android/dialer/commandline/impl/BlockingCommand.java
index f06e240..d064505 100644
--- a/java/com/android/dialer/commandline/impl/BlockingCommand.java
+++ b/java/com/android/dialer/commandline/impl/BlockingCommand.java
@@ -28,6 +28,7 @@
 import com.android.dialer.phonelookup.PhoneLookupInfo;
 import com.android.dialer.phonelookup.consolidator.PhoneLookupInfoConsolidator;
 import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil;
+import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
@@ -71,7 +72,7 @@
     if ("block".equals(command)) {
       String number = args.getPositionals().get(1);
       return Futures.transform(
-          Blocking.block(appContext, executorService, number, null),
+          Blocking.block(appContext, ImmutableList.of(number), null),
           (unused) -> "blocked " + number,
           MoreExecutors.directExecutor());
     }
@@ -79,7 +80,7 @@
     if ("unblock".equals(command)) {
       String number = args.getPositionals().get(1);
       return Futures.transform(
-          Blocking.unblock(appContext, executorService, number, null),
+          Blocking.unblock(appContext, ImmutableList.of(number), null),
           (unused) -> "unblocked " + number,
           MoreExecutors.directExecutor());
     }
diff --git a/java/com/android/dialer/logging/reporting_location.proto b/java/com/android/dialer/logging/reporting_location.proto
index b9912be..1d6ba47 100644
--- a/java/com/android/dialer/logging/reporting_location.proto
+++ b/java/com/android/dialer/logging/reporting_location.proto
@@ -15,5 +15,6 @@
     CALL_LOG_HISTORY = 1;
     FEEDBACK_PROMPT = 2;
     VOICEMAIL_HISTORY = 3;
+    CONTACT_DETAILS = 4;
   }
 }