Merge "Create superclass for BlockedNumberAdapter." into ub-contactsdialer-a-dev
diff --git a/res/layout/blocked_number_fragment.xml b/res/layout/blocked_number_fragment.xml
index e86ccb5..bb4b7f2 100644
--- a/res/layout/blocked_number_fragment.xml
+++ b/res/layout/blocked_number_fragment.xml
@@ -27,23 +27,22 @@
         android:layout_height="wrap_content"
         card_view:cardCornerRadius="0dp">
 
-        <ListView android:id="@id/android:list"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:background="@color/background_dialer_white"
-            android:layout_weight="1"
-            android:drawSelectorOnTop="false"
-            android:headerDividersEnabled="false" />
-
         <LinearLayout
-            android:id="@android:id/empty"
-            android:layout_width="fill_parent"
-            android:layout_height="fill_parent"
-            android:orientation="vertical">
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@color/background_dialer_white">
 
             <include layout="@layout/blocked_number_header" />
 
-            <TextView android:id="@id/android:empty"
+            <ListView android:id="@id/android:list"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:drawSelectorOnTop="false"
+                android:headerDividersEnabled="false" />
+
+            <TextView android:id="@android:id/empty"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:paddingStart="@dimen/blocked_number_horizontal_margin"
diff --git a/res/layout/blocked_number_header.xml b/res/layout/blocked_number_header.xml
index 95880a7..39f5322 100644
--- a/res/layout/blocked_number_header.xml
+++ b/res/layout/blocked_number_header.xml
@@ -13,10 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content">
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <TextView android:id="@+id/textView"
         android:layout_width="wrap_content"
@@ -26,7 +23,7 @@
         android:padding="@dimen/blocked_number_container_padding"
         style="@android:style/TextAppearance.Material.Subhead" />
 
-    <RelativeLayout android:id="@+id/importsettings"
+    <RelativeLayout android:id="@+id/import_settings"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:visibility="gone">
@@ -77,4 +74,4 @@
         android:layout_marginTop="8dp"
         android:text="@string/blockNumber" />
 
-</LinearLayout>
+</merge>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cd7ce8f..0b7d82c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -833,7 +833,7 @@
         You previously marked some callers to be automatically sent to voicemail via other apps.
     </string>
 
-    <!-- Labe for button to view numbers of contacts previous marked to be sent to voicemail.
+    <!-- Label for button to view numbers of contacts previous marked to be sent to voicemail.
          [CHAR_LIMIT=20] -->
     <string name="blocked_call_settings_view_numbers_button">View Numbers</string>
 
@@ -841,6 +841,9 @@
          list. [CHAR_LIMIT=20] -->
     <string name="blocked_call_settings_import_button">Import</string>
 
+    <!-- Error toast message for when send to voicemail import fails. [CHAR LIMIT=40] -->
+    <string name="send_to_voicemail_import_failed">Import failed</string>
+
     <!-- String describing the delete icon on a blocked number list item.
         When tapped, it will show a dialog confirming the unblocking of the number.
         [CHAR LIMIT=NONE]-->
@@ -853,7 +856,7 @@
     <!-- Button to bring up UI to add a number to the blocked call list. [CHAR LIMIT=40] -->
     <string name="blockNumber">Add number</string>
 
-    <!-- Heading for the block list in the "Spam and blocked calls" settings. [CHAR LIMIT=64] -->
+    <!-- Heading for the block list in the "Spam and blocked cal)ls" settings. [CHAR LIMIT=64] -->
     <string name="blockList">Block list</string>
 
     <!-- Label for progress dialog when validating a number to be added to the block list.
diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java
index 654a96f..ef21980 100644
--- a/src/com/android/dialer/CallDetailActivity.java
+++ b/src/com/android/dialer/CallDetailActivity.java
@@ -49,14 +49,15 @@
 import com.android.contacts.common.util.UriUtils;
 import com.android.dialer.calllog.CallDetailHistoryAdapter;
 import com.android.dialer.calllog.CallLogAsyncTaskUtil.CallLogAsyncTaskListener;
-import com.android.dialer.onboard.OnboardingActivity;
 import com.android.dialer.calllog.CallLogAsyncTaskUtil;
 import com.android.dialer.calllog.CallTypeHelper;
 import com.android.dialer.calllog.ContactInfoHelper;
 import com.android.dialer.calllog.PhoneAccountUtils;
-import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
 import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
+import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
 import com.android.dialer.filterednumber.FilterNumberDialogFragment;
+import com.android.dialer.filterednumber.FilteredNumbersUtil;
+import com.android.dialer.onboard.OnboardingActivity;
 import com.android.dialer.util.DialerUtils;
 import com.android.dialer.util.IntentUtil.CallIntentBuilder;
 import com.android.dialer.util.PhoneNumberUtil;
@@ -106,17 +107,9 @@
 
             // All calls are from the same number and same contact, so pick the first detail.
             mDetails = details[0];
-            mNumber = TextUtils.isEmpty(mDetails.number) ?
-                    null : mDetails.number.toString();
+            mNumber = TextUtils.isEmpty(mDetails.number) ? null : mDetails.number.toString();
             mDisplayNumber = mDetails.displayNumber;
             final int numberPresentation = mDetails.numberPresentation;
-            final Uri contactUri = mDetails.contactUri;
-            final Uri photoUri = mDetails.photoUri;
-            final PhoneAccountHandle accountHandle = mDetails.accountHandle;
-
-            // Cache the details about the phone number.
-            mIsVoicemailNumber =
-                    PhoneNumberUtil.isVoicemailNumber(mContext, accountHandle, mNumber);
 
             final CharSequence callLocationOrType = getNumberTypeOrLocation(mDetails);
 
@@ -137,7 +130,8 @@
                 }
             }
 
-            String accountLabel = PhoneAccountUtils.getAccountLabel(mContext, accountHandle);
+            String accountLabel =
+                    PhoneAccountUtils.getAccountLabel(mContext, mDetails.accountHandle);
             if (!TextUtils.isEmpty(accountLabel)) {
                 mAccountLabel.setText(accountLabel);
                 mAccountLabel.setVisibility(View.VISIBLE);
@@ -150,8 +144,10 @@
             mCallButton.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE);
 
             final boolean isSipNumber = PhoneNumberUtil.isSipNumber(mNumber);
+            final boolean isVoicemailNumber =
+                    PhoneNumberUtil.isVoicemailNumber(mContext, mDetails.accountHandle, mNumber);
             final boolean showEditNumberBeforeCallAction =
-                    canPlaceCallsTo && !isSipNumber && !mIsVoicemailNumber;
+                    canPlaceCallsTo && !isSipNumber && !isVoicemailNumber;
             mEditBeforeCallActionItem.setVisibility(
                     showEditNumberBeforeCallAction ? View.VISIBLE : View.GONE);
 
@@ -160,31 +156,13 @@
             mReportActionItem.setVisibility(
                     showReportAction ? View.VISIBLE : View.GONE);
 
-            updateBlockActionItem();
             invalidateOptionsMenu();
 
             mHistoryList.setAdapter(
                     new CallDetailHistoryAdapter(mContext, mInflater, mCallTypeHelper, details));
 
-            String lookupKey = contactUri == null ? null
-                    : UriUtils.getLookupKeyFromUri(contactUri);
+            updatePhotoAndBlockActionItem();
 
-            final boolean isBusiness = mContactInfoHelper.isBusiness(mDetails.sourceType);
-
-            final int contactType =
-                    mIsVoicemailNumber ? ContactPhotoManager.TYPE_VOICEMAIL :
-                    isBusiness ? ContactPhotoManager.TYPE_BUSINESS :
-                    ContactPhotoManager.TYPE_DEFAULT;
-
-            String nameForDefaultImage;
-            if (TextUtils.isEmpty(mDetails.name)) {
-                nameForDefaultImage = mDetails.displayNumber;
-            } else {
-                nameForDefaultImage = mDetails.name.toString();
-            }
-
-            loadContactPhotos(
-                    contactUri, photoUri, nameForDefaultImage, lookupKey, contactType);
             findViewById(R.id.call_detail).setVisibility(View.VISIBLE);
         }
 
@@ -217,7 +195,6 @@
     private PhoneCallDetails mDetails;
     protected String mNumber;
     private Uri mVoicemailUri;
-    private boolean mIsVoicemailNumber;
     private String mDefaultCountryIso;
     private String mDisplayNumber;
 
@@ -275,6 +252,9 @@
         mCallButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
+                if (TextUtils.isEmpty(mNumber)) {
+                    return;
+                }
                 mContext.startActivity(
                         new CallIntentBuilder(mNumber)
                                 .setCallInitiationType(LogState.INITIATION_CALL_DETAILS)
@@ -339,21 +319,6 @@
         return uris;
     }
 
-    /** Load the contact photos and places them in the corresponding views. */
-    private void loadContactPhotos(Uri contactUri, Uri photoUri, String displayName,
-            String lookupKey, int contactType) {
-
-        final DefaultImageRequest request = new DefaultImageRequest(displayName, lookupKey,
-                contactType, true /* isCircular */);
-
-        mQuickContactBadge.assignContactUri(contactUri);
-        mQuickContactBadge.setContentDescription(
-                mResources.getString(R.string.description_contact_details, displayName));
-
-        mContactPhotoManager.loadDirectoryPhoto(mQuickContactBadge, photoUri,
-                false /* darkTheme */, true /* isCircular */, request);
-    }
-
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         final MenuItem deleteMenuItem = menu.add(
@@ -399,7 +364,7 @@
                         mBlockedNumberId,
                         null /* normalizedNumber */,
                         mNumber,
-                        null /* countryIso */,
+                        mDetails.countryIso,
                         mDisplayNumber,
                         R.id.call_detail,
                         getFragmentManager(),
@@ -420,32 +385,81 @@
 
     @Override
     public void onChangeFilteredNumberSuccess() {
-        updateBlockActionItem();
+        updatePhotoAndBlockActionItem();
     }
 
     @Override
     public void onChangeFilteredNumberUndo() {
-        updateBlockActionItem();
+        updatePhotoAndBlockActionItem();
     }
 
-    private void updateBlockActionItem() {
+    private void updatePhotoAndBlockActionItem() {
+        if (mDetails == null || !FilteredNumbersUtil.canBlockNumber(this, mNumber)) {
+            return;
+        }
+
+        boolean failed = mFilteredNumberAsyncQueryHandler.startBlockedQuery(
+                new OnCheckBlockedListener() {
+                    @Override
+                    public void onCheckComplete(Integer id) {
+                        mBlockedNumberId = id;
+
+                        updateContactPhoto();
+                        updateBlockActionItem();
+                    }
+                }, null, mNumber, mDetails.countryIso);
+
+        if (failed) {
+            updateContactPhoto();
+            updateBlockActionItem();
+        }
+    }
+
+    // Loads and displays the contact photo.
+    private void updateContactPhoto() {
         if (mDetails == null) {
             return;
         }
 
-        mFilteredNumberAsyncQueryHandler.startBlockedQuery(new OnCheckBlockedListener() {
-            @Override
-            public void onCheckComplete(Integer id) {
-                mBlockedNumberId = id;
-                if (mBlockedNumberId == null) {
-                    mBlockNumberActionItem.setText(R.string.action_block_number);
-                } else {
-                    mBlockNumberActionItem.setText(R.string.action_unblock_number);
-                }
+        if (mBlockedNumberId != null) {
+            mQuickContactBadge.setImageDrawable(mContext.getDrawable(R.drawable.blocked_contact));
+            return;
+        }
 
-                mBlockNumberActionItem.setVisibility(View.VISIBLE);
-            }
-        }, null, mNumber, mDetails.countryIso);
+        final boolean isVoicemailNumber =
+                PhoneNumberUtil.isVoicemailNumber(mContext, mDetails.accountHandle, mNumber);
+        final boolean isBusiness = mContactInfoHelper.isBusiness(mDetails.sourceType);
+        int contactType = ContactPhotoManager.TYPE_DEFAULT;
+        if (isVoicemailNumber) {
+            contactType = ContactPhotoManager.TYPE_VOICEMAIL;
+        } else if (isBusiness) {
+            contactType = ContactPhotoManager.TYPE_BUSINESS;
+        }
+
+        final String displayName = TextUtils.isEmpty(mDetails.name)
+                ? mDetails.displayNumber : mDetails.name.toString();
+        final String lookupKey = mDetails.contactUri == null
+                ? null : UriUtils.getLookupKeyFromUri(mDetails.contactUri);
+
+        final DefaultImageRequest request =
+                new DefaultImageRequest(displayName, lookupKey, contactType, true /* isCircular */);
+
+        mQuickContactBadge.assignContactUri(mDetails.contactUri);
+        mQuickContactBadge.setContentDescription(
+                mResources.getString(R.string.description_contact_details, displayName));
+
+        mContactPhotoManager.loadDirectoryPhoto(mQuickContactBadge, mDetails.photoUri,
+                false /* darkTheme */, true /* isCircular */, request);
+    }
+
+    private void updateBlockActionItem() {
+        if (mBlockedNumberId == null) {
+            mBlockNumberActionItem.setText(R.string.action_block_number);
+        } else {
+            mBlockNumberActionItem.setText(R.string.action_unblock_number);
+        }
+
+        mBlockNumberActionItem.setVisibility(View.VISIBLE);
     }
 
     private void closeSystemDialogs() {
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 7f3a779..0ee7e93 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -314,6 +314,10 @@
 
     public void onPause() {
         pauseCache();
+
+        // Clear blocked id cache so that changes in blocked status will be reflected in the UI.
+        mBlockedIdCache.clear();
+
         if (mHiddenItemUri != null) {
             CallLogAsyncTaskUtil.deleteVoicemail(mContext, mHiddenItemUri, null);
         }
diff --git a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
index 8f2f4ba..23c77d3 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
@@ -27,6 +27,7 @@
 import android.support.v7.widget.CardView;
 import android.support.v7.widget.RecyclerView;
 import android.telecom.PhoneAccountHandle;
+import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.view.ContextMenu;
 import android.view.MenuItem;
@@ -48,6 +49,7 @@
 import com.android.dialer.R;
 import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
 import com.android.dialer.filterednumber.FilterNumberDialogFragment;
+import com.android.dialer.filterednumber.FilteredNumbersUtil;
 import com.android.dialer.util.DialerUtils;
 import com.android.dialer.util.PhoneNumberUtil;
 import com.android.dialer.voicemail.VoicemailPlaybackPresenter;
@@ -301,22 +303,24 @@
                     .setOnMenuItemClickListener(this);
         }
 
-        mFilteredNumberAsyncQueryHandler.startBlockedQuery(
-                new FilteredNumberAsyncQueryHandler.OnCheckBlockedListener() {
-                    @Override
-                    public void onCheckComplete(Integer id) {
-                        blockId = id;
-                        int blockTitleId = blockId == null ? R.string.action_block_number
-                                : R.string.action_unblock_number;
-                        final MenuItem blockItem = menu.add(
-                                ContextMenu.NONE,
-                                R.id.context_menu_block_number,
-                                ContextMenu.NONE,
-                                blockTitleId);
-                        blockItem.setOnMenuItemClickListener(
-                                CallLogListItemViewHolder.this);
-                    }
-            }, info.normalizedNumber, number, countryIso);
+        if (FilteredNumbersUtil.canBlockNumber(mContext, number)) {
+            mFilteredNumberAsyncQueryHandler.startBlockedQuery(
+                    new FilteredNumberAsyncQueryHandler.OnCheckBlockedListener() {
+                        @Override
+                        public void onCheckComplete(Integer id) {
+                            blockId = id;
+                            int blockTitleId = blockId == null ? R.string.action_block_number
+                                    : R.string.action_unblock_number;
+                            final MenuItem blockItem = menu.add(
+                                    ContextMenu.NONE,
+                                    R.id.context_menu_block_number,
+                                    ContextMenu.NONE,
+                                    blockTitleId);
+                            blockItem.setOnMenuItemClickListener(
+                                    CallLogListItemViewHolder.this);
+                        }
+                    }, info.normalizedNumber, number, countryIso);
+        }
     }
 
     @Override
diff --git a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java
index 4eb74bd..594ceeb 100644
--- a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java
+++ b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java
@@ -39,9 +39,13 @@
 import com.android.dialer.DialtactsActivity;
 import com.android.dialer.R;
 import com.android.dialer.calllog.PhoneAccountUtils;
+import com.android.dialer.filterednumber.FilteredNumbersUtil;
 import com.android.dialer.list.ListsFragment;
 import com.google.common.collect.Maps;
 
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -96,14 +100,14 @@
     public void updateNotification(Uri newCallUri) {
         // Lookup the list of new voicemails to include in the notification.
         // TODO: Move this into a service, to avoid holding the receiver up.
-        final NewCall[] newCalls = mNewCallsQuery.query();
+        final List<NewCall> newCalls = mNewCallsQuery.query();
 
         if (newCalls == null) {
             // Query failed, just return.
             return;
         }
 
-        if (newCalls.length == 0) {
+        if (newCalls.size() == 0) {
             // No voicemails to notify about: clear the notification.
             clearNotification();
             return;
@@ -122,7 +126,16 @@
         NewCall callToNotify = null;
 
         // Iterate over the new voicemails to determine all the information above.
-        for (NewCall newCall : newCalls) {
+        Iterator<NewCall> itr = newCalls.iterator();
+        while (itr.hasNext()) {
+            NewCall newCall = itr.next();
+
+            // Skip notifying for numbers which are blocked.
+            if (FilteredNumbersUtil.isBlocked(mContext, newCall.number, newCall.countryIso)) {
+                itr.remove();
+                continue;
+            }
+
             // Check if we already know the name associated with this number.
             String name = names.get(newCall.number);
             if (name == null) {
@@ -155,10 +168,14 @@
             }
         }
 
+        if (newCalls.isEmpty()) {
+            return;
+        }
+
         // If there is only one voicemail, set its transcription as the "long text".
         String transcription = null;
-        if (newCalls.length == 1) {
-            transcription = newCalls[0].transcription;
+        if (newCalls.size() == 1) {
+            transcription = newCalls.get(0).transcription;
         }
 
         if (newCallUri != null && callToNotify == null) {
@@ -167,7 +184,7 @@
 
         // Determine the title of the notification and the icon for it.
         final String title = resources.getQuantityString(
-                R.plurals.notification_voicemail_title, newCalls.length, newCalls.length);
+                R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size());
         // TODO: Use the photo of contact if all calls are from the same person.
         final int icon = android.R.drawable.stat_notify_voicemail;
 
@@ -218,6 +235,7 @@
         public final String accountComponentName;
         public final String accountId;
         public final String transcription;
+        public final String countryIso;
 
         public NewCall(
                 Uri callsUri,
@@ -226,7 +244,8 @@
                 int numberPresentation,
                 String accountComponentName,
                 String accountId,
-                String transcription) {
+                String transcription,
+                String countryIso) {
             this.callsUri = callsUri;
             this.voicemailUri = voicemailUri;
             this.number = number;
@@ -234,6 +253,7 @@
             this.accountComponentName = accountComponentName;
             this.accountId = accountId;
             this.transcription = transcription;
+            this.countryIso = countryIso;
         }
     }
 
@@ -242,7 +262,7 @@
         /**
          * Returns the new calls for which a notification should be generated.
          */
-        public NewCall[] query();
+        public List<NewCall> query();
     }
 
     /** Create a new instance of {@link NewCallsQuery}. */
@@ -263,7 +283,8 @@
             Calls.NUMBER_PRESENTATION,
             Calls.PHONE_ACCOUNT_COMPONENT_NAME,
             Calls.PHONE_ACCOUNT_ID,
-            Calls.TRANSCRIPTION
+            Calls.TRANSCRIPTION,
+            Calls.COUNTRY_ISO
         };
         private static final int ID_COLUMN_INDEX = 0;
         private static final int NUMBER_COLUMN_INDEX = 1;
@@ -272,6 +293,7 @@
         private static final int PHONE_ACCOUNT_COMPONENT_NAME_COLUMN_INDEX = 4;
         private static final int PHONE_ACCOUNT_ID_COLUMN_INDEX = 5;
         private static final int TRANSCRIPTION_COLUMN_INDEX = 6;
+        private static final int COUNTRY_ISO_COLUMN_INDEX = 7;
 
         private final ContentResolver mContentResolver;
         private final Context mContext;
@@ -282,7 +304,7 @@
         }
 
         @Override
-        public NewCall[] query() {
+        public List<NewCall> query() {
             if (!PermissionsUtil.hasPermission(mContext, READ_CALL_LOG)) {
                 Log.w(TAG, "No READ_CALL_LOG permission, returning null for calls lookup.");
                 return null;
@@ -296,9 +318,9 @@
                 if (cursor == null) {
                     return null;
                 }
-                NewCall[] newCalls = new NewCall[cursor.getCount()];
+                List<NewCall> newCalls = new LinkedList<NewCall>();
                 while (cursor.moveToNext()) {
-                    newCalls[cursor.getPosition()] = createNewCallsFromCursor(cursor);
+                    newCalls.add(createNewCallsFromCursor(cursor));
                 }
                 return newCalls;
             } catch (RuntimeException e) {
@@ -322,7 +344,8 @@
                     cursor.getInt(NUMBER_PRESENTATION_COLUMN_INDEX),
                     cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME_COLUMN_INDEX),
                     cursor.getString(PHONE_ACCOUNT_ID_COLUMN_INDEX),
-                    cursor.getString(TRANSCRIPTION_COLUMN_INDEX));
+                    cursor.getString(TRANSCRIPTION_COLUMN_INDEX),
+                    cursor.getString(COUNTRY_ISO_COLUMN_INDEX));
         }
     }
 
diff --git a/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java b/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java
index c5d2f6f..25613a6 100644
--- a/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java
+++ b/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java
@@ -202,7 +202,9 @@
                 new Listener() {
                     @Override
                     public void onInsertComplete(int token, Object cookie, Uri uri) {
-                        listener.onBlockComplete(uri);
+                        if (listener != null ) {
+                            listener.onBlockComplete(uri);
+                        }
                     }
                 }, getContentUri(null), values);
     }
@@ -241,7 +243,9 @@
                 startDelete(NO_TOKEN, new Listener() {
                     @Override
                     public void onDeleteComplete(int token, Object cookie, int result) {
-                        listener.onUnblockComplete(result, values);
+                        if (listener != null) {
+                            listener.onUnblockComplete(result, values);
+                        }
                     }
                 }, uri, null, null);
             }
diff --git a/src/com/android/dialer/filterednumber/BlockedNumberFragment.java b/src/com/android/dialer/filterednumber/BlockedNumberFragment.java
index 10d6ff6..43f734d 100644
--- a/src/com/android/dialer/filterednumber/BlockedNumberFragment.java
+++ b/src/com/android/dialer/filterednumber/BlockedNumberFragment.java
@@ -21,36 +21,28 @@
 import android.content.Intent;
 import android.content.Loader;
 import android.database.Cursor;
-import android.os.AsyncTask;
 import android.os.Bundle;
-import android.provider.ContactsContract.Contacts;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.dialer.R;
 import com.android.dialer.database.FilteredNumberContract;
+import com.android.dialer.filterednumber.FilteredNumbersUtil.CheckForSendToVoicemailContactListener;
+import com.android.dialer.filterednumber.FilteredNumbersUtil.ImportSendToVoicemailContactsListener;
 
 public class BlockedNumberFragment extends ListFragment implements
         LoaderManager.LoaderCallbacks<Cursor>, View.OnClickListener {
 
-    private static class SendToVoicemailContactQuery {
-        static final String[] PROJECTION = {
-            Contacts._ID
-        };
-
-        static final String SELECT_SEND_TO_VOICEMAIL_TRUE = Contacts.SEND_TO_VOICEMAIL + "=1";
-    }
-
     private BlockedNumberAdapter mAdapter;
+
     private View mImportSettings;
+    private View mImportButton;
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
-        LayoutInflater inflater = LayoutInflater.from(getContext());
-        getListView().addHeaderView(inflater.inflate(R.layout.blocked_number_header, null));
         if (mAdapter == null) {
             mAdapter = BlockedNumberAdapter.newBlockedNumberAdapter(
                     getContext(), getActivity().getFragmentManager());
@@ -58,9 +50,21 @@
         setListAdapter(mAdapter);
 
         getActivity().findViewById(R.id.add_number_button).setOnClickListener(this);
-        getListView().getEmptyView().findViewById(R.id.add_number_button).setOnClickListener(this);
 
-        mImportSettings = getActivity().findViewById(R.id.importsettings);
+        mImportSettings = getActivity().findViewById(R.id.import_settings);
+        mImportButton = getActivity().findViewById(R.id.import_button);
+        mImportButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                FilteredNumbersUtil.importSendToVoicemailContacts(
+                        getActivity(), new ImportSendToVoicemailContactsListener() {
+                            @Override
+                            public void onImportComplete() {
+                                mImportSettings.setVisibility(View.GONE);
+                            }
+                        });
+            }
+        });
     }
 
     @Override
@@ -78,7 +82,15 @@
     @Override
     public void onResume() {
         super.onResume();
-        checkForSendToVoicemailContact();
+
+        FilteredNumbersUtil.checkForSendToVoicemailContact(
+                getActivity(), new CheckForSendToVoicemailContactListener() {
+                    @Override
+                    public void onComplete(boolean hasSendToVoicemailContact) {
+                        final int visibility = hasSendToVoicemailContact ? View.VISIBLE : View.GONE;
+                        mImportSettings.setVisibility(visibility);
+                    }
+                });
     }
 
     @Override
@@ -122,44 +134,4 @@
             manageBlockedNumbersActivity.enterSearchUi();
         }
     }
-
-    /**
-     * Checks if there exists a contact with {@code Contacts.SEND_TO_VOICEMAIL} set to true,
-     * and updates the visibility of the import settings buttons accordingly.
-     */
-    private void checkForSendToVoicemailContact() {
-        final AsyncTask task = new AsyncTask<Object, Void, Boolean>() {
-            @Override
-            public Boolean doInBackground(Object[]  params) {
-                if (getActivity() == null) {
-                    return false;
-                }
-
-                final Cursor cursor = getActivity().getContentResolver().query(
-                        Contacts.CONTENT_URI,
-                        SendToVoicemailContactQuery.PROJECTION,
-                        SendToVoicemailContactQuery.SELECT_SEND_TO_VOICEMAIL_TRUE,
-                        null,
-                        null);
-
-                boolean hasSendToVoicemailContacts = false;
-                if (cursor != null) {
-                    try {
-                        hasSendToVoicemailContacts = cursor.getCount() > 0;
-                    } finally {
-                        cursor.close();
-                    }
-                }
-
-                return hasSendToVoicemailContacts;
-            }
-
-            @Override
-            public void onPostExecute(Boolean hasSendToVoicemailContact) {
-                final int visibility = hasSendToVoicemailContact ? View.VISIBLE : View.GONE;
-                mImportSettings.setVisibility(visibility);
-            }
-        };
-        task.execute();
-    }
 }
diff --git a/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java b/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java
index a1260ee..affb37b 100644
--- a/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java
+++ b/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java
@@ -26,7 +26,9 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.design.widget.Snackbar;
+import android.text.TextUtils;
 import android.view.View;
+import android.widget.Toast;
 
 import com.android.dialer.R;
 import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
@@ -57,6 +59,7 @@
     private static final String ARG_PARENT_VIEW_ID = "parentViewId";
 
     private String mDisplayNumber;
+    private String mNormalizedNumber;
 
     private FilteredNumberAsyncQueryHandler mHandler;
     private View mParentView;
@@ -105,7 +108,14 @@
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         super.onCreateDialog(savedInstanceState);
         final boolean isBlocked = getArguments().containsKey(ARG_BLOCK_ID);
+
         mDisplayNumber = getArguments().getString(ARG_DISPLAY_NUMBER);
+        if (TextUtils.isEmpty(mNormalizedNumber)) {
+            String number = getArguments().getString(ARG_NUMBER);
+            String countryIso = getArguments().getString(ARG_COUNTRY_ISO);
+            mNormalizedNumber =
+                    FilteredNumberAsyncQueryHandler.getNormalizedNumber(number, countryIso);
+        }
 
         mHandler = new FilteredNumberAsyncQueryHandler(getContext().getContentResolver());
         mParentView = getActivity().findViewById(getArguments().getInt(ARG_PARENT_VIEW_ID));
@@ -136,6 +146,18 @@
     }
 
     @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        String number = getArguments().getString(ARG_NUMBER);
+        if (TextUtils.isEmpty(mNormalizedNumber) ||
+                !FilteredNumbersUtil.canBlockNumber(getActivity(), number)) {
+            dismiss();
+            Toast.makeText(getContext(), getString(R.string.invalidNumber, number),
+                    Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    @Override
     public void onPause() {
         // Dismiss on rotation.
         dismiss();
@@ -159,13 +181,14 @@
     private void blockNumber() {
         final String message = getBlockedMessage();
         final String undoMessage = getUnblockedMessage();
+        final Callback callback = mCallback;
 
         final OnUnblockNumberListener onUndoListener = new OnUnblockNumberListener() {
             @Override
             public void onUnblockComplete(int rows, ContentValues values) {
                 Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
-                if (mCallback != null) {
-                    mCallback.onChangeFilteredNumberUndo();
+                if (callback != null) {
+                    callback.onChangeFilteredNumberUndo();
                 }
             }
         };
@@ -185,8 +208,8 @@
                         .setAction(R.string.block_number_undo, undoListener)
                         .show();
 
-                if (mCallback != null) {
-                    mCallback.onChangeFilteredNumberSuccess();
+                if (callback != null) {
+                    callback.onChangeFilteredNumberSuccess();
                 }
             }
         };
@@ -201,13 +224,14 @@
     private void unblockNumber() {
         final String message = getUnblockedMessage();
         final String undoMessage = getBlockedMessage();
+        final Callback callback = mCallback;
 
         final OnBlockNumberListener onUndoListener = new OnBlockNumberListener() {
             @Override
             public void onBlockComplete(final Uri uri) {
                 Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
-                if (mCallback != null) {
-                    mCallback.onChangeFilteredNumberUndo();
+                if (callback != null) {
+                    callback.onChangeFilteredNumberUndo();
                 }
             }
         };
@@ -227,8 +251,8 @@
                         .setAction(R.string.block_number_undo, undoListener)
                         .show();
 
-                if (mCallback != null) {
-                    mCallback.onChangeFilteredNumberSuccess();
+                if (callback != null) {
+                    callback.onChangeFilteredNumberSuccess();
                 }
             }
         }, getArguments().getInt(ARG_BLOCK_ID));
diff --git a/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java b/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java
new file mode 100644
index 0000000..68f262c
--- /dev/null
+++ b/src/com/android/dialer/filterednumber/FilteredNumbersUtil.java
@@ -0,0 +1,232 @@
+/*
+ * 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.filterednumber;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import com.android.dialer.R;
+import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
+import com.android.dialer.database.FilteredNumberContract.FilteredNumber;
+import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns;
+
+/**
+ * Utility to help with tasks related to filtered numbers.
+ */
+public class FilteredNumbersUtil {
+
+    public interface CheckForSendToVoicemailContactListener {
+        public void onComplete(boolean hasSendToVoicemailContact);
+    }
+
+    public interface ImportSendToVoicemailContactsListener {
+        public void onImportComplete();
+    }
+
+    private static class ContactsQuery {
+        static final String[] PROJECTION = {
+            Contacts._ID
+        };
+
+        static final String SELECT_SEND_TO_VOICEMAIL_TRUE = Contacts.SEND_TO_VOICEMAIL + "=1";
+
+        static final int ID_COLUMN_INDEX = 0;
+    }
+
+    private static class PhoneQuery {
+        static final String[] PROJECTION = {
+            Phone.NORMALIZED_NUMBER,
+            Phone.NUMBER
+        };
+
+        static final int NORMALIZED_NUMBER_COLUMN_INDEX = 0;
+        static final int NUMBER_COLUMN_INDEX = 1;
+
+        static final String SELECT_SEND_TO_VOICEMAIL_TRUE = Contacts.SEND_TO_VOICEMAIL + "=1";
+    }
+
+    /**
+     * Checks if there exists a contact with {@code Contacts.SEND_TO_VOICEMAIL} set to true.
+     */
+    public static void checkForSendToVoicemailContact(
+            final Context context, final CheckForSendToVoicemailContactListener listener) {
+        final AsyncTask task = new AsyncTask<Object, Void, Boolean>() {
+            @Override
+            public Boolean doInBackground(Object[]  params) {
+                if (context == null) {
+                    return false;
+                }
+
+                final Cursor cursor = context.getContentResolver().query(
+                        Contacts.CONTENT_URI,
+                        ContactsQuery.PROJECTION,
+                        ContactsQuery.SELECT_SEND_TO_VOICEMAIL_TRUE,
+                        null,
+                        null);
+
+                boolean hasSendToVoicemailContacts = false;
+                if (cursor != null) {
+                    try {
+                        hasSendToVoicemailContacts = cursor.getCount() > 0;
+                    } finally {
+                        cursor.close();
+                    }
+                }
+
+                return hasSendToVoicemailContacts;
+            }
+
+            @Override
+            public void onPostExecute(Boolean hasSendToVoicemailContact) {
+                if (listener != null) {
+                    listener.onComplete(hasSendToVoicemailContact);
+                }
+            }
+        };
+        task.execute();
+    }
+
+    /**
+     * Blocks all the phone numbers of any contacts marked as SEND_TO_VOICEMAIL, then clears the
+     * SEND_TO_VOICEMAIL flag on those contacts.
+     */
+    public static void importSendToVoicemailContacts(
+            final Context context, final ImportSendToVoicemailContactsListener listener) {
+        final FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler =
+                new FilteredNumberAsyncQueryHandler(context.getContentResolver());
+
+        final AsyncTask task = new AsyncTask<Object, Void, Boolean>() {
+            @Override
+            public Boolean doInBackground(Object[] params) {
+                if (context == null) {
+                    return false;
+                }
+
+                // Get the phone number of contacts marked as SEND_TO_VOICEMAIL.
+                final Cursor phoneCursor = context.getContentResolver().query(
+                        Phone.CONTENT_URI,
+                        PhoneQuery.PROJECTION,
+                        PhoneQuery.SELECT_SEND_TO_VOICEMAIL_TRUE,
+                        null,
+                        null);
+
+                if (phoneCursor == null) {
+                    return false;
+                }
+
+                try {
+                    while (phoneCursor.moveToNext()) {
+                        final String normalizedNumber = phoneCursor.getString(
+                                PhoneQuery.NORMALIZED_NUMBER_COLUMN_INDEX);
+                        final String number = phoneCursor.getString(
+                                PhoneQuery.NUMBER_COLUMN_INDEX);
+                        if (normalizedNumber != null) {
+                            // Block the phone number of the contact.
+                            mFilteredNumberAsyncQueryHandler.blockNumber(
+                                    null, normalizedNumber, number, null);
+                        }
+                    }
+                } finally {
+                    phoneCursor.close();
+                }
+
+                // Clear SEND_TO_VOICEMAIL on all contacts. The setting has been imported to Dialer.
+                ContentValues newValues = new ContentValues();
+                newValues.put(Contacts.SEND_TO_VOICEMAIL, 0);
+                context.getContentResolver().update(
+                        Contacts.CONTENT_URI,
+                        newValues,
+                        ContactsQuery.SELECT_SEND_TO_VOICEMAIL_TRUE,
+                        null);
+
+                return true;
+            }
+
+            @Override
+            public void onPostExecute(Boolean success) {
+                if (success) {
+                    if (listener != null) {
+                        listener.onImportComplete();
+                    }
+                } else if (context != null) {
+                    String toastStr = context.getString(R.string.send_to_voicemail_import_failed);
+                    Toast.makeText(context, toastStr, Toast.LENGTH_SHORT).show();
+                }
+            }
+        };
+        task.execute();
+    }
+
+     /**
+     * WARNING: This method should NOT be executed on the UI thread.
+     * Use {@code FilteredNumberAsyncQueryHandler} to asynchronously check if a number is blocked.
+     */
+    public static boolean isBlocked(Context context, String number, String countryIso) {
+        final String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso);
+        if (TextUtils.isEmpty(normalizedNumber)) {
+            return false;
+        }
+
+        final Cursor cursor = context.getContentResolver().query(
+                FilteredNumber.CONTENT_URI,
+                new String[] { FilteredNumberColumns._ID },
+                FilteredNumberColumns.NORMALIZED_NUMBER + "=?",
+                new String[] { normalizedNumber },
+                null);
+
+        boolean isBlocked = false;
+        if (cursor != null) {
+            try {
+                isBlocked = cursor.getCount() > 0;
+            } finally {
+                cursor.close();
+            }
+        }
+
+        return isBlocked;
+    }
+
+    public static boolean canBlockNumber(Context context, String number) {
+        if (PhoneNumberUtils.isEmergencyNumber(number)) {
+            return false;
+        }
+
+        TelecomManager telecomManager =
+                (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+        List<PhoneAccountHandle> phoneAccountHandles = telecomManager.getCallCapablePhoneAccounts();
+        for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) {
+            if (telecomManager.isVoiceMailNumber(phoneAccountHandle, number)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/src/com/android/dialer/list/BlockedListSearchFragment.java b/src/com/android/dialer/list/BlockedListSearchFragment.java
index ae64057..498994f 100644
--- a/src/com/android/dialer/list/BlockedListSearchFragment.java
+++ b/src/com/android/dialer/list/BlockedListSearchFragment.java
@@ -87,20 +87,13 @@
 
     private void blockNumber(final String number) {
         final String countryIso = GeoUtil.getCurrentCountryIso(getContext());
-        final String normalizedNumber =
-                FilteredNumberAsyncQueryHandler.getNormalizedNumber(number, countryIso);
-        if (normalizedNumber == null) {
-            Toast.makeText(getContext(), getString(R.string.invalidNumber, number),
-                    Toast.LENGTH_SHORT).show();
-            return;
-        }
         final OnCheckBlockedListener onCheckListener = new OnCheckBlockedListener() {
             @Override
             public void onCheckComplete(Integer id) {
                 if (id == null) {
                     FilterNumberDialogFragment.show(
                             id,
-                            normalizedNumber,
+                            null,
                             number,
                             countryIso,
                             number,
@@ -113,8 +106,12 @@
                 }
             }
         };
-        mFilteredNumberAsyncQueryHandler.startBlockedQuery(
-                onCheckListener, normalizedNumber, number, countryIso);
+        boolean failed = mFilteredNumberAsyncQueryHandler.startBlockedQuery(
+                onCheckListener, null, number, countryIso);
+        if (failed) {
+            Toast.makeText(getContext(), getString(R.string.invalidNumber, number),
+                    Toast.LENGTH_SHORT).show();
+        }
     }
 
     @Override
@@ -137,14 +134,6 @@
             final ContactListItemView view,
             final String number,
             final Integer blockId) {
-        final String countryIso = GeoUtil.getCurrentCountryIso(getContext());
-        final String normalizedNumber =
-                FilteredNumberAsyncQueryHandler.getNormalizedNumber(number, countryIso);
-        if (normalizedNumber == null) {
-            Toast.makeText(getContext(), getString(R.string.invalidNumber, number),
-                    Toast.LENGTH_SHORT).show();
-            return;
-        }
         if (blockId != null) {
             Toast.makeText(getContext(), getString(R.string.alreadyBlocked, number),
                     Toast.LENGTH_SHORT).show();
@@ -153,9 +142,9 @@
 
         FilterNumberDialogFragment.show(
                 blockId,
-                normalizedNumber,
+                null,
                 number,
-                countryIso,
+                GeoUtil.getCurrentCountryIso(getContext()),
                 number,
                 R.id.blocked_numbers_activity_container,
                 getFragmentManager(),
diff --git a/tests/src/com/android/dialer/filterednumber/FilteredNumbersUtilTest.java b/tests/src/com/android/dialer/filterednumber/FilteredNumbersUtilTest.java
new file mode 100644
index 0000000..8011755
--- /dev/null
+++ b/tests/src/com/android/dialer/filterednumber/FilteredNumbersUtilTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.filterednumber;
+
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.test.AndroidTestCase;
+
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.LinkedList;
+
+public class FilteredNumbersUtilTest extends AndroidTestCase {
+
+    private static final String NORMAL_PHONE_NUMBER = "555-555-5555";
+    private static final String VOICEMAIL_NUMBER = "*555";
+
+    @Mock private Context mContext;
+    @Mock private TelecomManager mTelecomManager;
+    private PhoneAccountHandle mHandle;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
+
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager);
+
+        LinkedList<PhoneAccountHandle> handles = new LinkedList<PhoneAccountHandle>();
+        mHandle = new PhoneAccountHandle(new ComponentName(getContext(), "TestCase"), "0");
+        handles.add(mHandle);
+
+        when(mTelecomManager.getCallCapablePhoneAccounts()).thenReturn(handles);
+    }
+
+    public void testCanBlockNormalNumber() {
+        assertTrue(FilteredNumbersUtil.canBlockNumber(mContext, NORMAL_PHONE_NUMBER));
+    }
+
+    public void testCannotBlockVoicemailNumber() {
+        when(mTelecomManager.isVoiceMailNumber(Matchers.eq(mHandle), Matchers.anyString()))
+                .thenReturn(true);
+        assertFalse(FilteredNumbersUtil.canBlockNumber(mContext, VOICEMAIL_NUMBER));
+    }
+}