Merge changes I6af1fc7d,I0627d54a

* changes:
  Automated g4 rollback of changelist 167310802.
  Automated g4 rollback of changelist 167332236.
diff --git a/java/com/android/dialer/app/AndroidManifest.xml b/java/com/android/dialer/app/AndroidManifest.xml
index 1c04b76..4200082 100644
--- a/java/com/android/dialer/app/AndroidManifest.xml
+++ b/java/com/android/dialer/app/AndroidManifest.xml
@@ -61,6 +61,18 @@
   <application android:theme="@style/Theme.AppCompat">
 
     <activity
+      android:exported="false"
+      android:label="@string/manage_blocked_numbers_label"
+      android:name="com.android.dialer.app.filterednumber.BlockedNumbersSettingsActivity"
+      android:parentActivityName="com.android.dialer.app.settings.DialerSettingsActivity"
+      android:theme="@style/ManageBlockedNumbersStyle">
+      <intent-filter>
+        <action android:name="com.android.dialer.action.BLOCKED_NUMBERS_SETTINGS"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+      </intent-filter>
+    </activity>
+
+    <activity
       android:label="@string/call_log_activity_title"
       android:name="com.android.dialer.app.calllog.CallLogActivity"
       android:theme="@style/DialtactsThemeWithoutActionBarOverlay">
diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java
index 7f5a9b9..13b6eb9 100644
--- a/java/com/android/dialer/app/DialtactsActivity.java
+++ b/java/com/android/dialer/app/DialtactsActivity.java
@@ -49,6 +49,7 @@
 import android.text.TextWatcher;
 import android.view.DragEvent;
 import android.view.Gravity;
+import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.MotionEvent;
@@ -168,7 +169,6 @@
   private static final String TAG = "DialtactsActivity";
   private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui";
   private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui";
-  private static final String KEY_IN_NEW_SEARCH_UI = "in_new_search_ui";
   private static final String KEY_SEARCH_QUERY = "search_query";
   private static final String KEY_FIRST_LAUNCH = "first_launch";
   private static final String KEY_WAS_CONFIGURATION_CHANGE = "was_configuration_change";
@@ -213,8 +213,6 @@
    */
   private boolean mStateSaved;
 
-  private boolean mIsKeyboardOpen;
-  private boolean mInNewSearch;
   private boolean mIsRestarting;
   private boolean mInDialpadSearch;
   private boolean mInRegularSearch;
@@ -332,6 +330,27 @@
 
   private int mActionBarHeight;
   private int mPreviouslySelectedTabIndex;
+  /** Handles the user closing the soft keyboard. */
+  private final View.OnKeyListener mSearchEditTextLayoutListener =
+      new View.OnKeyListener() {
+        @Override
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+          if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
+            if (TextUtils.isEmpty(mSearchView.getText().toString())) {
+              // If the search term is empty, close the search UI.
+              PerformanceReport.recordClick(UiAction.Type.CLOSE_SEARCH_WITH_HIDE_BUTTON);
+              maybeExitSearchUi();
+            } else {
+              // If the search term is not empty, show the dialpad fab.
+              if (!mFloatingActionButtonController.isVisible()) {
+                PerformanceReport.recordClick(UiAction.Type.HIDE_KEYBOARD_IN_SEARCH);
+              }
+              showFabInSearchUi();
+            }
+          }
+          return false;
+        }
+      };
 
   /**
    * The text returned from a voice search query. Set in {@link #onActivityResult} and used in
@@ -391,20 +410,30 @@
 
     SearchEditTextLayout searchEditTextLayout =
         actionBar.getCustomView().findViewById(R.id.search_view_container);
+    searchEditTextLayout.setPreImeKeyListener(mSearchEditTextLayoutListener);
 
     mActionBarController = new ActionBarController(this, searchEditTextLayout);
 
     mSearchView = searchEditTextLayout.findViewById(R.id.search_view);
     mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
     mSearchView.setHint(getSearchBoxHint());
-
     mVoiceSearchButton = searchEditTextLayout.findViewById(R.id.voice_search_button);
     searchEditTextLayout
         .findViewById(R.id.search_box_collapsed)
         .setOnClickListener(mSearchViewOnClickListener);
-    searchEditTextLayout
-        .findViewById(R.id.search_back_button)
-        .setOnClickListener(v -> exitSearchUi());
+    searchEditTextLayout.setCallback(
+        new SearchEditTextLayout.Callback() {
+          @Override
+          public void onBackButtonClicked() {
+            onBackPressed();
+          }
+
+          @Override
+          public void onSearchViewClicked() {
+            // Hide FAB, as the keyboard is shown.
+            mFloatingActionButtonController.scaleOut();
+          }
+        });
 
     mIsLandscape =
         getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
@@ -431,7 +460,6 @@
       mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY);
       mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI);
       mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI);
-      mInNewSearch = savedInstanceState.getBoolean(KEY_IN_NEW_SEARCH_UI);
       mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH);
       mWasConfigurationChange = savedInstanceState.getBoolean(KEY_WAS_CONFIGURATION_CHANGE);
       mShowDialpadOnResume = savedInstanceState.getBoolean(KEY_IS_DIALPAD_SHOWN);
@@ -626,7 +654,6 @@
     outState.putString(KEY_SEARCH_QUERY, mSearchQuery);
     outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch);
     outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch);
-    outState.putBoolean(KEY_IN_NEW_SEARCH_UI, mInNewSearch);
     outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch);
     outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown);
     outState.putBoolean(KEY_WAS_CONFIGURATION_CHANGE, isChangingConfigurations());
@@ -866,19 +893,14 @@
     updateSearchFragmentPosition();
   }
 
-  @Override
-  public void onCallPlacedFromDialpad() {
-    hideDialpadFragment(false /* animate */, true /*clearDialpad */);
-    exitSearchUi();
-  }
-
   /**
    * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in a
    * callback after the hide animation ends.
    *
    * @see #commitDialpadFragmentHide
    */
-  private void hideDialpadFragment(boolean animate, boolean clearDialpad) {
+  @Override
+  public void hideDialpadFragment(boolean animate, boolean clearDialpad) {
     LogUtil.enterBlock("DialtactsActivity.hideDialpadFragment");
     if (mDialpadFragment == null || mDialpadFragment.getView() == null) {
       return;
@@ -913,6 +935,11 @@
 
     mActionBarController.onDialpadDown();
 
+    if (isInSearchUi()) {
+      if (TextUtils.isEmpty(mSearchQuery)) {
+        exitSearchUi();
+      }
+    }
     // reset the title to normal.
     setTitle(R.string.launcherActivityLabel);
   }
@@ -960,7 +987,7 @@
 
   @Override
   public boolean isInSearchUi() {
-    return mInDialpadSearch || mInRegularSearch || mInNewSearch;
+    return mInDialpadSearch || mInRegularSearch;
   }
 
   @Override
@@ -971,7 +998,6 @@
   private void setNotInSearchUi() {
     mInDialpadSearch = false;
     mInRegularSearch = false;
-    mInNewSearch = false;
   }
 
   private void hideDialpadAndSearchUi() {
@@ -1145,21 +1171,17 @@
     }
 
     final String tag;
-    mInDialpadSearch = false;
-    mInRegularSearch = false;
-    mInNewSearch = false;
     boolean useNewSearch =
         ConfigProviderBindings.get(this).getBoolean("enable_new_search_fragment", false);
     if (useNewSearch) {
       tag = TAG_NEW_SEARCH_FRAGMENT;
-      mInNewSearch = true;
     } else if (smartDialSearch) {
       tag = TAG_SMARTDIAL_SEARCH_FRAGMENT;
-      mInDialpadSearch = true;
     } else {
       tag = TAG_REGULAR_SEARCH_FRAGMENT;
-      mInRegularSearch = true;
     }
+    mInDialpadSearch = smartDialSearch;
+    mInRegularSearch = !smartDialSearch;
 
     mFloatingActionButtonController.scaleOut();
 
@@ -1282,36 +1304,45 @@
       return;
     }
     if (mIsDialpadShown) {
-      hideDialpadFragment(true, false);
-    } else if (isInSearchUi()) {
-      if (mIsKeyboardOpen) {
-        DialerUtils.hideInputMethod(mParentLayout);
-        PerformanceReport.recordClick(UiAction.Type.HIDE_KEYBOARD_IN_SEARCH);
-      } else {
+      if (TextUtils.isEmpty(mSearchQuery)
+          || (mSmartDialSearchFragment != null
+              && mSmartDialSearchFragment.isVisible()
+              && mSmartDialSearchFragment.getAdapter().getCount() == 0)) {
         exitSearchUi();
       }
+      hideDialpadFragment(true, false);
+    } else if (isInSearchUi()) {
+      exitSearchUi();
+      DialerUtils.hideInputMethod(mParentLayout);
     } else {
       super.onBackPressed();
     }
   }
 
-  @Override
-  public void onConfigurationChanged(Configuration configuration) {
-    super.onConfigurationChanged(configuration);
-    // Checks whether a hardware keyboard is available
-    if (configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
-      mIsKeyboardOpen = true;
-    } else if (configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
-      mIsKeyboardOpen = false;
-    }
-  }
-
   private void maybeEnterSearchUi() {
     if (!isInSearchUi()) {
       enterSearchUi(true /* isSmartDial */, mSearchQuery, false);
     }
   }
 
+  /** @return True if the search UI was exited, false otherwise */
+  private boolean maybeExitSearchUi() {
+    if (isInSearchUi() && TextUtils.isEmpty(mSearchQuery)) {
+      exitSearchUi();
+      DialerUtils.hideInputMethod(mParentLayout);
+      return true;
+    }
+    return false;
+  }
+
+  private void showFabInSearchUi() {
+    mFloatingActionButtonController.changeIcon(
+        getResources().getDrawable(R.drawable.quantum_ic_dialpad_white_24, null),
+        getResources().getString(R.string.action_menu_dialpad_button));
+    mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
+    mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS);
+  }
+
   @Override
   public void onDialpadQueryChanged(String query) {
     mDialpadQuery = query;
diff --git a/java/com/android/dialer/app/filterednumber/BlockedNumbersAdapter.java b/java/com/android/dialer/app/filterednumber/BlockedNumbersAdapter.java
new file mode 100644
index 0000000..4f8bc66
--- /dev/null
+++ b/java/com/android/dialer/app/filterednumber/BlockedNumbersAdapter.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2015 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.app.filterednumber;
+
+import android.app.FragmentManager;
+import android.content.Context;
+import android.database.Cursor;
+import android.telephony.PhoneNumberUtils;
+import android.view.View;
+import com.android.dialer.app.R;
+import com.android.dialer.blocking.BlockNumberDialogFragment;
+import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns;
+import com.android.dialer.location.GeoUtil;
+import com.android.dialer.logging.InteractionEvent;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.phonenumbercache.ContactInfoHelper;
+
+/** TODO(calderwoodra): documentation */
+public class BlockedNumbersAdapter extends NumbersAdapter {
+
+  private BlockedNumbersAdapter(
+      Context context,
+      FragmentManager fragmentManager,
+      ContactInfoHelper contactInfoHelper,
+      ContactPhotoManager contactPhotoManager) {
+    super(context, fragmentManager, contactInfoHelper, contactPhotoManager);
+  }
+
+  public static BlockedNumbersAdapter newBlockedNumbersAdapter(
+      Context context, FragmentManager fragmentManager) {
+    return new BlockedNumbersAdapter(
+        context,
+        fragmentManager,
+        new ContactInfoHelper(context, GeoUtil.getCurrentCountryIso(context)),
+        ContactPhotoManager.getInstance(context));
+  }
+
+  @Override
+  public void bindView(View view, final Context context, Cursor cursor) {
+    super.bindView(view, context, cursor);
+    final Integer id = cursor.getInt(cursor.getColumnIndex(FilteredNumberColumns._ID));
+    final String countryIso =
+        cursor.getString(cursor.getColumnIndex(FilteredNumberColumns.COUNTRY_ISO));
+    final String number = cursor.getString(cursor.getColumnIndex(FilteredNumberColumns.NUMBER));
+
+    final View deleteButton = view.findViewById(R.id.delete_button);
+    deleteButton.setOnClickListener(
+        new View.OnClickListener() {
+          @Override
+          public void onClick(View view) {
+            BlockNumberDialogFragment.show(
+                id,
+                number,
+                countryIso,
+                PhoneNumberUtils.formatNumber(number, countryIso),
+                R.id.blocked_numbers_activity_container,
+                getFragmentManager(),
+                new BlockNumberDialogFragment.Callback() {
+                  @Override
+                  public void onFilterNumberSuccess() {}
+
+                  @Override
+                  public void onUnfilterNumberSuccess() {
+                    Logger.get(context)
+                        .logInteraction(InteractionEvent.Type.UNBLOCK_NUMBER_MANAGEMENT_SCREEN);
+                  }
+
+                  @Override
+                  public void onChangeFilteredNumberUndo() {}
+                });
+          }
+        });
+
+    updateView(view, number, countryIso);
+  }
+
+  @Override
+  public boolean isEmpty() {
+    // Always return false, so that the header with blocking-related options always shows.
+    return false;
+  }
+}
diff --git a/java/com/android/dialer/app/filterednumber/BlockedNumbersFragment.java b/java/com/android/dialer/app/filterednumber/BlockedNumbersFragment.java
new file mode 100644
index 0000000..36afe54
--- /dev/null
+++ b/java/com/android/dialer/app/filterednumber/BlockedNumbersFragment.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2015 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.app.filterednumber;
+
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.support.v4.app.ActivityCompat;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.dialer.app.R;
+import com.android.dialer.blocking.BlockedNumbersMigrator;
+import com.android.dialer.blocking.BlockedNumbersMigrator.Listener;
+import com.android.dialer.blocking.FilteredNumberCompat;
+import com.android.dialer.blocking.FilteredNumbersUtil;
+import com.android.dialer.blocking.FilteredNumbersUtil.CheckForSendToVoicemailContactListener;
+import com.android.dialer.blocking.FilteredNumbersUtil.ImportSendToVoicemailContactsListener;
+import com.android.dialer.database.FilteredNumberContract;
+import com.android.dialer.lettertile.LetterTileDrawable;
+import com.android.dialer.voicemailstatus.VisualVoicemailEnabledChecker;
+
+/** TODO(calderwoodra): documentation */
+public class BlockedNumbersFragment extends ListFragment
+    implements LoaderManager.LoaderCallbacks<Cursor>,
+        View.OnClickListener,
+        VisualVoicemailEnabledChecker.Callback {
+
+  private static final char ADD_BLOCKED_NUMBER_ICON_LETTER = '+';
+  protected View migratePromoView;
+  private BlockedNumbersMigrator blockedNumbersMigratorForTest;
+  private TextView blockedNumbersText;
+  private TextView footerText;
+  private BlockedNumbersAdapter mAdapter;
+  private VisualVoicemailEnabledChecker mVoicemailEnabledChecker;
+  private View mImportSettings;
+  private View mBlockedNumbersDisabledForEmergency;
+  private View mBlockedNumberListDivider;
+
+  @Override
+  public Context getContext() {
+    return getActivity();
+  }
+
+  @Override
+  public void onActivityCreated(Bundle savedInstanceState) {
+    super.onActivityCreated(savedInstanceState);
+
+    LayoutInflater inflater =
+        (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    getListView().addHeaderView(inflater.inflate(R.layout.blocked_number_header, null));
+    getListView().addFooterView(inflater.inflate(R.layout.blocked_number_footer, null));
+    //replace the icon for add number with LetterTileDrawable(), so it will have identical style
+    ImageView addNumberIcon = (ImageView) getActivity().findViewById(R.id.add_number_icon);
+    LetterTileDrawable drawable = new LetterTileDrawable(getResources());
+    drawable.setLetter(ADD_BLOCKED_NUMBER_ICON_LETTER);
+    drawable.setColor(
+        ActivityCompat.getColor(getActivity(), R.color.add_blocked_number_icon_color));
+    drawable.setIsCircular(true);
+    addNumberIcon.setImageDrawable(drawable);
+
+    if (mAdapter == null) {
+      mAdapter =
+          BlockedNumbersAdapter.newBlockedNumbersAdapter(
+              getContext(), getActivity().getFragmentManager());
+    }
+    setListAdapter(mAdapter);
+
+    blockedNumbersText = (TextView) getListView().findViewById(R.id.blocked_number_text_view);
+    migratePromoView = getListView().findViewById(R.id.migrate_promo);
+    getListView().findViewById(R.id.migrate_promo_allow_button).setOnClickListener(this);
+    mImportSettings = getListView().findViewById(R.id.import_settings);
+    mBlockedNumbersDisabledForEmergency =
+        getListView().findViewById(R.id.blocked_numbers_disabled_for_emergency);
+    mBlockedNumberListDivider = getActivity().findViewById(R.id.blocked_number_list_divider);
+    getListView().findViewById(R.id.import_button).setOnClickListener(this);
+    getListView().findViewById(R.id.view_numbers_button).setOnClickListener(this);
+    getListView().findViewById(R.id.add_number_linear_layout).setOnClickListener(this);
+
+    footerText = (TextView) getActivity().findViewById(R.id.blocked_number_footer_textview);
+    mVoicemailEnabledChecker = new VisualVoicemailEnabledChecker(getContext(), this);
+    mVoicemailEnabledChecker.asyncUpdate();
+    updateActiveVoicemailProvider();
+  }
+
+  @Override
+  public void onDestroy() {
+    setListAdapter(null);
+    super.onDestroy();
+  }
+
+  @Override
+  public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getLoaderManager().initLoader(0, null, this);
+  }
+
+  @Override
+  public void onResume() {
+    super.onResume();
+
+    ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
+    ColorDrawable backgroundDrawable =
+        new ColorDrawable(ActivityCompat.getColor(getActivity(), R.color.dialer_theme_color));
+    actionBar.setBackgroundDrawable(backgroundDrawable);
+    actionBar.setDisplayShowCustomEnabled(false);
+    actionBar.setDisplayHomeAsUpEnabled(true);
+    actionBar.setDisplayShowHomeEnabled(true);
+    actionBar.setDisplayShowTitleEnabled(true);
+    actionBar.setTitle(R.string.manage_blocked_numbers_label);
+
+    // If the device can use the framework blocking solution, users should not be able to add
+    // new blocked numbers from the Blocked Management UI. They will be shown a promo card
+    // asking them to migrate to new blocking instead.
+    if (FilteredNumberCompat.canUseNewFiltering()) {
+      migratePromoView.setVisibility(View.VISIBLE);
+      blockedNumbersText.setVisibility(View.GONE);
+      getListView().findViewById(R.id.add_number_linear_layout).setVisibility(View.GONE);
+      getListView().findViewById(R.id.add_number_linear_layout).setOnClickListener(null);
+      mBlockedNumberListDivider.setVisibility(View.GONE);
+      mImportSettings.setVisibility(View.GONE);
+      getListView().findViewById(R.id.import_button).setOnClickListener(null);
+      getListView().findViewById(R.id.view_numbers_button).setOnClickListener(null);
+      mBlockedNumbersDisabledForEmergency.setVisibility(View.GONE);
+      footerText.setVisibility(View.GONE);
+    } else {
+      FilteredNumbersUtil.checkForSendToVoicemailContact(
+          getActivity(),
+          new CheckForSendToVoicemailContactListener() {
+            @Override
+            public void onComplete(boolean hasSendToVoicemailContact) {
+              final int visibility = hasSendToVoicemailContact ? View.VISIBLE : View.GONE;
+              mImportSettings.setVisibility(visibility);
+            }
+          });
+    }
+
+    // All views except migrate and the block list are hidden when new filtering is available
+    if (!FilteredNumberCompat.canUseNewFiltering()
+        && FilteredNumbersUtil.hasRecentEmergencyCall(getContext())) {
+      mBlockedNumbersDisabledForEmergency.setVisibility(View.VISIBLE);
+    } else {
+      mBlockedNumbersDisabledForEmergency.setVisibility(View.GONE);
+    }
+
+    mVoicemailEnabledChecker.asyncUpdate();
+  }
+
+  @Override
+  public View onCreateView(
+      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+    return inflater.inflate(R.layout.blocked_number_fragment, container, false);
+  }
+
+  @Override
+  public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+    final String[] projection = {
+      FilteredNumberContract.FilteredNumberColumns._ID,
+      FilteredNumberContract.FilteredNumberColumns.COUNTRY_ISO,
+      FilteredNumberContract.FilteredNumberColumns.NUMBER,
+      FilteredNumberContract.FilteredNumberColumns.NORMALIZED_NUMBER
+    };
+    final String selection =
+        FilteredNumberContract.FilteredNumberColumns.TYPE
+            + "="
+            + FilteredNumberContract.FilteredNumberTypes.BLOCKED_NUMBER;
+    return new CursorLoader(
+        getContext(),
+        FilteredNumberContract.FilteredNumber.CONTENT_URI,
+        projection,
+        selection,
+        null,
+        null);
+  }
+
+  @Override
+  public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+    mAdapter.swapCursor(data);
+    if (FilteredNumberCompat.canUseNewFiltering() || data.getCount() == 0) {
+      mBlockedNumberListDivider.setVisibility(View.INVISIBLE);
+    } else {
+      mBlockedNumberListDivider.setVisibility(View.VISIBLE);
+    }
+  }
+
+  @Override
+  public void onLoaderReset(Loader<Cursor> loader) {
+    mAdapter.swapCursor(null);
+  }
+
+  @Override
+  public void onClick(final View view) {
+    final BlockedNumbersSettingsActivity activity = (BlockedNumbersSettingsActivity) getActivity();
+    if (activity == null) {
+      return;
+    }
+
+    int resId = view.getId();
+    if (resId == R.id.add_number_linear_layout) {
+      activity.showSearchUi();
+    } else if (resId == R.id.view_numbers_button) {
+      activity.showNumbersToImportPreviewUi();
+    } else if (resId == R.id.import_button) {
+      FilteredNumbersUtil.importSendToVoicemailContacts(
+          activity,
+          new ImportSendToVoicemailContactsListener() {
+            @Override
+            public void onImportComplete() {
+              mImportSettings.setVisibility(View.GONE);
+            }
+          });
+    } else if (resId == R.id.migrate_promo_allow_button) {
+      view.setEnabled(false);
+      (blockedNumbersMigratorForTest != null
+              ? blockedNumbersMigratorForTest
+              : new BlockedNumbersMigrator(getContext()))
+          .migrate(
+              new Listener() {
+                @Override
+                public void onComplete() {
+                  getContext()
+                      .startActivity(
+                          FilteredNumberCompat.createManageBlockedNumbersIntent(getContext()));
+                  // Remove this activity from the backstack
+                  activity.finish();
+                }
+              });
+    }
+  }
+
+  @Override
+  public void onVisualVoicemailEnabledStatusChanged(boolean newStatus) {
+    updateActiveVoicemailProvider();
+  }
+
+  private void updateActiveVoicemailProvider() {
+    if (getActivity() == null || getActivity().isFinishing()) {
+      return;
+    }
+    if (mVoicemailEnabledChecker.isVisualVoicemailEnabled()) {
+      footerText.setText(R.string.block_number_footer_message_vvm);
+    } else {
+      footerText.setText(R.string.block_number_footer_message_no_vvm);
+    }
+  }
+
+  void setBlockedNumbersMigratorForTest(BlockedNumbersMigrator blockedNumbersMigrator) {
+    blockedNumbersMigratorForTest = blockedNumbersMigrator;
+  }
+}
diff --git a/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java b/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java
new file mode 100644
index 0000000..858d283
--- /dev/null
+++ b/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2015 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.app.filterednumber;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+import com.android.dialer.app.R;
+import com.android.dialer.app.list.BlockedListSearchFragment;
+import com.android.dialer.app.list.SearchFragment;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.logging.ScreenEvent;
+
+/** TODO(calderwoodra): documentation */
+public class BlockedNumbersSettingsActivity extends AppCompatActivity
+    implements SearchFragment.HostInterface {
+
+  private static final String TAG_BLOCKED_MANAGEMENT_FRAGMENT = "blocked_management";
+  private static final String TAG_BLOCKED_SEARCH_FRAGMENT = "blocked_search";
+  private static final String TAG_VIEW_NUMBERS_TO_IMPORT_FRAGMENT = "view_numbers_to_import";
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.blocked_numbers_activity);
+
+    // If savedInstanceState != null, the Activity will automatically restore the last fragment.
+    if (savedInstanceState == null) {
+      showManagementUi();
+    }
+  }
+
+  /** Shows fragment with the list of currently blocked numbers and settings related to blocking. */
+  public void showManagementUi() {
+    BlockedNumbersFragment fragment =
+        (BlockedNumbersFragment)
+            getFragmentManager().findFragmentByTag(TAG_BLOCKED_MANAGEMENT_FRAGMENT);
+    if (fragment == null) {
+      fragment = new BlockedNumbersFragment();
+    }
+
+    getFragmentManager()
+        .beginTransaction()
+        .replace(R.id.blocked_numbers_activity_container, fragment, TAG_BLOCKED_MANAGEMENT_FRAGMENT)
+        .commit();
+
+    Logger.get(this).logScreenView(ScreenEvent.Type.BLOCKED_NUMBER_MANAGEMENT, this);
+  }
+
+  /** Shows fragment with search UI for browsing/finding numbers to block. */
+  public void showSearchUi() {
+    BlockedListSearchFragment fragment =
+        (BlockedListSearchFragment)
+            getFragmentManager().findFragmentByTag(TAG_BLOCKED_SEARCH_FRAGMENT);
+    if (fragment == null) {
+      fragment = new BlockedListSearchFragment();
+      fragment.setHasOptionsMenu(false);
+      fragment.setShowEmptyListForNullQuery(true);
+      fragment.setDirectorySearchEnabled(false);
+    }
+
+    getFragmentManager()
+        .beginTransaction()
+        .replace(R.id.blocked_numbers_activity_container, fragment, TAG_BLOCKED_SEARCH_FRAGMENT)
+        .addToBackStack(null)
+        .commit();
+
+    Logger.get(this).logScreenView(ScreenEvent.Type.BLOCKED_NUMBER_ADD_NUMBER, this);
+  }
+
+  /**
+   * Shows fragment with UI to preview the numbers of contacts currently marked as send-to-voicemail
+   * in Contacts. These numbers can be imported into Dialer's blocked number list.
+   */
+  public void showNumbersToImportPreviewUi() {
+    ViewNumbersToImportFragment fragment =
+        (ViewNumbersToImportFragment)
+            getFragmentManager().findFragmentByTag(TAG_VIEW_NUMBERS_TO_IMPORT_FRAGMENT);
+    if (fragment == null) {
+      fragment = new ViewNumbersToImportFragment();
+    }
+
+    getFragmentManager()
+        .beginTransaction()
+        .replace(
+            R.id.blocked_numbers_activity_container, fragment, TAG_VIEW_NUMBERS_TO_IMPORT_FRAGMENT)
+        .addToBackStack(null)
+        .commit();
+  }
+
+  @Override
+  public boolean onOptionsItemSelected(MenuItem item) {
+    if (item.getItemId() == android.R.id.home) {
+      onBackPressed();
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public void onBackPressed() {
+    // TODO: Achieve back navigation without overriding onBackPressed.
+    if (getFragmentManager().getBackStackEntryCount() > 0) {
+      getFragmentManager().popBackStack();
+    } else {
+      super.onBackPressed();
+    }
+  }
+
+  @Override
+  public boolean isActionBarShowing() {
+    return false;
+  }
+
+  @Override
+  public boolean isDialpadShown() {
+    return false;
+  }
+
+  @Override
+  public int getDialpadHeight() {
+    return 0;
+  }
+
+  @Override
+  public int getActionBarHeight() {
+    return 0;
+  }
+}
diff --git a/java/com/android/dialer/app/filterednumber/NumbersAdapter.java b/java/com/android/dialer/app/filterednumber/NumbersAdapter.java
new file mode 100644
index 0000000..938a784
--- /dev/null
+++ b/java/com/android/dialer/app/filterednumber/NumbersAdapter.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2015 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.app.filterednumber;
+
+import android.app.FragmentManager;
+import android.content.Context;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.QuickContactBadge;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import com.android.dialer.app.R;
+import com.android.dialer.compat.CompatUtils;
+import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.contactphoto.ContactPhotoManager.DefaultImageRequest;
+import com.android.dialer.lettertile.LetterTileDrawable;
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.phonenumbercache.ContactInfoHelper;
+import com.android.dialer.phonenumberutil.PhoneNumberHelper;
+import com.android.dialer.util.UriUtils;
+
+/** TODO(calderwoodra): documentation */
+public class NumbersAdapter extends SimpleCursorAdapter {
+
+  private final Context mContext;
+  private final FragmentManager mFragmentManager;
+  private final ContactInfoHelper mContactInfoHelper;
+  private final BidiFormatter mBidiFormatter = BidiFormatter.getInstance();
+  private final ContactPhotoManager mContactPhotoManager;
+
+  public NumbersAdapter(
+      Context context,
+      FragmentManager fragmentManager,
+      ContactInfoHelper contactInfoHelper,
+      ContactPhotoManager contactPhotoManager) {
+    super(context, R.layout.blocked_number_item, null, new String[] {}, new int[] {}, 0);
+    mContext = context;
+    mFragmentManager = fragmentManager;
+    mContactInfoHelper = contactInfoHelper;
+    mContactPhotoManager = contactPhotoManager;
+  }
+
+  public void updateView(View view, String number, String countryIso) {
+    final TextView callerName = (TextView) view.findViewById(R.id.caller_name);
+    final TextView callerNumber = (TextView) view.findViewById(R.id.caller_number);
+    final QuickContactBadge quickContactBadge =
+        (QuickContactBadge) view.findViewById(R.id.quick_contact_photo);
+    quickContactBadge.setOverlay(null);
+    if (CompatUtils.hasPrioritizedMimeType()) {
+      quickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
+    }
+
+    ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso);
+    if (info == null) {
+      info = new ContactInfo();
+      info.number = number;
+    }
+    final CharSequence locationOrType = getNumberTypeOrLocation(info);
+    final String displayNumber = getDisplayNumber(info);
+    final String displayNumberStr =
+        mBidiFormatter.unicodeWrap(displayNumber, TextDirectionHeuristics.LTR);
+
+    String nameForDefaultImage;
+    if (!TextUtils.isEmpty(info.name)) {
+      nameForDefaultImage = info.name;
+      callerName.setText(info.name);
+      callerNumber.setText(locationOrType + " " + displayNumberStr);
+    } else {
+      nameForDefaultImage = displayNumber;
+      callerName.setText(displayNumberStr);
+      if (!TextUtils.isEmpty(locationOrType)) {
+        callerNumber.setText(locationOrType);
+        callerNumber.setVisibility(View.VISIBLE);
+      } else {
+        callerNumber.setVisibility(View.GONE);
+      }
+    }
+    loadContactPhoto(info, nameForDefaultImage, quickContactBadge);
+  }
+
+  private void loadContactPhoto(ContactInfo info, String displayName, QuickContactBadge badge) {
+    final String lookupKey =
+        info.lookupUri == null ? null : UriUtils.getLookupKeyFromUri(info.lookupUri);
+    final int contactType =
+        mContactInfoHelper.isBusiness(info.sourceType)
+            ? LetterTileDrawable.TYPE_BUSINESS
+            : LetterTileDrawable.TYPE_DEFAULT;
+    final DefaultImageRequest request =
+        new DefaultImageRequest(displayName, lookupKey, contactType, true /* isCircular */);
+    badge.assignContactUri(info.lookupUri);
+    badge.setContentDescription(
+        mContext.getResources().getString(R.string.description_contact_details, displayName));
+    mContactPhotoManager.loadDirectoryPhoto(
+        badge, info.photoUri, false /* darkTheme */, true /* isCircular */, request);
+  }
+
+  private String getDisplayNumber(ContactInfo info) {
+    if (!TextUtils.isEmpty(info.formattedNumber)) {
+      return info.formattedNumber;
+    } else if (!TextUtils.isEmpty(info.number)) {
+      return info.number;
+    } else {
+      return "";
+    }
+  }
+
+  private CharSequence getNumberTypeOrLocation(ContactInfo info) {
+    if (!TextUtils.isEmpty(info.name)) {
+      return ContactsContract.CommonDataKinds.Phone.getTypeLabel(
+          mContext.getResources(), info.type, info.label);
+    } else {
+      return PhoneNumberHelper.getGeoDescription(mContext, info.number);
+    }
+  }
+
+  protected Context getContext() {
+    return mContext;
+  }
+
+  protected FragmentManager getFragmentManager() {
+    return mFragmentManager;
+  }
+}
diff --git a/java/com/android/dialer/app/filterednumber/ViewNumbersToImportAdapter.java b/java/com/android/dialer/app/filterednumber/ViewNumbersToImportAdapter.java
new file mode 100644
index 0000000..106c4fb
--- /dev/null
+++ b/java/com/android/dialer/app/filterednumber/ViewNumbersToImportAdapter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 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.app.filterednumber;
+
+import android.app.FragmentManager;
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import com.android.dialer.app.R;
+import com.android.dialer.blocking.FilteredNumbersUtil;
+import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.location.GeoUtil;
+import com.android.dialer.phonenumbercache.ContactInfoHelper;
+
+/** TODO(calderwoodra): documentation */
+public class ViewNumbersToImportAdapter extends NumbersAdapter {
+
+  private ViewNumbersToImportAdapter(
+      Context context,
+      FragmentManager fragmentManager,
+      ContactInfoHelper contactInfoHelper,
+      ContactPhotoManager contactPhotoManager) {
+    super(context, fragmentManager, contactInfoHelper, contactPhotoManager);
+  }
+
+  public static ViewNumbersToImportAdapter newViewNumbersToImportAdapter(
+      Context context, FragmentManager fragmentManager) {
+    return new ViewNumbersToImportAdapter(
+        context,
+        fragmentManager,
+        new ContactInfoHelper(context, GeoUtil.getCurrentCountryIso(context)),
+        ContactPhotoManager.getInstance(context));
+  }
+
+  @Override
+  public void bindView(View view, Context context, Cursor cursor) {
+    super.bindView(view, context, cursor);
+
+    final String number = cursor.getString(FilteredNumbersUtil.PhoneQuery.NUMBER_COLUMN_INDEX);
+
+    view.findViewById(R.id.delete_button).setVisibility(View.GONE);
+    updateView(view, number, null /* countryIso */);
+  }
+}
diff --git a/java/com/android/dialer/app/filterednumber/ViewNumbersToImportFragment.java b/java/com/android/dialer/app/filterednumber/ViewNumbersToImportFragment.java
new file mode 100644
index 0000000..1de7682
--- /dev/null
+++ b/java/com/android/dialer/app/filterednumber/ViewNumbersToImportFragment.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2015 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.app.filterednumber;
+
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.dialer.app.R;
+import com.android.dialer.blocking.FilteredNumbersUtil;
+import com.android.dialer.blocking.FilteredNumbersUtil.ImportSendToVoicemailContactsListener;
+
+/** TODO(calderwoodra): documentation */
+public class ViewNumbersToImportFragment extends ListFragment
+    implements LoaderManager.LoaderCallbacks<Cursor>, View.OnClickListener {
+
+  private ViewNumbersToImportAdapter mAdapter;
+
+  @Override
+  public Context getContext() {
+    return getActivity();
+  }
+
+  @Override
+  public void onActivityCreated(Bundle savedInstanceState) {
+    super.onActivityCreated(savedInstanceState);
+
+    if (mAdapter == null) {
+      mAdapter =
+          ViewNumbersToImportAdapter.newViewNumbersToImportAdapter(
+              getContext(), getActivity().getFragmentManager());
+    }
+    setListAdapter(mAdapter);
+  }
+
+  @Override
+  public void onDestroy() {
+    setListAdapter(null);
+    super.onDestroy();
+  }
+
+  @Override
+  public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getLoaderManager().initLoader(0, null, this);
+  }
+
+  @Override
+  public void onResume() {
+    super.onResume();
+
+    ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
+    actionBar.setTitle(R.string.import_send_to_voicemail_numbers_label);
+    actionBar.setDisplayShowCustomEnabled(false);
+    actionBar.setDisplayHomeAsUpEnabled(true);
+    actionBar.setDisplayShowHomeEnabled(true);
+    actionBar.setDisplayShowTitleEnabled(true);
+
+    getActivity().findViewById(R.id.cancel_button).setOnClickListener(this);
+    getActivity().findViewById(R.id.import_button).setOnClickListener(this);
+  }
+
+  @Override
+  public View onCreateView(
+      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+    return inflater.inflate(R.layout.view_numbers_to_import_fragment, container, false);
+  }
+
+  @Override
+  public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+    final CursorLoader cursorLoader =
+        new CursorLoader(
+            getContext(),
+            Phone.CONTENT_URI,
+            FilteredNumbersUtil.PhoneQuery.PROJECTION,
+            FilteredNumbersUtil.PhoneQuery.SELECT_SEND_TO_VOICEMAIL_TRUE,
+            null,
+            null);
+    return cursorLoader;
+  }
+
+  @Override
+  public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+    mAdapter.swapCursor(data);
+  }
+
+  @Override
+  public void onLoaderReset(Loader<Cursor> loader) {
+    mAdapter.swapCursor(null);
+  }
+
+  @Override
+  public void onClick(final View view) {
+    if (view.getId() == R.id.import_button) {
+      FilteredNumbersUtil.importSendToVoicemailContacts(
+          getContext(),
+          new ImportSendToVoicemailContactsListener() {
+            @Override
+            public void onImportComplete() {
+              if (getActivity() != null) {
+                getActivity().onBackPressed();
+              }
+            }
+          });
+    } else if (view.getId() == R.id.cancel_button) {
+      getActivity().onBackPressed();
+    }
+  }
+}
diff --git a/java/com/android/dialer/app/list/BlockedListSearchFragment.java b/java/com/android/dialer/app/list/BlockedListSearchFragment.java
new file mode 100644
index 0000000..bef5af7
--- /dev/null
+++ b/java/com/android/dialer/app/list/BlockedListSearchFragment.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2015 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.app.list;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.telephony.PhoneNumberUtils;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.EditText;
+import android.widget.Toast;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.util.ContactDisplayUtils;
+import com.android.dialer.app.R;
+import com.android.dialer.app.widget.SearchEditTextLayout;
+import com.android.dialer.blocking.BlockNumberDialogFragment;
+import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
+import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.location.GeoUtil;
+import com.android.dialer.logging.InteractionEvent;
+import com.android.dialer.logging.Logger;
+
+/** TODO(calderwoodra): documentation */
+public class BlockedListSearchFragment extends RegularSearchFragment
+    implements BlockNumberDialogFragment.Callback {
+
+  private final TextWatcher mPhoneSearchQueryTextListener =
+      new TextWatcher() {
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+          setQueryString(s.toString());
+        }
+
+        @Override
+        public void afterTextChanged(Editable s) {}
+      };
+  private final SearchEditTextLayout.Callback mSearchLayoutCallback =
+      new SearchEditTextLayout.Callback() {
+        @Override
+        public void onBackButtonClicked() {
+          getActivity().onBackPressed();
+        }
+
+        @Override
+        public void onSearchViewClicked() {}
+      };
+  private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler;
+  private EditText mSearchView;
+
+  @Override
+  public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    setShowEmptyListForNullQuery(true);
+    /*
+     * Pass in the empty string here so ContactEntryListFragment#setQueryString interprets it as
+     * an empty search query, rather than as an uninitalized value. In the latter case, the
+     * adapter returned by #createListAdapter is used, which populates the view with contacts.
+     * Passing in the empty string forces ContactEntryListFragment to interpret it as an empty
+     * query, which results in showing an empty view
+     */
+    setQueryString(getQueryString() == null ? "" : getQueryString());
+    mFilteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler(getContext());
+  }
+
+  @Override
+  public void onResume() {
+    super.onResume();
+
+    ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
+    actionBar.setCustomView(R.layout.search_edittext);
+    actionBar.setDisplayShowCustomEnabled(true);
+    actionBar.setDisplayHomeAsUpEnabled(false);
+    actionBar.setDisplayShowHomeEnabled(false);
+
+    final SearchEditTextLayout searchEditTextLayout =
+        (SearchEditTextLayout) actionBar.getCustomView().findViewById(R.id.search_view_container);
+    searchEditTextLayout.expand(false, true);
+    searchEditTextLayout.setCallback(mSearchLayoutCallback);
+    searchEditTextLayout.setBackgroundDrawable(null);
+
+    mSearchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view);
+    mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
+    mSearchView.setHint(R.string.block_number_search_hint);
+
+    searchEditTextLayout
+        .findViewById(R.id.search_box_expanded)
+        .setBackgroundColor(getContext().getResources().getColor(android.R.color.white));
+
+    if (!TextUtils.isEmpty(getQueryString())) {
+      mSearchView.setText(getQueryString());
+    }
+
+    // TODO: Don't set custom text size; use default search text size.
+    mSearchView.setTextSize(
+        TypedValue.COMPLEX_UNIT_PX,
+        getResources().getDimension(R.dimen.blocked_number_search_text_size));
+  }
+
+  @Override
+  protected ContactEntryListAdapter createListAdapter() {
+    BlockedListSearchAdapter adapter = new BlockedListSearchAdapter(getActivity());
+    adapter.setDisplayPhotos(true);
+    // Don't show SIP addresses.
+    adapter.setUseCallableUri(false);
+    // Keep in sync with the queryString set in #onCreate
+    adapter.setQueryString(getQueryString() == null ? "" : getQueryString());
+    return adapter;
+  }
+
+  @Override
+  public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+    super.onItemClick(parent, view, position, id);
+    final int adapterPosition = position - getListView().getHeaderViewsCount();
+    final BlockedListSearchAdapter adapter = (BlockedListSearchAdapter) getAdapter();
+    final int shortcutType = adapter.getShortcutTypeFromPosition(adapterPosition);
+    final Integer blockId = (Integer) view.getTag(R.id.block_id);
+    final String number;
+    switch (shortcutType) {
+      case DialerPhoneNumberListAdapter.SHORTCUT_INVALID:
+        // Handles click on a search result, either contact or nearby places result.
+        number = adapter.getPhoneNumber(adapterPosition);
+        blockContactNumber(number, blockId);
+        break;
+      case DialerPhoneNumberListAdapter.SHORTCUT_BLOCK_NUMBER:
+        // Handles click on 'Block number' shortcut to add the user query as a number.
+        number = adapter.getQueryString();
+        blockNumber(number);
+        break;
+      default:
+        LogUtil.w(
+            "BlockedListSearchFragment.onItemClick",
+            "ignoring unsupported shortcut type: " + shortcutType);
+        break;
+    }
+  }
+
+  @Override
+  protected void onItemClick(int position, long id) {
+    // Prevent SearchFragment.onItemClicked from being called.
+  }
+
+  private void blockNumber(final String number) {
+    final String countryIso = GeoUtil.getCurrentCountryIso(getContext());
+    final OnCheckBlockedListener onCheckListener =
+        new OnCheckBlockedListener() {
+          @Override
+          public void onCheckComplete(Integer id) {
+            if (id == null) {
+              BlockNumberDialogFragment.show(
+                  id,
+                  number,
+                  countryIso,
+                  PhoneNumberUtils.formatNumber(number, countryIso),
+                  R.id.blocked_numbers_activity_container,
+                  getFragmentManager(),
+                  BlockedListSearchFragment.this);
+            } else if (id == FilteredNumberAsyncQueryHandler.INVALID_ID) {
+              Toast.makeText(
+                      getContext(),
+                      ContactDisplayUtils.getTtsSpannedPhoneNumber(
+                          getResources(), R.string.invalidNumber, number),
+                      Toast.LENGTH_SHORT)
+                  .show();
+            } else {
+              Toast.makeText(
+                      getContext(),
+                      ContactDisplayUtils.getTtsSpannedPhoneNumber(
+                          getResources(), R.string.alreadyBlocked, number),
+                      Toast.LENGTH_SHORT)
+                  .show();
+            }
+          }
+        };
+    mFilteredNumberAsyncQueryHandler.isBlockedNumber(onCheckListener, number, countryIso);
+  }
+
+  @Override
+  public void onFilterNumberSuccess() {
+    Logger.get(getContext()).logInteraction(InteractionEvent.Type.BLOCK_NUMBER_MANAGEMENT_SCREEN);
+    goBack();
+  }
+
+  @Override
+  public void onUnfilterNumberSuccess() {
+    LogUtil.e(
+        "BlockedListSearchFragment.onUnfilterNumberSuccess",
+        "unblocked a number from the BlockedListSearchFragment");
+    goBack();
+  }
+
+  private void goBack() {
+    Activity activity = getActivity();
+    if (activity == null) {
+      return;
+    }
+    activity.onBackPressed();
+  }
+
+  @Override
+  public void onChangeFilteredNumberUndo() {
+    getAdapter().notifyDataSetChanged();
+  }
+
+  private void blockContactNumber(final String number, final Integer blockId) {
+    if (blockId != null) {
+      Toast.makeText(
+              getContext(),
+              ContactDisplayUtils.getTtsSpannedPhoneNumber(
+                  getResources(), R.string.alreadyBlocked, number),
+              Toast.LENGTH_SHORT)
+          .show();
+      return;
+    }
+
+    BlockNumberDialogFragment.show(
+        blockId,
+        number,
+        GeoUtil.getCurrentCountryIso(getContext()),
+        number,
+        R.id.blocked_numbers_activity_container,
+        getFragmentManager(),
+        this);
+  }
+}
diff --git a/java/com/android/dialer/app/widget/ActionBarController.java b/java/com/android/dialer/app/widget/ActionBarController.java
index 3daa0e2..c1b4cc2 100644
--- a/java/com/android/dialer/app/widget/ActionBarController.java
+++ b/java/com/android/dialer/app/widget/ActionBarController.java
@@ -49,6 +49,18 @@
         }
       };
 
+  private final AnimationCallback mFadeInCallback =
+      new AnimationCallback() {
+        @Override
+        public void onAnimationEnd() {
+          slideActionBar(false /* slideUp */, false /* animate */);
+        }
+
+        @Override
+        public void onAnimationCancel() {
+          slideActionBar(false /* slideUp */, false /* animate */);
+        }
+      };
   private ValueAnimator mAnimator;
 
   public ActionBarController(ActivityUi activityUi, SearchEditTextLayout searchBox) {
@@ -100,13 +112,17 @@
         mSearchBox.isFadedOut(),
         mSearchBox.isExpanded());
     if (mActivityUi.isInSearchUi()) {
-      if (mSearchBox.isFadedOut()) {
-        mSearchBox.setVisible(true);
+      if (mActivityUi.hasSearchQuery()) {
+        if (mSearchBox.isFadedOut()) {
+          mSearchBox.setVisible(true);
+        }
+        if (!mSearchBox.isExpanded()) {
+          mSearchBox.expand(false /* animate */, false /* requestFocus */);
+        }
+        slideActionBar(false /* slideUp */, true /* animate */);
+      } else {
+        mSearchBox.fadeIn(mFadeInCallback);
       }
-      if (!mSearchBox.isExpanded()) {
-        mSearchBox.expand(false /* animate */, false /* requestFocus */);
-      }
-      slideActionBar(false /* slideUp */, true /* animate */);
     }
   }
 
diff --git a/java/com/android/dialer/app/widget/SearchEditTextLayout.java b/java/com/android/dialer/app/widget/SearchEditTextLayout.java
index 2051b65..95bd12a 100644
--- a/java/com/android/dialer/app/widget/SearchEditTextLayout.java
+++ b/java/com/android/dialer/app/widget/SearchEditTextLayout.java
@@ -23,6 +23,7 @@
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
+import android.view.KeyEvent;
 import android.view.View;
 import android.widget.EditText;
 import android.widget.FrameLayout;
@@ -37,6 +38,7 @@
   /* Subclass-visible for testing */
   protected boolean mIsExpanded = false;
   protected boolean mIsFadedOut = false;
+  private OnKeyListener mPreImeKeyListener;
   private int mTopMargin;
   private int mBottomMargin;
   private int mLeftMargin;
@@ -54,10 +56,20 @@
 
   private ValueAnimator mAnimator;
 
+  private Callback mCallback;
+
   public SearchEditTextLayout(Context context, AttributeSet attrs) {
     super(context, attrs);
   }
 
+  public void setPreImeKeyListener(OnKeyListener listener) {
+    mPreImeKeyListener = listener;
+  }
+
+  public void setCallback(Callback listener) {
+    mCallback = listener;
+  }
+
   @Override
   protected void onFinishInflate() {
     MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
@@ -70,7 +82,7 @@
 
     mCollapsed = findViewById(R.id.search_box_collapsed);
     mExpanded = findViewById(R.id.search_box_expanded);
-    mSearchView = mExpanded.findViewById(R.id.search_view);
+    mSearchView = (EditText) mExpanded.findViewById(R.id.search_view);
 
     mSearchIcon = findViewById(R.id.search_magnifying_glass);
     mCollapsedSearchBox = findViewById(R.id.search_box_start_search);
@@ -111,6 +123,16 @@
           }
         });
 
+    mSearchView.setOnClickListener(
+        new View.OnClickListener() {
+          @Override
+          public void onClick(View v) {
+            if (mCallback != null) {
+              mCallback.onSearchViewClicked();
+            }
+          }
+        });
+
     mSearchView.addTextChangedListener(
         new TextWatcher() {
           @Override
@@ -125,10 +147,43 @@
           public void afterTextChanged(Editable s) {}
         });
 
-    mClearButtonView.setOnClickListener(v -> mSearchView.setText(null));
+    findViewById(R.id.search_close_button)
+        .setOnClickListener(
+            new OnClickListener() {
+              @Override
+              public void onClick(View v) {
+                mSearchView.setText(null);
+              }
+            });
+
+    findViewById(R.id.search_back_button)
+        .setOnClickListener(
+            new OnClickListener() {
+              @Override
+              public void onClick(View v) {
+                if (mCallback != null) {
+                  mCallback.onBackButtonClicked();
+                }
+              }
+            });
+
     super.onFinishInflate();
   }
 
+  @Override
+  public boolean dispatchKeyEventPreIme(KeyEvent event) {
+    if (mPreImeKeyListener != null) {
+      if (mPreImeKeyListener.onKey(this, event.getKeyCode(), event)) {
+        return true;
+      }
+    }
+    return super.dispatchKeyEventPreIme(event);
+  }
+
+  public void fadeOut() {
+    fadeOut(null);
+  }
+
   public void fadeOut(AnimUtils.AnimationCallback callback) {
     AnimUtils.fadeOut(this, ANIMATION_DURATION, callback);
     mIsFadedOut = true;
@@ -269,4 +324,12 @@
     params.rightMargin = (int) (mRightMargin * fraction);
     requestLayout();
   }
+
+  /** Listener for the back button next to the search view being pressed */
+  public interface Callback {
+
+    void onBackButtonClicked();
+
+    void onSearchViewClicked();
+  }
 }
diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java
index 837c3af..86a8379 100644
--- a/java/com/android/dialer/dialpadview/DialpadFragment.java
+++ b/java/com/android/dialer/dialpadview/DialpadFragment.java
@@ -1001,12 +1001,12 @@
     DialerUtils.startActivityWithErrorToast(
         getActivity(),
         new CallIntentBuilder(CallUtil.getVoicemailUri(), CallInitiationType.Type.DIALPAD).build());
-    hideAndClearDialpad();
+    hideAndClearDialpad(false);
   }
 
-  private void hideAndClearDialpad() {
+  private void hideAndClearDialpad(boolean animate) {
     LogUtil.enterBlock("DialpadFragment.hideAndClearDialpad");
-    FragmentUtils.getParentUnsafe(this, DialpadListener.class).onCallPlacedFromDialpad();
+    FragmentUtils.getParentUnsafe(this, DialpadListener.class).hideDialpadFragment(animate, true);
   }
 
   /**
@@ -1053,7 +1053,7 @@
         final Intent intent =
             new CallIntentBuilder(number, CallInitiationType.Type.DIALPAD).build();
         DialerUtils.startActivityWithErrorToast(getActivity(), intent);
-        hideAndClearDialpad();
+        hideAndClearDialpad(false);
       }
     }
   }
@@ -1297,7 +1297,7 @@
       return true;
     } else if (resId == R.id.menu_call_with_note) {
       CallSubjectDialog.start(getActivity(), mDigits.getText().toString());
-      hideAndClearDialpad();
+      hideAndClearDialpad(false);
       return true;
     } else {
       return false;
@@ -1710,7 +1710,7 @@
 
     void onDialpadShown();
 
-    void onCallPlacedFromDialpad();
+    void hideDialpadFragment(boolean animate, boolean value);
   }
 
   /** Callback for async lookup of the last number dialed. */