Build management screen for managing blocked numbers.

Currently uses a simple dialog for adding numbers.
Search function to be added in separate CL.

Bug: 23350280
Change-Id: Ib25b9e0d72e95853af88c446b2143341f6d077cb
diff --git a/res/layout/blocked_number_fragment.xml b/res/layout/blocked_number_fragment.xml
new file mode 100644
index 0000000..b1e4d0f
--- /dev/null
+++ b/res/layout/blocked_number_fragment.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:card_view="http://schemas.android.com/apk/res-auto"
+    android:orientation="vertical"
+    android:background="@color/blocked_number_background"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        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">
+
+            <include layout="@layout/blocked_number_header" />
+
+            <TextView android:id="@id/android:empty"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:paddingStart="@dimen/blocked_number_horizontal_margin"
+                android:paddingTop="@dimen/blocked_number_top_margin"
+                android:paddingBottom="@dimen/blocked_number_bottom_margin"
+                android:text="@string/listNoBlockedNumbers" />
+
+        </LinearLayout>
+
+    </android.support.v7.widget.CardView>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/blocked_number_header.xml b/res/layout/blocked_number_header.xml
new file mode 100644
index 0000000..fed94cc
--- /dev/null
+++ b/res/layout/blocked_number_header.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/blockList"
+        android:paddingStart="@dimen/blocked_number_horizontal_margin"
+        android:paddingTop="@dimen/blocked_number_top_margin"
+        android:paddingBottom="@dimen/blocked_number_bottom_margin"
+        android:textColor="@color/blocked_number_accent_color"
+        style="@android:style/TextAppearance.Material.Subhead" />
+
+    <Button
+        android:id="@+id/add_number_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/blockNumber"
+        android:layout_gravity="right"
+        android:textColor="@color/blocked_number_accent_color"
+        style="?android:attr/borderlessButtonStyle" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/blocked_number_item.xml b/res/layout/blocked_number_item.xml
new file mode 100644
index 0000000..6c87533
--- /dev/null
+++ b/res/layout/blocked_number_item.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/caller_information"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="@dimen/blocked_number_horizontal_margin"
+    android:paddingTop="@dimen/blocked_number_top_margin"
+    android:paddingBottom="@dimen/blocked_number_bottom_margin"
+    android:baselineAligned="false"
+    android:gravity="center_vertical"
+    android:orientation="horizontal"
+    android:focusable="true"
+    android:background="@color/background_dialer_white">
+
+    <QuickContactBadge
+        android:id="@+id/quick_contact_photo"
+        android:layout_width="@dimen/contact_photo_size"
+        android:layout_height="@dimen/contact_photo_size"
+        android:focusable="true" />
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:gravity="center_vertical"
+        android:layout_marginStart="@dimen/blocked_number_horizontal_margin">
+
+        <TextView
+            android:id="@+id/caller_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@color/blocked_number_primary_text_color"
+            android:textSize="@dimen/blocked_number_primary_text_size"
+            android:includeFontPadding="false"
+            android:layout_marginBottom="5dp"
+            android:singleLine="true" />
+
+        <TextView
+            android:id="@+id/caller_number"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@color/blocked_number_secondary_text_color"
+            android:textSize="@dimen/blocked_number_secondary_text_size"
+            android:layout_marginBottom="1dp"
+            android:singleLine="true" />
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/delete_button"
+        android:layout_width="@dimen/blocked_number_delete_icon_size"
+        android:layout_height="@dimen/blocked_number_delete_icon_size"
+        android:layout_marginEnd="16dp"
+        android:background="?android:attr/selectableItemBackgroundBorderless"
+        android:src="@drawable/ic_remove"
+        android:scaleType="center"
+        android:tint="@color/delete_icon_tint"
+        android:contentDescription="@string/description_blocked_number_list_delete" />
+
+</LinearLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 8ce3c17..a747927 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -104,4 +104,11 @@
     <color name="floating_action_button_touch_tint">#80ffffff</color>
 
     <color name="call_log_action_divider">#eeeeee</color>
+
+    <!--  Colors for blocked numbers list -->
+    <color name="blocked_number_primary_text_color">@color/dialtacts_primary_text_color</color>
+    <color name="blocked_number_secondary_text_color">@color/dialtacts_secondary_text_color</color>
+    <color name="delete_icon_tint">#6D6D6D</color>
+    <color name="blocked_number_background">#E0E0E0</color>
+    <color name="blocked_number_accent_color">#42A5F5</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 776cd11..b5acf20 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -147,4 +147,12 @@
     <dimen name="promo_card_line_spacing">4dp</dimen>
 
     <dimen name="voicemail_playback_top_padding">12dp</dimen>
+
+    <!-- Size of entries in blocked numbers list -->
+    <dimen name="blocked_number_horizontal_margin">16dp</dimen>
+    <dimen name="blocked_number_top_margin">16dp</dimen>
+    <dimen name="blocked_number_bottom_margin">16dp</dimen>
+    <dimen name="blocked_number_primary_text_size">16sp</dimen>
+    <dimen name="blocked_number_secondary_text_size">12sp</dimen>
+    <dimen name="blocked_number_delete_icon_size">32dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7bd88ba..093aa19 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -805,6 +805,40 @@
     <!-- Label for the call settings section [CHAR LIMIT=30] -->
     <string name="call_settings_label">Calls</string>
 
+    <!-- Label for the blocked calls settings section [CHAR LIMIT=30] -->
+    <string name="blocked_calls_settings_label">Spam and blocked calls</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]-->
+    <string name="description_blocked_number_list_delete">Unblock number</string>
+
+    <!-- Displayed in the blocked numbers list when there are no blocked numbers.
+        [CHAR LIMIT=NONE] -->
+    <string name="listNoBlockedNumbers">No blocked numbers</string>
+
+    <!-- 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] -->
+    <string name="blockList">Block list</string>
+
+    <!-- Label for progress dialog when validating a number to be added to the block list.
+        [CHAR LIMIT=64] -->
+    <string name="checkingNumber">Checking
+        <xliff:g id="number" example="(555) 555-5555">%1$s</xliff:g>
+    </string>
+
+    <!-- Error message shown when user tries to add invalid number to the block list.
+        [CHAR LIMIT=64] -->
+    <string name="invalidNumber"><xliff:g id="number" example="(555) 555-5555">%1$s</xliff:g>
+        is invalid.</string>
+
+    <!-- Error message shown when user tries to add a number to the block list that was already
+        blocked. [CHAR LIMIT=64] -->
+    <string name="alreadyBlocked"><xliff:g id="number" example="(555) 555-5555">%1$s</xliff:g>
+        is already blocked.</string>
+
     <!-- Label for the phone account settings [CHAR LIMIT=30] -->
     <string name="phone_account_settings_label">Calling accounts</string>
 
diff --git a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
index 521b2a4..7fc62a2 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
@@ -320,6 +320,8 @@
                         FilterNumberDialogFragment.newInstance(blockId, info.normalizedNumber,
                                 number, countryIso, info.formattedNumber);
                 newFragment.setQueryHandler(mFilteredNumberAsyncQueryHandler);
+                newFragment.setParentView(
+                        ((Activity) mContext).findViewById(R.id.floating_action_button_container));
                 newFragment.show(((Activity) mContext).getFragmentManager(),
                         FilterNumberDialogFragment.BLOCK_DIALOG_FRAGMENT);
                 return true;
diff --git a/src/com/android/dialer/database/DialerDatabaseHelper.java b/src/com/android/dialer/database/DialerDatabaseHelper.java
index d36a0f6..271afee 100644
--- a/src/com/android/dialer/database/DialerDatabaseHelper.java
+++ b/src/com/android/dialer/database/DialerDatabaseHelper.java
@@ -75,7 +75,7 @@
      *   0-98   KitKat
      * </pre>
      */
-    public static final int DATABASE_VERSION = 6;
+    public static final int DATABASE_VERSION = 7;
     public static final String DATABASE_NAME = "dialer.db";
 
     /**
@@ -440,7 +440,7 @@
             return;
         }
 
-        if (oldVersion < 6) {
+        if (oldVersion < 7) {
             db.execSQL("DROP TABLE IF EXISTS " + Tables.FILTERED_NUMBER_TABLE);
             db.execSQL("CREATE TABLE " + Tables.FILTERED_NUMBER_TABLE + " ("
                     + FilteredNumberColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
@@ -453,7 +453,7 @@
                     + FilteredNumberColumns.TYPE + " INTEGER,"
                     + FilteredNumberColumns.SOURCE + " INTEGER"
                     + ");");
-            oldVersion = 6;
+            oldVersion = 7;
         }
 
         if (oldVersion != DATABASE_VERSION) {
diff --git a/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java b/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java
index cedde27..061d628 100644
--- a/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java
+++ b/src/com/android/dialer/database/FilteredNumberAsyncQueryHandler.java
@@ -121,7 +121,7 @@
         isBlocked(listener, normalizedNumber);
     }
 
-    private String getNormalizedNumber(String number, String countryIso) {
+    public static String getNormalizedNumber(String number, String countryIso) {
         if (PhoneNumberHelper.isUriNumber(number)) {
             return number;
         } else {
diff --git a/src/com/android/dialer/database/FilteredNumberContract.java b/src/com/android/dialer/database/FilteredNumberContract.java
index 0ec171b..38c49dc 100644
--- a/src/com/android/dialer/database/FilteredNumberContract.java
+++ b/src/com/android/dialer/database/FilteredNumberContract.java
@@ -61,7 +61,7 @@
 
     public interface FilteredNumberColumns {
         // TYPE: INTEGER
-        static final String _ID = "id";
+        static final String _ID = "_id";
         /**
          * Represents the number to be filtered, normalized to compare phone numbers for equality.
          *
diff --git a/src/com/android/dialer/filterednumber/BlockedNumberAdapter.java b/src/com/android/dialer/filterednumber/BlockedNumberAdapter.java
new file mode 100644
index 0000000..504b520
--- /dev/null
+++ b/src/com/android/dialer/filterednumber/BlockedNumberAdapter.java
@@ -0,0 +1,148 @@
+/*
+ * 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.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.provider.ContactsContract;
+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.contacts.common.ContactPhotoManager;
+import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
+import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.util.UriUtils;
+import com.android.dialer.R;
+import com.android.dialer.calllog.ContactInfo;
+import com.android.dialer.calllog.ContactInfoHelper;
+import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
+import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns;
+import com.android.dialer.util.PhoneNumberUtil;
+
+public class BlockedNumberAdapter extends SimpleCursorAdapter {
+
+    private Context mContext;
+    private ContactInfoHelper mContactInfoHelper;
+    private Resources mResources;
+    private BidiFormatter mBidiFormatter = BidiFormatter.getInstance();
+    private ContactPhotoManager mContactPhotoManager;
+    private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler;
+
+    public BlockedNumberAdapter(Context context,
+                                FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler) {
+        super(context, R.layout.blocked_number_item, null, new String[]{}, new int[]{}, 0);
+        mContext = context;
+        mContactInfoHelper = new ContactInfoHelper(context, GeoUtil.getCurrentCountryIso(context));
+        mContactPhotoManager = ContactPhotoManager.getInstance(context);
+        mResources = context.getResources();
+        mFilteredNumberAsyncQueryHandler = filteredNumberAsyncQueryHandler;
+    }
+
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        super.bindView(view, context, cursor);
+        final TextView callerName = (TextView) view.findViewById(R.id.caller_name);
+        final TextView callerNumber = (TextView) view.findViewById(R.id.caller_number);
+        final View deleteNumber = view.findViewById(R.id.delete_button);
+        final QuickContactBadge quickContactBadge =
+                (QuickContactBadge) view.findViewById(R.id.quick_contact_photo);
+        quickContactBadge.setOverlay(null);
+        quickContactBadge.setPrioritizedMimeType(
+                ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
+
+        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 String normalizedNumber = cursor.getString(cursor.getColumnIndex(
+                FilteredNumberColumns.NORMALIZED_NUMBER));
+        final ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso);
+        final CharSequence locationOrType = getNumberTypeOrLocation(info);
+        final String displayNumber = getDisplayNumber(info);
+        final String displayNumberStr = mBidiFormatter.unicodeWrap(
+                displayNumber.toString(), TextDirectionHeuristics.LTR);
+
+        deleteNumber.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                FilterNumberDialogFragment newFragment =
+                        FilterNumberDialogFragment.newInstance(id, normalizedNumber, number,
+                                countryIso, displayNumber);
+                newFragment.setQueryHandler(mFilteredNumberAsyncQueryHandler);
+                newFragment.setParentView(view);
+                newFragment.show(((Activity) mContext).getFragmentManager(),
+                        FilterNumberDialogFragment.BLOCK_DIALOG_FRAGMENT);
+            }
+        });
+
+        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)
+                ? ContactPhotoManager.TYPE_BUSINESS : ContactPhotoManager.TYPE_DEFAULT;
+        final DefaultImageRequest request = new DefaultImageRequest(displayName, lookupKey,
+                contactType, true /* isCircular */);
+        badge.assignContactUri(info.lookupUri);
+        badge.setContentDescription(
+                mResources.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(mResources, info.type,
+                    info.label);
+        } else {
+            return PhoneNumberUtil.getGeoDescription(mContext, info.number);
+        }
+    }
+}
diff --git a/src/com/android/dialer/filterednumber/BlockedNumberFragment.java b/src/com/android/dialer/filterednumber/BlockedNumberFragment.java
new file mode 100644
index 0000000..a65013a
--- /dev/null
+++ b/src/com/android/dialer/filterednumber/BlockedNumberFragment.java
@@ -0,0 +1,154 @@
+/*
+ * 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.app.AlertDialog;
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.CursorLoader;
+import android.content.DialogInterface;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.dialog.IndeterminateProgressDialog;
+import com.android.dialer.R;
+import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
+import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
+import com.android.dialer.database.FilteredNumberContract;
+
+public class BlockedNumberFragment extends ListFragment implements
+        LoaderManager.LoaderCallbacks<Cursor>, View.OnClickListener {
+
+    private BlockedNumberAdapter mAdapter;
+    private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler;
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        LayoutInflater inflater = LayoutInflater.from(getContext());
+        getListView().addHeaderView(inflater.inflate(R.layout.blocked_number_header, null));
+        mFilteredNumberAsyncQueryHandler =
+                new FilteredNumberAsyncQueryHandler(getActivity().getContentResolver());
+        if (mAdapter == null) {
+            mAdapter = new BlockedNumberAdapter(getContext(), mFilteredNumberAsyncQueryHandler);
+        }
+        setListAdapter(mAdapter);
+        final Button addNumberBtn = (Button) getActivity().findViewById(R.id.add_number_button);
+        addNumberBtn.setOnClickListener(this);
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        setListAdapter(null);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.blocked_number_fragment, container, false);
+        return view;
+    }
+
+    @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;
+        final CursorLoader cursorLoader = new CursorLoader(
+                getContext(), FilteredNumberContract.FilteredNumber.CONTENT_URI, projection,
+                selection, 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 v) {
+        final String countryIso = GeoUtil.getCurrentCountryIso(getContext());
+        final EditText numberField = new EditText(getContext());
+        final DialogInterface.OnClickListener okListener = new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int whichButton) {
+                final String number = numberField.getText().toString();
+                final IndeterminateProgressDialog progressDialog =
+                        IndeterminateProgressDialog.show(getFragmentManager(),
+                                getString(R.string.checkingNumber, number), null, 1000);
+                final String normalizedNumber =
+                        FilteredNumberAsyncQueryHandler.getNormalizedNumber(number, countryIso);
+                if (normalizedNumber == null) {
+                    progressDialog.dismiss();
+                    Toast.makeText(getContext(), getString(R.string.invalidNumber, number),
+                            Toast.LENGTH_LONG).show();
+                } else {
+                    final OnCheckBlockedListener onCheckListener = new OnCheckBlockedListener() {
+                        @Override
+                        public void onCheckComplete(Integer id) {
+                            progressDialog.dismiss();
+                            if (id == null) {
+                                FilterNumberDialogFragment newFragment =
+                                        FilterNumberDialogFragment.newInstance(id, normalizedNumber,
+                                                number, countryIso, number);
+                                newFragment.setQueryHandler(mFilteredNumberAsyncQueryHandler);
+                                newFragment.setParentView(v);
+                                newFragment.show(getActivity().getFragmentManager(),
+                                        FilterNumberDialogFragment.BLOCK_DIALOG_FRAGMENT);
+                            } else {
+                                Toast.makeText(getContext(),
+                                        getString(R.string.alreadyBlocked, number),
+                                        Toast.LENGTH_LONG).show();
+                            }
+                        }
+                    };
+                    mFilteredNumberAsyncQueryHandler.isBlocked(
+                            onCheckListener, normalizedNumber, number, countryIso);
+                }
+            }
+        };
+        new AlertDialog.Builder(getContext())
+                .setTitle(getString(R.string.blockNumber))
+                .setView(numberField)
+                .setPositiveButton(getString(R.string.blockNumberOk), okListener)
+                .setNegativeButton(android.R.string.cancel, null)
+                .show();
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java b/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java
index f94d0f8..e9a88c8 100644
--- a/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java
+++ b/src/com/android/dialer/filterednumber/FilterNumberDialogFragment.java
@@ -39,11 +39,16 @@
     private static final String ARG_DISPLAY_NUMBER = "argDisplayNumber";
 
     private FilteredNumberAsyncQueryHandler mHandler;
+    private View mParentView;
 
     public void setQueryHandler (FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler) {
         mHandler = filteredNumberAsyncQueryHandler;
     }
 
+    public void setParentView(View view) {
+        mParentView = view;
+    }
+
     public static FilterNumberDialogFragment newInstance(Integer blockId, String normalizedNumber,
         String number, String countryIso, String displayNumber) {
         final FilterNumberDialogFragment fragment = new FilterNumberDialogFragment();
@@ -91,7 +96,6 @@
     }
 
     public void blockNumber() {
-        final View view = getActivity().findViewById(R.id.floating_action_button_container);
         final String displayNumber = getArguments().getString(ARG_DISPLAY_NUMBER);
         final String message = getString(R.string.snackbar_number_blocked, displayNumber);
         final String undoMessage = getString(R.string.snackbar_number_unblocked, displayNumber);
@@ -99,7 +103,7 @@
                 new FilteredNumberAsyncQueryHandler.OnUnblockNumberListener() {
                     @Override
                     public void onUnblockComplete(int rows, ContentValues values) {
-                        Snackbar.make(view, undoMessage, Snackbar.LENGTH_LONG).show();
+                        Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
                     }
                 };
 
@@ -107,7 +111,7 @@
                 new FilteredNumberAsyncQueryHandler.OnBlockNumberListener() {
                     @Override
                     public void onBlockComplete(final Uri uri) {
-                        Snackbar.make(view, message, Snackbar.LENGTH_LONG)
+                        Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG)
                                 .setAction(R.string.block_number_undo,
                                         // Delete the newly created row on 'undo'.
                                         new View.OnClickListener() {
@@ -123,7 +127,6 @@
     }
 
     public void unblockNumber() {
-        final View view = getActivity().findViewById(R.id.floating_action_button_container);
         final String displayNumber = getArguments().getString(ARG_DISPLAY_NUMBER);
         final String message = getString(R.string.snackbar_number_unblocked, displayNumber);
         final String undoMessage = getString(R.string.snackbar_number_blocked, displayNumber);
@@ -131,14 +134,14 @@
                 new FilteredNumberAsyncQueryHandler.OnBlockNumberListener() {
                     @Override
                     public void onBlockComplete(final Uri uri) {
-                        Snackbar.make(view, undoMessage, Snackbar.LENGTH_LONG).show();
+                        Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
                     }
                 };
         mHandler.unblock(
                 new FilteredNumberAsyncQueryHandler.OnUnblockNumberListener() {
                     @Override
                     public void onUnblockComplete(int rows, final ContentValues values) {
-                        Snackbar.make(view, message, Snackbar.LENGTH_LONG)
+                        Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG)
                                 .setAction(R.string.block_number_undo,
                                         new View.OnClickListener() {
                                             // Re-insert the row on 'undo', with a new ID.
diff --git a/src/com/android/dialer/settings/DialerSettingsActivity.java b/src/com/android/dialer/settings/DialerSettingsActivity.java
index 01a9fcf..2b72772 100644
--- a/src/com/android/dialer/settings/DialerSettingsActivity.java
+++ b/src/com/android/dialer/settings/DialerSettingsActivity.java
@@ -15,24 +15,20 @@
  */
 package com.android.dialer.settings;
 
-import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Bundle;
-import android.os.Process;
 import android.os.UserManager;
-import android.preference.PreferenceActivity;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
-import android.util.Log;
 import android.view.MenuItem;
 import android.widget.Toast;
 
-import com.android.contacts.common.util.PermissionsUtil;
 import com.android.dialer.R;
+import com.android.dialer.filterednumber.BlockedNumberFragment;
 
 import java.util.List;
 
@@ -90,6 +86,11 @@
                 target.add(phoneAccountSettingsHeader);
             }
 
+            Header blockedCallsHeader = new Header();
+            blockedCallsHeader.titleRes = R.string.blocked_calls_settings_label;
+            blockedCallsHeader.fragment = BlockedNumberFragment.class.getName();
+            target.add(blockedCallsHeader);
+
             if (telephonyManager.isTtyModeSupported()
                     || telephonyManager.isHearingAidCompatibilitySupported()) {
                 Header accessibilitySettingsHeader = new Header();
diff --git a/src/com/android/dialer/util/PhoneNumberUtil.java b/src/com/android/dialer/util/PhoneNumberUtil.java
index 84f58aa..539d8b9 100644
--- a/src/com/android/dialer/util/PhoneNumberUtil.java
+++ b/src/com/android/dialer/util/PhoneNumberUtil.java
@@ -25,13 +25,19 @@
 import android.util.Pair;
 
 import com.android.contacts.common.util.PhoneNumberHelper;
+import com.android.contacts.common.util.TelephonyManagerUtils;
 import com.google.common.collect.Sets;
+import com.google.i18n.phonenumbers.NumberParseException;
+import com.google.i18n.phonenumbers.Phonenumber;
+import com.google.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
 
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
 public class PhoneNumberUtil {
+    private static final String TAG = "PhoneNumberUtil";
     private static final Set<String> LEGACY_UNKNOWN_NUMBERS = Sets.newHashSet("-1", "-2", "-3");
 
     /** Returns true if it is possible to place a call to the given number. */
@@ -92,4 +98,41 @@
     public static boolean isLegacyUnknownNumbers(CharSequence number) {
         return number != null && LEGACY_UNKNOWN_NUMBERS.contains(number.toString());
     }
+
+    /**
+     * @return a geographical description string for the specified number.
+     * @see com.android.i18n.phonenumbers.PhoneNumberOfflineGeocoder
+     */
+    public static String getGeoDescription(Context context, String number) {
+        Log.v(TAG, "getGeoDescription('" + number + "')...");
+
+        if (TextUtils.isEmpty(number)) {
+            return null;
+        }
+
+        com.google.i18n.phonenumbers.PhoneNumberUtil util =
+                com.google.i18n.phonenumbers.PhoneNumberUtil.getInstance();
+        PhoneNumberOfflineGeocoder geocoder = PhoneNumberOfflineGeocoder.getInstance();
+
+        Locale locale = context.getResources().getConfiguration().locale;
+        String countryIso = TelephonyManagerUtils.getCurrentCountryIso(context, locale);
+        Phonenumber.PhoneNumber pn = null;
+        try {
+            Log.v(TAG, "parsing '" + number
+                    + "' for countryIso '" + countryIso + "'...");
+            pn = util.parse(number, countryIso);
+            Log.v(TAG, "- parsed number: " + pn);
+        } catch (NumberParseException e) {
+            Log.v(TAG, "getGeoDescription: NumberParseException for incoming number '" +
+                    number + "'");
+        }
+
+        if (pn != null) {
+            String description = geocoder.getDescriptionForNumber(pn, locale);
+            Log.v(TAG, "- got description: '" + description + "'");
+            return description;
+        }
+
+        return null;
+    }
 }