Implement dialer blocked number phone lookup

This CL implements looking up the dialer internal database for blocked numbers when the system database is not available yet.

Data is only invalidated when dialer is alive since that is the only time blocked numbers can be set and removed.

Bug: 70989538,70989547
Test: DialerBlockedNumberPhoneLookupTest
PiperOrigin-RevId: 180956355
Change-Id: Ie7acf091bf58a074d0a1ee39613fad035d2e6e60
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b459a44..d19d0c9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,8 +16,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   coreApp="true"
   package="com.android.dialer"
-  android:versionCode="190000"
-  android:versionName="14.0">
+  android:versionCode="220000"
+  android:versionName="17.0">
 
   <uses-sdk
     android:minSdkVersion="23"
diff --git a/java/com/android/dialer/binary/google/AndroidManifest.xml b/java/com/android/dialer/binary/google/AndroidManifest.xml
index 86f6bcb..0ebc006 100644
--- a/java/com/android/dialer/binary/google/AndroidManifest.xml
+++ b/java/com/android/dialer/binary/google/AndroidManifest.xml
@@ -16,8 +16,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   coreApp="true"
   package="com.google.android.google_stub_dialer"
-  android:versionCode="190000"
-  android:versionName="14.0">
+  android:versionCode="220000"
+  android:versionName="17.0">
 
   <uses-sdk
     android:minSdkVersion="23"
diff --git a/java/com/android/dialer/calllog/CallLogFramework.java b/java/com/android/dialer/calllog/CallLogFramework.java
index e4bb4c8..c9d5f09 100644
--- a/java/com/android/dialer/calllog/CallLogFramework.java
+++ b/java/com/android/dialer/calllog/CallLogFramework.java
@@ -20,6 +20,7 @@
 import android.content.SharedPreferences;
 import android.support.annotation.MainThread;
 import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
 import com.android.dialer.buildtype.BuildType;
 import com.android.dialer.calllog.datasources.CallLogDataSource;
 import com.android.dialer.calllog.datasources.DataSources;
@@ -38,7 +39,8 @@
 @Singleton
 public final class CallLogFramework implements CallLogDataSource.ContentObserverCallbacks {
 
-  static final String PREF_FORCE_REBUILD = "callLogFrameworkForceRebuild";
+  @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+  public static final String PREF_FORCE_REBUILD = "callLogFrameworkForceRebuild";
 
   private final DataSources dataSources;
   private final SharedPreferences sharedPreferences;
diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
index 2148627..6ec11ad 100644
--- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
@@ -33,14 +33,15 @@
 import com.android.dialer.calllog.datasources.CallLogDataSource;
 import com.android.dialer.calllog.datasources.CallLogMutations;
 import com.android.dialer.calllog.datasources.util.RowCombiner;
+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.Annotations.LightweightExecutor;
 import com.android.dialer.phonelookup.PhoneLookup;
 import com.android.dialer.phonelookup.PhoneLookupInfo;
-import com.android.dialer.phonelookup.PhoneLookupSelector;
 import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract;
 import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory;
+import com.android.dialer.phonelookup.selector.PhoneLookupSelector;
 import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -63,9 +64,11 @@
  * Responsible for maintaining the columns in the annotated call log which are derived from phone
  * numbers.
  */
-public final class PhoneLookupDataSource implements CallLogDataSource {
+public final class PhoneLookupDataSource
+    implements CallLogDataSource, PhoneLookup.ContentObserverCallbacks {
 
   private final PhoneLookup<PhoneLookupInfo> phoneLookup;
+  private final PhoneLookupSelector phoneLookupSelector;
   private final ListeningExecutorService backgroundExecutorService;
   private final ListeningExecutorService lightweightExecutorService;
 
@@ -86,12 +89,16 @@
    */
   private final Set<String> phoneLookupHistoryRowsToDelete = new ArraySet<>();
 
+  private CallLogDataSource.ContentObserverCallbacks dataSourceContentObserverCallbacks;
+
   @Inject
   PhoneLookupDataSource(
       PhoneLookup<PhoneLookupInfo> phoneLookup,
+      PhoneLookupSelector phoneLookupSelector,
       @BackgroundExecutor ListeningExecutorService backgroundExecutorService,
       @LightweightExecutor ListeningExecutorService lightweightExecutorService) {
     this.phoneLookup = phoneLookup;
+    this.phoneLookupSelector = phoneLookupSelector;
     this.backgroundExecutorService = backgroundExecutorService;
     this.lightweightExecutorService = lightweightExecutorService;
   }
@@ -275,8 +282,16 @@
   @MainThread
   @Override
   public void registerContentObservers(
-      Context appContext, ContentObserverCallbacks contentObserverCallbacks) {
-    // No content observers required for this data source.
+      Context appContext, CallLogDataSource.ContentObserverCallbacks contentObserverCallbacks) {
+    dataSourceContentObserverCallbacks = contentObserverCallbacks;
+    phoneLookup.registerContentObservers(appContext, this);
+  }
+
+  @MainThread
+  @Override
+  public void markDirtyAndNotify(Context appContext) {
+    Assert.isMainThread();
+    dataSourceContentObserverCallbacks.markDirtyAndNotify(appContext);
   }
 
   private static ImmutableSet<DialerPhoneNumber>
@@ -455,7 +470,7 @@
             }));
   }
 
-  private static void populateInserts(
+  private void populateInserts(
       ImmutableMap<Long, PhoneLookupInfo> existingInfo, CallLogMutations mutations) {
     for (Entry<Long, ContentValues> entry : mutations.getInserts().entrySet()) {
       long id = entry.getKey();
@@ -468,7 +483,7 @@
     }
   }
 
-  private static void updateMutations(
+  private void updateMutations(
       ImmutableMap<Long, PhoneLookupInfo> updatesToApply, CallLogMutations mutations) {
     for (Entry<Long, PhoneLookupInfo> entry : updatesToApply.entrySet()) {
       long id = entry.getKey();
@@ -554,17 +569,16 @@
     return normalizedNumbersToDelete;
   }
 
-  private static void updateContentValues(
-      ContentValues contentValues, PhoneLookupInfo phoneLookupInfo) {
-    contentValues.put(AnnotatedCallLog.NAME, PhoneLookupSelector.selectName(phoneLookupInfo));
+  private void updateContentValues(ContentValues contentValues, PhoneLookupInfo phoneLookupInfo) {
+    contentValues.put(AnnotatedCallLog.NAME, phoneLookupSelector.selectName(phoneLookupInfo));
     contentValues.put(
-        AnnotatedCallLog.PHOTO_URI, PhoneLookupSelector.selectPhotoUri(phoneLookupInfo));
+        AnnotatedCallLog.PHOTO_URI, phoneLookupSelector.selectPhotoUri(phoneLookupInfo));
     contentValues.put(
-        AnnotatedCallLog.PHOTO_ID, PhoneLookupSelector.selectPhotoId(phoneLookupInfo));
+        AnnotatedCallLog.PHOTO_ID, phoneLookupSelector.selectPhotoId(phoneLookupInfo));
     contentValues.put(
-        AnnotatedCallLog.LOOKUP_URI, PhoneLookupSelector.selectLookupUri(phoneLookupInfo));
+        AnnotatedCallLog.LOOKUP_URI, phoneLookupSelector.selectLookupUri(phoneLookupInfo));
     contentValues.put(
-        AnnotatedCallLog.NUMBER_TYPE_LABEL, PhoneLookupSelector.selectNumberLabel(phoneLookupInfo));
+        AnnotatedCallLog.NUMBER_TYPE_LABEL, phoneLookupSelector.selectNumberLabel(phoneLookupInfo));
     contentValues.put(
         AnnotatedCallLog.CAN_REPORT_AS_INVALID_NUMBER,
         PhoneLookupSelector.canReportAsInvalidNumber(phoneLookupInfo));
diff --git a/java/com/android/dialer/common/database/Selection.java b/java/com/android/dialer/common/database/Selection.java
index b61472d..e449fd9 100644
--- a/java/com/android/dialer/common/database/Selection.java
+++ b/java/com/android/dialer/common/database/Selection.java
@@ -18,8 +18,11 @@
 
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.text.TextUtils;
 import com.android.dialer.common.Assert;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
@@ -106,7 +109,14 @@
    *     enclosed in a parenthesis.
    */
   @NonNull
+  @SuppressWarnings("rawtypes")
   public static Selection fromString(@Nullable String selection, @Nullable String... args) {
+    return new Builder(selection, args == null ? Collections.emptyList() : Arrays.asList(args))
+        .build();
+  }
+
+  @NonNull
+  public static Selection fromString(@Nullable String selection, Collection<String> args) {
     return new Builder(selection, args).build();
   }
 
@@ -149,6 +159,16 @@
     public Selection is(@NonNull String condition) {
       return fromString(column + " " + Assert.isNotNull(condition));
     }
+
+    public Selection in(String... values) {
+      return in(values == null ? Collections.emptyList() : Arrays.asList(values));
+    }
+
+    public Selection in(Collection<String> values) {
+      return fromString(
+          column + " IN (" + TextUtils.join(",", Collections.nCopies(values.size(), "?")) + ")",
+          values);
+    }
   }
 
   /** Builder for {@link Selection} */
@@ -159,14 +179,14 @@
 
     private Builder() {}
 
-    private Builder(@Nullable String selection, @Nullable String... args) {
+    private Builder(@Nullable String selection, Collection<String> args) {
       if (selection == null) {
         return;
       }
       checkArgsCount(selection, args);
       this.selection.append(parenthesized(selection));
       if (args != null) {
-        Collections.addAll(selectionArgs, args);
+        selectionArgs.addAll(args);
       }
     }
 
@@ -213,14 +233,14 @@
       return this;
     }
 
-    private static void checkArgsCount(@NonNull String selection, @Nullable String... args) {
+    private static void checkArgsCount(@NonNull String selection, Collection<String> args) {
       int argsInSelection = 0;
       for (int i = 0; i < selection.length(); i++) {
         if (selection.charAt(i) == '?') {
           argsInSelection++;
         }
       }
-      Assert.checkArgument(argsInSelection == (args == null ? 0 : args.length));
+      Assert.checkArgument(argsInSelection == (args == null ? 0 : args.size()));
     }
   }
 
diff --git a/java/com/android/dialer/phonelookup/PhoneLookup.java b/java/com/android/dialer/phonelookup/PhoneLookup.java
index 859085e..118ae60 100644
--- a/java/com/android/dialer/phonelookup/PhoneLookup.java
+++ b/java/com/android/dialer/phonelookup/PhoneLookup.java
@@ -16,6 +16,8 @@
 
 package com.android.dialer.phonelookup;
 
+import android.content.Context;
+import android.support.annotation.MainThread;
 import android.support.annotation.NonNull;
 import android.telecom.Call;
 import com.android.dialer.DialerPhoneNumber;
@@ -82,4 +84,16 @@
    * be efficiently implemented.
    */
   ListenableFuture<Void> onSuccessfulBulkUpdate();
+
+  @MainThread
+  void registerContentObservers(
+      Context appContext, ContentObserverCallbacks contentObserverCallbacks);
+
+  /**
+   * Methods which may optionally be called as a result of a phone lookup's content observer firing.
+   */
+  interface ContentObserverCallbacks {
+    @MainThread
+    void markDirtyAndNotify(Context appContext);
+  }
 }
diff --git a/java/com/android/dialer/phonelookup/PhoneLookupModule.java b/java/com/android/dialer/phonelookup/PhoneLookupModule.java
index 8a59005..b4f3787 100644
--- a/java/com/android/dialer/phonelookup/PhoneLookupModule.java
+++ b/java/com/android/dialer/phonelookup/PhoneLookupModule.java
@@ -16,6 +16,7 @@
 
 package com.android.dialer.phonelookup;
 
+import com.android.dialer.phonelookup.blockednumber.DialerBlockedNumberPhoneLookup;
 import com.android.dialer.phonelookup.composite.CompositePhoneLookup;
 import com.android.dialer.phonelookup.cp2.Cp2PhoneLookup;
 import com.google.common.collect.ImmutableList;
@@ -27,8 +28,11 @@
 public abstract class PhoneLookupModule {
 
   @Provides
-  static ImmutableList<PhoneLookup> providePhoneLookupList(Cp2PhoneLookup cp2PhoneLookup) {
-    return ImmutableList.of(cp2PhoneLookup);
+  @SuppressWarnings({"unchecked", "rawtype"})
+  static ImmutableList<PhoneLookup> providePhoneLookupList(
+      Cp2PhoneLookup cp2PhoneLookup,
+      DialerBlockedNumberPhoneLookup dialerBlockedNumberPhoneLookup) {
+    return ImmutableList.of(cp2PhoneLookup, dialerBlockedNumberPhoneLookup);
   }
 
   @Provides
diff --git a/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java
new file mode 100644
index 0000000..54df399
--- /dev/null
+++ b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java
@@ -0,0 +1,210 @@
+/*
+ * 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.database.Cursor;
+import android.net.Uri;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.WorkerThread;
+import android.telecom.Call;
+import android.util.ArraySet;
+import com.android.dialer.DialerPhoneNumber;
+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.database.FilteredNumberContract.FilteredNumber;
+import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns;
+import com.android.dialer.database.FilteredNumberContract.FilteredNumberTypes;
+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.DialerBlockedNumberInfo;
+import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil;
+import com.android.dialer.phonenumberproto.PartitionedNumbers;
+import com.android.dialer.telecom.TelecomCallUtil;
+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 com.google.i18n.phonenumbers.PhoneNumberUtil;
+import java.util.Set;
+import javax.inject.Inject;
+
+/**
+ * Lookup blocked numbers in the dialer internal database. This is used when the system database is
+ * not yet available.
+ */
+public final class DialerBlockedNumberPhoneLookup implements PhoneLookup<DialerBlockedNumberInfo> {
+
+  private final Context appContext;
+  private final ListeningExecutorService executorService;
+
+  @Inject
+  DialerBlockedNumberPhoneLookup(
+      @ApplicationContext Context appContext,
+      @BackgroundExecutor ListeningExecutorService executorService) {
+    this.appContext = appContext;
+    this.executorService = executorService;
+  }
+
+  @Override
+  public ListenableFuture<DialerBlockedNumberInfo> lookup(@NonNull Call call) {
+    return executorService.submit(
+        () -> {
+          DialerPhoneNumberUtil dialerPhoneNumberUtil =
+              new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance());
+
+          DialerPhoneNumber number =
+              dialerPhoneNumberUtil.parse(
+                  TelecomCallUtil.getNumber(call),
+                  TelecomCallUtil.getCountryCode(appContext, call).orNull());
+          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, DialerBlockedNumberInfo>>
+      getMostRecentInfo(ImmutableMap<DialerPhoneNumber, DialerBlockedNumberInfo> existingInfoMap) {
+    LogUtil.enterBlock("DialerBlockedNumberPhoneLookup.getMostRecentPhoneLookupInfo");
+    return executorService.submit(() -> queryNumbers(existingInfoMap.keySet()));
+  }
+
+  @Override
+  public void setSubMessage(
+      PhoneLookupInfo.Builder phoneLookupInfo, DialerBlockedNumberInfo subMessage) {
+    phoneLookupInfo.setDialerBlockedNumberInfo(subMessage);
+  }
+
+  @Override
+  public DialerBlockedNumberInfo getSubMessage(PhoneLookupInfo phoneLookupInfo) {
+    return phoneLookupInfo.getDialerBlockedNumberInfo();
+  }
+
+  @Override
+  public ListenableFuture<Void> onSuccessfulBulkUpdate() {
+    return Futures.immediateFuture(null);
+  }
+
+  @WorkerThread
+  private ImmutableMap<DialerPhoneNumber, DialerBlockedNumberInfo> queryNumbers(
+      ImmutableSet<DialerPhoneNumber> numbers) {
+    Assert.isWorkerThread();
+    PartitionedNumbers partitionedNumbers = new PartitionedNumbers(numbers);
+
+    Set<DialerPhoneNumber> blockedNumbers = new ArraySet<>();
+
+    Selection normalizedSelection =
+        Selection.column(FilteredNumberColumns.NORMALIZED_NUMBER)
+            .in(partitionedNumbers.validE164Numbers());
+    try (Cursor cursor =
+        appContext
+            .getContentResolver()
+            .query(
+                FilteredNumber.CONTENT_URI,
+                new String[] {FilteredNumberColumns.NORMALIZED_NUMBER, FilteredNumberColumns.TYPE},
+                normalizedSelection.getSelection(),
+                normalizedSelection.getSelectionArgs(),
+                null)) {
+      while (cursor != null && cursor.moveToNext()) {
+        if (cursor.getInt(1) == FilteredNumberTypes.BLOCKED_NUMBER) {
+          blockedNumbers.addAll(partitionedNumbers.dialerPhoneNumbersForE164(cursor.getString(0)));
+        }
+      }
+    }
+
+    Selection rawSelection =
+        Selection.column(FilteredNumberColumns.NUMBER)
+            .in(
+                partitionedNumbers
+                    .unformattableNumbers()
+                    .toArray(new String[partitionedNumbers.unformattableNumbers().size()]));
+    try (Cursor cursor =
+        appContext
+            .getContentResolver()
+            .query(
+                FilteredNumber.CONTENT_URI,
+                new String[] {FilteredNumberColumns.NUMBER, FilteredNumberColumns.TYPE},
+                rawSelection.getSelection(),
+                rawSelection.getSelectionArgs(),
+                null)) {
+      while (cursor != null && cursor.moveToNext()) {
+        if (cursor.getInt(1) == FilteredNumberTypes.BLOCKED_NUMBER) {
+          blockedNumbers.addAll(
+              partitionedNumbers.dialerPhoneNumbersForUnformattable(cursor.getString(0)));
+        }
+      }
+    }
+
+    ImmutableMap.Builder<DialerPhoneNumber, DialerBlockedNumberInfo> result =
+        ImmutableMap.builder();
+
+    for (DialerPhoneNumber number : numbers) {
+      result.put(
+          number,
+          DialerBlockedNumberInfo.newBuilder()
+              .setBlockedState(
+                  blockedNumbers.contains(number) ? BlockedState.BLOCKED : BlockedState.NOT_BLOCKED)
+              .build());
+    }
+
+    return result.build();
+  }
+
+  @Override
+  public void registerContentObservers(
+      Context appContext, ContentObserverCallbacks contentObserverCallbacks) {
+    appContext
+        .getContentResolver()
+        .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(null);
+      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);
+    }
+  }
+}
diff --git a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java
index da4378b..34f3531 100644
--- a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java
+++ b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java
@@ -16,6 +16,8 @@
 
 package com.android.dialer.phonelookup.composite;
 
+import android.content.Context;
+import android.support.annotation.MainThread;
 import android.support.annotation.NonNull;
 import android.telecom.Call;
 import com.android.dialer.DialerPhoneNumber;
@@ -60,7 +62,7 @@
    * <p>Note: If any of the dependent lookups fails, the returned future will also fail. If any of
    * the dependent lookups does not complete, the returned future will also not complete.
    */
-  @SuppressWarnings("unchecked")
+  @SuppressWarnings({"unchecked", "rawtype"})
   @Override
   public ListenableFuture<PhoneLookupInfo> lookup(@NonNull Call call) {
     // TODO(zachh): Add short-circuiting logic so that this call is not blocked on low-priority
@@ -164,4 +166,13 @@
     return Futures.transform(
         Futures.allAsList(futures), unused -> null, lightweightExecutorService);
   }
+
+  @Override
+  @MainThread
+  public void registerContentObservers(
+      Context appContext, ContentObserverCallbacks contentObserverCallbacks) {
+    for (PhoneLookup phoneLookup : phoneLookups) {
+      phoneLookup.registerContentObservers(appContext, contentObserverCallbacks);
+    }
+  }
 }
diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java
index 60c934a..307e0a4 100644
--- a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java
+++ b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java
@@ -37,7 +37,7 @@
 import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info;
 import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo;
 import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory;
-import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil;
+import com.android.dialer.phonenumberproto.PartitionedNumbers;
 import com.android.dialer.storage.Unencrypted;
 import com.android.dialer.telecom.TelecomCallUtil;
 import com.google.common.base.Optional;
@@ -45,7 +45,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.i18n.phonenumbers.PhoneNumberUtil;
 import com.google.protobuf.InvalidProtocolBufferException;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -392,6 +391,12 @@
         });
   }
 
+  @Override
+  public void registerContentObservers(
+      Context appContext, ContentObserverCallbacks contentObserverCallbacks) {
+    // Do nothing since CP2 changes are too noisy.
+  }
+
   /**
    * 1. get all contact ids. if the id is unset, add the number to the list of contacts to look up.
    * 2. reduce our list of contact ids to those that were updated after lastModified. 3. Now we have
@@ -475,7 +480,8 @@
     // Divide the numbers into those we can format to E164 and those we can't. Then run separate
     // queries against the contacts table using the NORMALIZED_NUMBER and NUMBER columns.
     // TODO(zachh): These queries are inefficient without a lastModified column to filter on.
-    PartitionedNumbers partitionedNumbers = new PartitionedNumbers(updatedNumbers);
+    PartitionedNumbers partitionedNumbers =
+        new PartitionedNumbers(ImmutableSet.copyOf(updatedNumbers));
     if (!partitionedNumbers.validE164Numbers().isEmpty()) {
       try (Cursor cursor =
           queryPhoneTableBasedOnE164(CP2_INFO_PROJECTION, partitionedNumbers.validE164Numbers())) {
@@ -701,56 +707,4 @@
     }
     return where.toString();
   }
-
-  /**
-   * Divides a set of {@link DialerPhoneNumber DialerPhoneNumbers} by those that can be formatted to
-   * E164 and those that cannot.
-   */
-  private static class PartitionedNumbers {
-    private Map<String, Set<DialerPhoneNumber>> e164NumbersToDialerPhoneNumbers = new ArrayMap<>();
-    private Map<String, Set<DialerPhoneNumber>> unformattableNumbersToDialerPhoneNumbers =
-        new ArrayMap<>();
-
-    PartitionedNumbers(Set<DialerPhoneNumber> dialerPhoneNumbers) {
-      DialerPhoneNumberUtil dialerPhoneNumberUtil =
-          new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance());
-      for (DialerPhoneNumber dialerPhoneNumber : dialerPhoneNumbers) {
-        Optional<String> e164 = dialerPhoneNumberUtil.formatToE164(dialerPhoneNumber);
-        if (e164.isPresent()) {
-          String validE164 = e164.get();
-          Set<DialerPhoneNumber> currentNumbers = e164NumbersToDialerPhoneNumbers.get(validE164);
-          if (currentNumbers == null) {
-            currentNumbers = new ArraySet<>();
-            e164NumbersToDialerPhoneNumbers.put(validE164, currentNumbers);
-          }
-          currentNumbers.add(dialerPhoneNumber);
-        } else {
-          String unformattableNumber = dialerPhoneNumber.getRawInput().getNumber();
-          Set<DialerPhoneNumber> currentNumbers =
-              unformattableNumbersToDialerPhoneNumbers.get(unformattableNumber);
-          if (currentNumbers == null) {
-            currentNumbers = new ArraySet<>();
-            unformattableNumbersToDialerPhoneNumbers.put(unformattableNumber, currentNumbers);
-          }
-          currentNumbers.add(dialerPhoneNumber);
-        }
-      }
-    }
-
-    Set<String> unformattableNumbers() {
-      return unformattableNumbersToDialerPhoneNumbers.keySet();
-    }
-
-    Set<String> validE164Numbers() {
-      return e164NumbersToDialerPhoneNumbers.keySet();
-    }
-
-    Set<DialerPhoneNumber> dialerPhoneNumbersForE164(String e164) {
-      return e164NumbersToDialerPhoneNumbers.get(e164);
-    }
-
-    Set<DialerPhoneNumber> dialerPhoneNumbersForUnformattable(String unformattableNumber) {
-      return unformattableNumbersToDialerPhoneNumbers.get(unformattableNumber);
-    }
-  }
 }
diff --git a/java/com/android/dialer/phonelookup/phone_lookup_info.proto b/java/com/android/dialer/phonelookup/phone_lookup_info.proto
index c42faf4..75423b9 100644
--- a/java/com/android/dialer/phonelookup/phone_lookup_info.proto
+++ b/java/com/android/dialer/phonelookup/phone_lookup_info.proto
@@ -83,4 +83,26 @@
     optional InfoType info_type = 6;
   }
   optional PeopleApiInfo people_api_info = 3;
+
+  // Whether a number is blocked or not. Used by both the system blacklist and
+  // dialer fallback
+  enum BlockedState {
+    UNKNOWN = 0;
+    BLOCKED = 1;
+    NOT_BLOCKED = 2;
+  }
+
+  // Message for the android system BlockedNumber lookup. Available starting in
+  // N.
+  message SystemBlockedNumberInfo {
+    optional BlockedState blocked_state = 1;
+  }
+  optional SystemBlockedNumberInfo system_blocked_number_info = 4;
+
+  // Message for the dialer fallback for blocked number. Used in M or when the
+  // migration to the system has not been completed.
+  message DialerBlockedNumberInfo {
+    optional BlockedState blocked_state = 1;
+  }
+  optional DialerBlockedNumberInfo dialer_blocked_number_info = 5;
 }
\ No newline at end of file
diff --git a/java/com/android/dialer/phonelookup/selector/AndroidManifest.xml b/java/com/android/dialer/phonelookup/selector/AndroidManifest.xml
new file mode 100644
index 0000000..5d836c7
--- /dev/null
+++ b/java/com/android/dialer/phonelookup/selector/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<!--
+ ~ 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
+ -->
+<manifest
+    package="com.android.dialer.phonelookup.selector">
+</manifest>
diff --git a/java/com/android/dialer/phonelookup/PhoneLookupSelector.java b/java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java
similarity index 78%
rename from java/com/android/dialer/phonelookup/PhoneLookupSelector.java
rename to java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java
index c933af7..a960d4e 100644
--- a/java/com/android/dialer/phonelookup/PhoneLookupSelector.java
+++ b/java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java
@@ -13,13 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License
  */
-package com.android.dialer.phonelookup;
+package com.android.dialer.phonelookup.selector;
 
+import android.content.Context;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+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.Cp2Info.Cp2ContactInfo;
 import com.android.dialer.phonelookup.PhoneLookupInfo.PeopleApiInfo;
 import com.android.dialer.phonelookup.PhoneLookupInfo.PeopleApiInfo.InfoType;
+import javax.inject.Inject;
 
 /**
  * Prioritizes information from a {@link PhoneLookupInfo}.
@@ -35,12 +42,20 @@
  */
 public final class PhoneLookupSelector {
 
+  private final Context appContext;
+
+  @Inject
+  @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+  public PhoneLookupSelector(@ApplicationContext Context appContext) {
+    this.appContext = appContext;
+  }
+
   /**
    * Select the name associated with this number. Examples of this are a local contact's name or a
    * business name received from caller ID.
    */
   @NonNull
-  public static String selectName(PhoneLookupInfo phoneLookupInfo) {
+  public String selectName(PhoneLookupInfo phoneLookupInfo) {
     Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo);
     if (firstLocalContact != null) {
       String name = firstLocalContact.getName();
@@ -56,7 +71,7 @@
 
   /** Select the photo URI associated with this number. */
   @NonNull
-  public static String selectPhotoUri(PhoneLookupInfo phoneLookupInfo) {
+  public String selectPhotoUri(PhoneLookupInfo phoneLookupInfo) {
     Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo);
     if (firstLocalContact != null) {
       String photoUri = firstLocalContact.getPhotoUri();
@@ -68,7 +83,7 @@
   }
 
   /** Select the photo ID associated with this number, or 0 if there is none. */
-  public static long selectPhotoId(PhoneLookupInfo phoneLookupInfo) {
+  public long selectPhotoId(PhoneLookupInfo phoneLookupInfo) {
     Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo);
     if (firstLocalContact != null) {
       long photoId = firstLocalContact.getPhotoId();
@@ -81,7 +96,7 @@
 
   /** Select the lookup URI associated with this number. */
   @NonNull
-  public static String selectLookupUri(PhoneLookupInfo phoneLookupInfo) {
+  public String selectLookupUri(PhoneLookupInfo phoneLookupInfo) {
     Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo);
     if (firstLocalContact != null) {
       String lookupUri = firstLocalContact.getLookupUri();
@@ -97,7 +112,11 @@
    * set by the user.
    */
   @NonNull
-  public static String selectNumberLabel(PhoneLookupInfo phoneLookupInfo) {
+  public String selectNumberLabel(PhoneLookupInfo phoneLookupInfo) {
+    if (isBlocked(phoneLookupInfo)) {
+      return appContext.getString(R.string.blocked_number_new_call_log_label);
+    }
+
     Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo);
     if (firstLocalContact != null) {
       String label = firstLocalContact.getLabel();
@@ -135,10 +154,15 @@
    * show a synthesized photo containing photos of both "Mom" and "Dad").
    */
   @Nullable
-  private static Cp2ContactInfo firstLocalContact(PhoneLookupInfo phoneLookupInfo) {
+  private Cp2ContactInfo firstLocalContact(PhoneLookupInfo phoneLookupInfo) {
     if (phoneLookupInfo.getCp2Info().getCp2ContactInfoCount() > 0) {
       return phoneLookupInfo.getCp2Info().getCp2ContactInfo(0);
     }
     return null;
   }
+
+  private static boolean isBlocked(PhoneLookupInfo info) {
+    return info.hasDialerBlockedNumberInfo()
+        && info.getDialerBlockedNumberInfo().getBlockedState().equals(BlockedState.BLOCKED);
+  }
 }
diff --git a/java/com/android/dialer/phonelookup/selector/res/values/strings.xml b/java/com/android/dialer/phonelookup/selector/res/values/strings.xml
new file mode 100644
index 0000000..2080b39
--- /dev/null
+++ b/java/com/android/dialer/phonelookup/selector/res/values/strings.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ 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
+  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+  <!-- Label under the name of a blocked number in the call log. [CHAR LIMIT=15] -->
+  <string name="blocked_number_new_call_log_label">Blocked</string>
+
+</resources>
diff --git a/java/com/android/dialer/phonenumberproto/PartitionedNumbers.java b/java/com/android/dialer/phonenumberproto/PartitionedNumbers.java
new file mode 100644
index 0000000..372f21e
--- /dev/null
+++ b/java/com/android/dialer/phonenumberproto/PartitionedNumbers.java
@@ -0,0 +1,114 @@
+/*
+ * 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.phonenumberproto;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.WorkerThread;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.ArraySet;
+import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.common.Assert;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Divides a set of {@link DialerPhoneNumber DialerPhoneNumbers} by those that can be formatted to
+ * E164 and those that cannot.
+ */
+public final class PartitionedNumbers {
+  private final ImmutableMap<String, ImmutableSet<DialerPhoneNumber>>
+      e164NumbersToDialerPhoneNumbers;
+  private final ImmutableMap<String, ImmutableSet<DialerPhoneNumber>>
+      unformattableNumbersToDialerPhoneNumbers;
+
+  @WorkerThread
+  public PartitionedNumbers(@NonNull ImmutableSet<DialerPhoneNumber> dialerPhoneNumbers) {
+    Assert.isWorkerThread();
+    DialerPhoneNumberUtil dialerPhoneNumberUtil =
+        new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance());
+    Map<String, Set<DialerPhoneNumber>> e164MapBuilder = new ArrayMap<>();
+    Map<String, Set<DialerPhoneNumber>> unformattableMapBuilder = new ArrayMap<>();
+
+    for (DialerPhoneNumber dialerPhoneNumber : dialerPhoneNumbers) {
+      Optional<String> e164 = dialerPhoneNumberUtil.formatToE164(dialerPhoneNumber);
+      if (e164.isPresent()) {
+        String validE164 = e164.get();
+        Set<DialerPhoneNumber> currentNumbers = e164MapBuilder.get(validE164);
+        if (currentNumbers == null) {
+          currentNumbers = new ArraySet<>();
+          e164MapBuilder.put(validE164, currentNumbers);
+        }
+        currentNumbers.add(dialerPhoneNumber);
+      } else {
+        String unformattableNumber = dialerPhoneNumber.getRawInput().getNumber();
+        Set<DialerPhoneNumber> currentNumbers = unformattableMapBuilder.get(unformattableNumber);
+        if (currentNumbers == null) {
+          currentNumbers = new ArraySet<>();
+          unformattableMapBuilder.put(unformattableNumber, currentNumbers);
+        }
+        currentNumbers.add(dialerPhoneNumber);
+      }
+    }
+
+    e164NumbersToDialerPhoneNumbers = makeImmutable(e164MapBuilder);
+    unformattableNumbersToDialerPhoneNumbers = makeImmutable(unformattableMapBuilder);
+  }
+
+  /** Returns the set of formatted number from the original DialerPhoneNumbers */
+  @NonNull
+  public ImmutableSet<String> unformattableNumbers() {
+    return unformattableNumbersToDialerPhoneNumbers.keySet();
+  }
+
+  /** Returns the set of raw number that is unformattable from the original DialerPhoneNumbers */
+  @NonNull
+  public ImmutableSet<String> validE164Numbers() {
+    return e164NumbersToDialerPhoneNumbers.keySet();
+  }
+
+  /**
+   * Returns the corresponding set of original DialerPhoneNumber that maps to the e.164 number, or
+   * an empty set if the number is not found.
+   */
+  @NonNull
+  public ImmutableSet<DialerPhoneNumber> dialerPhoneNumbersForE164(String e164) {
+    return Assert.isNotNull(e164NumbersToDialerPhoneNumbers.get(e164));
+  }
+
+  /**
+   * Returns the corresponding set of original DialerPhoneNumber that maps to the unformattable
+   * number returned by {@link #unformattableNumbers()}, or an empty set if the number is not found.
+   */
+  @NonNull
+  public ImmutableSet<DialerPhoneNumber> dialerPhoneNumbersForUnformattable(
+      String unformattableNumber) {
+    return Assert.isNotNull(unformattableNumbersToDialerPhoneNumbers.get(unformattableNumber));
+  }
+
+  private static <K, V> ImmutableMap<K, ImmutableSet<V>> makeImmutable(
+      Map<K, Set<V>> mutableMapOfSet) {
+    ImmutableMap.Builder<K, ImmutableSet<V>> mapBuilder = ImmutableMap.builder();
+    for (Map.Entry<K, Set<V>> entry : mutableMapOfSet.entrySet()) {
+      mapBuilder.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue()));
+    }
+    return mapBuilder.build();
+  }
+}
diff --git a/java/com/android/dialer/telecom/TelecomCallUtil.java b/java/com/android/dialer/telecom/TelecomCallUtil.java
index b877a73..7d71b4b 100644
--- a/java/com/android/dialer/telecom/TelecomCallUtil.java
+++ b/java/com/android/dialer/telecom/TelecomCallUtil.java
@@ -103,20 +103,27 @@
   @WorkerThread
   public static Optional<String> getE164Number(Context appContext, Call call) {
     Assert.isWorkerThread();
-    PhoneAccountHandle phoneAccountHandle = call.getDetails().getAccountHandle();
-    Optional<SubscriptionInfo> subscriptionInfo =
-        TelecomUtil.getSubscriptionInfo(appContext, phoneAccountHandle);
     String rawNumber = getNumber(call);
     if (TextUtils.isEmpty(rawNumber)) {
       return Optional.absent();
     }
-    String countryCode =
-        subscriptionInfo.isPresent() ? subscriptionInfo.get().getCountryIso() : null;
-    if (countryCode == null) {
+    Optional<String> countryCode = getCountryCode(appContext, call);
+    if (!countryCode.isPresent()) {
       LogUtil.w("TelecomCallUtil.getE164Number", "couldn't find a country code for call");
       return Optional.absent();
     }
-    return Optional.fromNullable(
-        PhoneNumberUtils.formatNumberToE164(rawNumber, countryCode.toUpperCase(Locale.US)));
+    return Optional.fromNullable(PhoneNumberUtils.formatNumberToE164(rawNumber, countryCode.get()));
+  }
+
+  @WorkerThread
+  public static Optional<String> getCountryCode(Context appContext, Call call) {
+    Assert.isWorkerThread();
+    PhoneAccountHandle phoneAccountHandle = call.getDetails().getAccountHandle();
+    Optional<SubscriptionInfo> subscriptionInfo =
+        TelecomUtil.getSubscriptionInfo(appContext, phoneAccountHandle);
+    if (subscriptionInfo.isPresent() && subscriptionInfo.get().getCountryIso() != null) {
+      return Optional.of(subscriptionInfo.get().getCountryIso().toUpperCase(Locale.US));
+    }
+    return Optional.absent();
   }
 }
diff --git a/packages.mk b/packages.mk
index a1cf7fc..03268fd 100644
--- a/packages.mk
+++ b/packages.mk
@@ -39,6 +39,7 @@
 	com.android.dialer.notification \
 	com.android.dialer.oem \
 	com.android.dialer.phonelookup.database \
+	com.android.dialer.phonelookup.selector \
 	com.android.dialer.phonenumberutil \
 	com.android.dialer.postcall \
 	com.android.dialer.precall.impl \