Implement System blocked number

This CL implements looking up blocked number in the Android system blocked number provider, which is available after N. Dialer and system blocked number will return empty if requirements are not made (N+ and migration completed is needed for system, otherwise dialer should not be used).

Bug: 70989543
Test: Unit tests
PiperOrigin-RevId: 182852672
Change-Id: I1360b7eed7c18f459292d769529ffcfceb61a7ed
diff --git a/java/com/android/dialer/phonelookup/PhoneLookupModule.java b/java/com/android/dialer/phonelookup/PhoneLookupModule.java
index e93ca0f..8a78ba0 100644
--- a/java/com/android/dialer/phonelookup/PhoneLookupModule.java
+++ b/java/com/android/dialer/phonelookup/PhoneLookupModule.java
@@ -17,6 +17,7 @@
 package com.android.dialer.phonelookup;
 
 import com.android.dialer.phonelookup.blockednumber.DialerBlockedNumberPhoneLookup;
+import com.android.dialer.phonelookup.blockednumber.SystemBlockedNumberPhoneLookup;
 import com.android.dialer.phonelookup.composite.CompositePhoneLookup;
 import com.android.dialer.phonelookup.cp2.Cp2LocalPhoneLookup;
 import com.android.dialer.phonelookup.cp2.Cp2RemotePhoneLookup;
@@ -33,9 +34,13 @@
   static ImmutableList<PhoneLookup> providePhoneLookupList(
       Cp2LocalPhoneLookup cp2LocalPhoneLookup,
       Cp2RemotePhoneLookup cp2RemotePhoneLookup,
-      DialerBlockedNumberPhoneLookup dialerBlockedNumberPhoneLookup) {
+      DialerBlockedNumberPhoneLookup dialerBlockedNumberPhoneLookup,
+      SystemBlockedNumberPhoneLookup systemBlockedNumberPhoneLookup) {
     return ImmutableList.of(
-        cp2LocalPhoneLookup, cp2RemotePhoneLookup, dialerBlockedNumberPhoneLookup);
+        cp2LocalPhoneLookup,
+        cp2RemotePhoneLookup,
+        dialerBlockedNumberPhoneLookup,
+        systemBlockedNumberPhoneLookup);
   }
 
   @Provides
diff --git a/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java
index ce12177..2271c75 100644
--- a/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java
+++ b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java
@@ -17,17 +17,14 @@
 package com.android.dialer.phonelookup.blockednumber;
 
 import android.content.Context;
-import android.database.ContentObserver;
 import android.database.Cursor;
-import android.net.Uri;
-import android.support.annotation.MainThread;
 import android.support.annotation.WorkerThread;
 import android.util.ArraySet;
 import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.blocking.FilteredNumberCompat;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
-import com.android.dialer.common.concurrent.ThreadUtil;
 import com.android.dialer.common.database.Selection;
 import com.android.dialer.database.FilteredNumberContract.FilteredNumber;
 import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns;
@@ -65,6 +62,9 @@
 
   @Override
   public ListenableFuture<DialerBlockedNumberInfo> lookup(DialerPhoneNumber dialerPhoneNumber) {
+    if (FilteredNumberCompat.useNewFiltering(appContext)) {
+      return Futures.immediateFuture(DialerBlockedNumberInfo.getDefaultInstance());
+    }
     return executorService.submit(
         () -> queryNumbers(ImmutableSet.of(dialerPhoneNumber)).get(dialerPhoneNumber));
   }
@@ -79,6 +79,9 @@
   @Override
   public ListenableFuture<ImmutableMap<DialerPhoneNumber, DialerBlockedNumberInfo>>
       getMostRecentInfo(ImmutableMap<DialerPhoneNumber, DialerBlockedNumberInfo> existingInfoMap) {
+    if (FilteredNumberCompat.useNewFiltering(appContext)) {
+      return Futures.immediateFuture(existingInfoMap);
+    }
     LogUtil.enterBlock("DialerBlockedNumberPhoneLookup.getMostRecentPhoneLookupInfo");
     return executorService.submit(() -> queryNumbers(existingInfoMap.keySet()));
   }
@@ -128,11 +131,7 @@
     }
 
     Selection rawSelection =
-        Selection.column(FilteredNumberColumns.NUMBER)
-            .in(
-                partitionedNumbers
-                    .invalidNumbers()
-                    .toArray(new String[partitionedNumbers.invalidNumbers().size()]));
+        Selection.column(FilteredNumberColumns.NUMBER).in(partitionedNumbers.invalidNumbers());
     try (Cursor cursor =
         appContext
             .getContentResolver()
@@ -173,26 +172,6 @@
         .registerContentObserver(
             FilteredNumber.CONTENT_URI,
             true, // FilteredNumberProvider notifies on the item
-            new FilteredNumberObserver(appContext, contentObserverCallbacks));
-  }
-
-  private static class FilteredNumberObserver extends ContentObserver {
-    private final Context appContext;
-    private final ContentObserverCallbacks contentObserverCallbacks;
-
-    FilteredNumberObserver(Context appContext, ContentObserverCallbacks contentObserverCallbacks) {
-      super(ThreadUtil.getUiThreadHandler());
-      this.appContext = appContext;
-      this.contentObserverCallbacks = contentObserverCallbacks;
-    }
-
-    @MainThread
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored") // never throws.
-    public void onChange(boolean selfChange, Uri uri) {
-      Assert.isMainThread();
-      LogUtil.enterBlock("DialerBlockedNumberPhoneLookup.FilteredNumberObserver.onChange");
-      contentObserverCallbacks.markDirtyAndNotify(appContext);
-    }
+            new MarkDirtyObserver(appContext, contentObserverCallbacks));
   }
 }
diff --git a/java/com/android/dialer/phonelookup/blockednumber/MarkDirtyObserver.java b/java/com/android/dialer/phonelookup/blockednumber/MarkDirtyObserver.java
new file mode 100644
index 0000000..1c41d8f
--- /dev/null
+++ b/java/com/android/dialer/phonelookup/blockednumber/MarkDirtyObserver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.phonelookup.blockednumber;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.support.annotation.MainThread;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.dialer.phonelookup.PhoneLookup.ContentObserverCallbacks;
+
+/** Calls {@link ContentObserverCallbacks#markDirtyAndNotify(Context)} when the content changed */
+class MarkDirtyObserver extends ContentObserver {
+
+  private final Context appContext;
+  private final ContentObserverCallbacks contentObserverCallbacks;
+
+  MarkDirtyObserver(Context appContext, ContentObserverCallbacks contentObserverCallbacks) {
+    super(ThreadUtil.getUiThreadHandler());
+    this.appContext = appContext;
+    this.contentObserverCallbacks = contentObserverCallbacks;
+  }
+
+  @MainThread
+  @Override
+  public void onChange(boolean selfChange, Uri uri) {
+    Assert.isMainThread();
+    LogUtil.enterBlock("SystemBlockedNumberPhoneLookup.FilteredNumberObserver.onChange");
+    contentObserverCallbacks.markDirtyAndNotify(appContext);
+  }
+}
diff --git a/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java b/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java
new file mode 100644
index 0000000..e0ff995
--- /dev/null
+++ b/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.phonelookup.blockednumber;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.provider.BlockedNumberContract.BlockedNumbers;
+import android.support.annotation.NonNull;
+import android.support.annotation.WorkerThread;
+import android.util.ArraySet;
+import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.blocking.FilteredNumberCompat;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
+import com.android.dialer.common.database.Selection;
+import com.android.dialer.inject.ApplicationContext;
+import com.android.dialer.phonelookup.PhoneLookup;
+import com.android.dialer.phonelookup.PhoneLookupInfo;
+import com.android.dialer.phonelookup.PhoneLookupInfo.BlockedState;
+import com.android.dialer.phonelookup.PhoneLookupInfo.Builder;
+import com.android.dialer.phonelookup.PhoneLookupInfo.SystemBlockedNumberInfo;
+import com.android.dialer.phonenumberproto.PartitionedNumbers;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import java.util.Set;
+import javax.inject.Inject;
+
+/**
+ * Lookup blocked numbers in the system database. Requires N+ and migration from dialer database
+ * completed (need user consent to move data into system).
+ */
+public class SystemBlockedNumberPhoneLookup implements PhoneLookup<SystemBlockedNumberInfo> {
+
+  private final Context appContext;
+  private final ListeningExecutorService executorService;
+
+  @Inject
+  SystemBlockedNumberPhoneLookup(
+      @ApplicationContext Context appContext,
+      @BackgroundExecutor ListeningExecutorService executorService) {
+    this.appContext = appContext;
+    this.executorService = executorService;
+  }
+
+  @Override
+  public ListenableFuture<SystemBlockedNumberInfo> lookup(@NonNull DialerPhoneNumber number) {
+    if (!FilteredNumberCompat.useNewFiltering(appContext)) {
+      return Futures.immediateFuture(SystemBlockedNumberInfo.getDefaultInstance());
+    }
+    return executorService.submit(
+        () -> {
+          return queryNumbers(ImmutableSet.of(number)).get(number);
+        });
+  }
+
+  @Override
+  public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) {
+    // Dirty state is recorded with PhoneLookupDataSource.markDirtyAndNotify(), which will force
+    // rebuild with the CallLogFramework
+    return Futures.immediateFuture(false);
+  }
+
+  @Override
+  public ListenableFuture<ImmutableMap<DialerPhoneNumber, SystemBlockedNumberInfo>>
+      getMostRecentInfo(ImmutableMap<DialerPhoneNumber, SystemBlockedNumberInfo> existingInfoMap) {
+    LogUtil.enterBlock("SystemBlockedNumberPhoneLookup.getMostRecentPhoneLookupInfo");
+    if (!FilteredNumberCompat.useNewFiltering(appContext)) {
+      return Futures.immediateFuture(existingInfoMap);
+    }
+    return executorService.submit(() -> queryNumbers(existingInfoMap.keySet()));
+  }
+
+  @WorkerThread
+  @TargetApi(VERSION_CODES.N)
+  private ImmutableMap<DialerPhoneNumber, SystemBlockedNumberInfo> queryNumbers(
+      ImmutableSet<DialerPhoneNumber> numbers) {
+    Assert.isWorkerThread();
+    PartitionedNumbers partitionedNumbers = new PartitionedNumbers(numbers);
+
+    Set<DialerPhoneNumber> blockedNumbers = new ArraySet<>();
+
+    Selection normalizedSelection =
+        Selection.column(BlockedNumbers.COLUMN_E164_NUMBER)
+            .in(partitionedNumbers.validE164Numbers());
+    try (Cursor cursor =
+        appContext
+            .getContentResolver()
+            .query(
+                BlockedNumbers.CONTENT_URI,
+                new String[] {BlockedNumbers.COLUMN_E164_NUMBER},
+                normalizedSelection.getSelection(),
+                normalizedSelection.getSelectionArgs(),
+                null)) {
+      while (cursor != null && cursor.moveToNext()) {
+        blockedNumbers.addAll(
+            partitionedNumbers.dialerPhoneNumbersForValidE164(cursor.getString(0)));
+      }
+    }
+
+    Selection rawSelection =
+        Selection.column(BlockedNumbers.COLUMN_ORIGINAL_NUMBER)
+            .in(partitionedNumbers.invalidNumbers());
+    try (Cursor cursor =
+        appContext
+            .getContentResolver()
+            .query(
+                BlockedNumbers.CONTENT_URI,
+                new String[] {BlockedNumbers.COLUMN_ORIGINAL_NUMBER},
+                rawSelection.getSelection(),
+                rawSelection.getSelectionArgs(),
+                null)) {
+      while (cursor != null && cursor.moveToNext()) {
+        blockedNumbers.addAll(partitionedNumbers.dialerPhoneNumbersForInvalid(cursor.getString(0)));
+      }
+    }
+
+    ImmutableMap.Builder<DialerPhoneNumber, SystemBlockedNumberInfo> result =
+        ImmutableMap.builder();
+
+    for (DialerPhoneNumber number : numbers) {
+      result.put(
+          number,
+          SystemBlockedNumberInfo.newBuilder()
+              .setBlockedState(
+                  blockedNumbers.contains(number) ? BlockedState.BLOCKED : BlockedState.NOT_BLOCKED)
+              .build());
+    }
+
+    return result.build();
+  }
+
+  @Override
+  public void setSubMessage(Builder phoneLookupInfo, SystemBlockedNumberInfo subMessage) {
+    phoneLookupInfo.setSystemBlockedNumberInfo(subMessage);
+  }
+
+  @Override
+  public SystemBlockedNumberInfo getSubMessage(PhoneLookupInfo phoneLookupInfo) {
+    return phoneLookupInfo.getSystemBlockedNumberInfo();
+  }
+
+  @Override
+  public ListenableFuture<Void> onSuccessfulBulkUpdate() {
+    return Futures.immediateFuture(null);
+  }
+
+  @Override
+  public void registerContentObservers(
+      Context appContext, ContentObserverCallbacks contentObserverCallbacks) {
+    if (VERSION.SDK_INT < VERSION_CODES.N) {
+      return;
+    }
+    appContext
+        .getContentResolver()
+        .registerContentObserver(
+            BlockedNumbers.CONTENT_URI,
+            true, // BlockedNumbers notifies on the item
+            new MarkDirtyObserver(appContext, contentObserverCallbacks));
+  }
+}
diff --git a/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java b/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java
index ccad3e7..27f0d21 100644
--- a/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java
+++ b/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java
@@ -179,11 +179,7 @@
    * returned.
    */
   public String getNumberLabel() {
-    if (phoneLookupInfo.hasDialerBlockedNumberInfo()
-        && phoneLookupInfo
-            .getDialerBlockedNumberInfo()
-            .getBlockedState()
-            .equals(BlockedState.BLOCKED)) {
+    if (isBlocked()) {
       return appContext.getString(R.string.blocked_number_new_call_log_label);
     }
 
@@ -219,6 +215,21 @@
     return false;
   }
 
+  public boolean isBlocked() {
+    // If system blocking reported blocked state it always takes priority over the dialer blocking.
+    // It will be absent if dialer blocking should be used.
+    if (phoneLookupInfo.getSystemBlockedNumberInfo().hasBlockedState()) {
+      return phoneLookupInfo
+          .getSystemBlockedNumberInfo()
+          .getBlockedState()
+          .equals(BlockedState.BLOCKED);
+    }
+    return phoneLookupInfo
+        .getDialerBlockedNumberInfo()
+        .getBlockedState()
+        .equals(BlockedState.BLOCKED);
+  }
+
   /**
    * Returns true if the {@link PhoneLookupInfo} passed to the constructor has incomplete CP2 local
    * info.