Merge "Implement dialer blocked number phone lookup"
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 \