Blacklist (6/9)

Add back call blacklist backend.

Change-Id: I065f1c206cbb86b790e698b396cc611c9a434279

Conflicts:
	src/com/android/phone/CallFeaturesSetting.java
	src/com/android/phone/CallNotifier.java

Move message blacklist to framework (2/3).

Change-Id: Ie1bea95513b985e79da2e28c86b15a2042c98754

Telephony: fix Blacklist support for L (2/3)

Change-Id: I9cd9c7007fc1016bf2872b01b18334bd8c2601ba
Signed-off-by: Roman Birg <roman@cyngn.com>

Conflicts:
	src/com/android/phone/CallNotifier.java

Fix blacklist for SlimLP

Change-Id: If52d357bdd5c3992ba78fe1195e9a76b07b527d6
Signed-off-by: RobbieL811 <RobbieLancaster811@gmail.com>

Fix blacklist for SlimLP

Revert "Telephony: fix Blacklist support for L (2/3)"

This reverts commit 640561592e4ebf6c7cb1675ca47c5bf567b565a3.

Change-Id: I6b1f5dfd4f2bc6a81df31cdeb5ac46e7ebd7bcec
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7d6aad6..0f1ff89 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -65,6 +65,7 @@
     <uses-permission android:name="android.permission.STATUS_BAR" />
     <uses-permission android:name="android.permission.READ_SMS" />
     <uses-permission android:name="android.permission.WRITE_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
     <uses-permission android:name="android.permission.SEND_SMS" />
     <uses-permission android:name="android.permission.SEND_RESPOND_VIA_MESSAGE" />
     <uses-permission android:name="android.permission.SET_TIME" />
@@ -520,6 +521,9 @@
         <service android:name="EmergencyCallbackModeService">
         </service>
 
+        <!-- Blacklist notification intent service -->
+        <service android:name="ClarBlacklistService" />
+
         <!-- service to dump telephony information -->
         <service android:name="TelephonyDebugService" />
 
@@ -617,6 +621,12 @@
             android:theme="@style/DialerSettingsLight">
         </activity>
 
+        <receiver android:name="RejectedSmsReceiver">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_REJECTED" />
+            </intent-filter>
+        </receiver>
+
         <!-- BroadcastReceiver for receiving Intents from Notification mechanism. -->
         <receiver android:name="PhoneGlobals$NotificationBroadcastReceiver" android:exported="false">
             <intent-filter>
diff --git a/res/drawable-hdpi/ic_block_contact_holo_dark.png b/res/drawable-hdpi/ic_block_contact_holo_dark.png
new file mode 100644
index 0000000..4c05fbe
--- /dev/null
+++ b/res/drawable-hdpi/ic_block_contact_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_block_message_holo_dark.png b/res/drawable-hdpi/ic_block_message_holo_dark.png
new file mode 100644
index 0000000..739559f
--- /dev/null
+++ b/res/drawable-hdpi/ic_block_message_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_unblock_contact_holo_dark.png b/res/drawable-hdpi/ic_unblock_contact_holo_dark.png
new file mode 100644
index 0000000..682800c
--- /dev/null
+++ b/res/drawable-hdpi/ic_unblock_contact_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_unblock_message_holo_dark.png b/res/drawable-hdpi/ic_unblock_message_holo_dark.png
new file mode 100644
index 0000000..498d246
--- /dev/null
+++ b/res/drawable-hdpi/ic_unblock_message_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_block_contact_holo_dark.png b/res/drawable-mdpi/ic_block_contact_holo_dark.png
new file mode 100644
index 0000000..1daf088
--- /dev/null
+++ b/res/drawable-mdpi/ic_block_contact_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_block_message_holo_dark.png b/res/drawable-mdpi/ic_block_message_holo_dark.png
new file mode 100644
index 0000000..08aee62
--- /dev/null
+++ b/res/drawable-mdpi/ic_block_message_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_unblock_contact_holo_dark.png b/res/drawable-mdpi/ic_unblock_contact_holo_dark.png
new file mode 100644
index 0000000..f60ee16
--- /dev/null
+++ b/res/drawable-mdpi/ic_unblock_contact_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_unblock_message_holo_dark.png b/res/drawable-mdpi/ic_unblock_message_holo_dark.png
new file mode 100644
index 0000000..d9526d0
--- /dev/null
+++ b/res/drawable-mdpi/ic_unblock_message_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_block_contact_holo_dark.png b/res/drawable-xhdpi/ic_block_contact_holo_dark.png
new file mode 100644
index 0000000..afea00f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_block_contact_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_block_message_holo_dark.png b/res/drawable-xhdpi/ic_block_message_holo_dark.png
new file mode 100644
index 0000000..0d78f36
--- /dev/null
+++ b/res/drawable-xhdpi/ic_block_message_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_unblock_contact_holo_dark.png b/res/drawable-xhdpi/ic_unblock_contact_holo_dark.png
new file mode 100644
index 0000000..659a9a2
--- /dev/null
+++ b/res/drawable-xhdpi/ic_unblock_contact_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_unblock_message_holo_dark.png b/res/drawable-xhdpi/ic_unblock_message_holo_dark.png
new file mode 100644
index 0000000..3cd13c1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_unblock_message_holo_dark.png
Binary files differ
diff --git a/res/values/custom_strings.xml b/res/values/custom_strings.xml
index 573ba23..430dbcf 100644
--- a/res/values/custom_strings.xml
+++ b/res/values/custom_strings.xml
@@ -25,4 +25,24 @@
     <string name="prox_auto_speaker_incall_only_summary_on">On outgoing calls, listener will only become active after the receiving party answers</string>
     <string name="prox_auto_speaker_incall_only_summary_off">Listener will be active during all offhook call states (default)</string>
 
-</resources>
+    <!-- Advanced Settings -->
+    <string name="advanced_setting">Advanced Settings</string>
+
+    <!-- Blacklist preferences -->
+    <string name="blacklist_title">Blacklist</string>
+    <string name="blacklist_summary_disabled">Disabled</string>
+    <string name="blacklist_summary">Incoming calls from phone numbers in the blacklist will be blocked</string>
+
+    <!-- Blacklist notifications -->
+    <string name="blacklist_call_notification">Blocked a call from %s</string>
+    <string name="blacklist_call_notification_private_number">Blocked a call from a private number</string>
+    <string name="blacklist_call_notification_unknown_number">Blocked a call from unknown number %s</string>
+    <string name="blacklist_call_notification_multiple"><xliff:g id="count">%d</xliff:g> calls were blocked</string>
+    <string name="blacklist_message_notification">Blocked a message from %s</string>
+    <string name="blacklist_message_notification_private_number">Blocked a message from a private number</string>
+    <string name="blacklist_message_notification_unknown_number">Blocked a message from unknown number %s</string>
+    <string name="blacklist_message_notification_multiple"><xliff:g id="count">%d</xliff:g> messages were blocked</string>
+    <string name="blacklist_notification_list_private">Private</string>
+    <string name="unblock_number">Unblock</string>
+
+</resources>
\ No newline at end of file
diff --git a/res/xml/call_feature_setting.xml b/res/xml/call_feature_setting.xml
index 9bf9079..7def545 100644
--- a/res/xml/call_feature_setting.xml
+++ b/res/xml/call_feature_setting.xml
@@ -179,4 +179,17 @@
 
     </PreferenceCategory>
 
+    <PreferenceCategory
+        android:key="pref_advanced_settings"
+        android:title="@string/advanced_settings">
+
+         <PreferenceScreen
+             android:key="button_blacklist"
+             android:title="@string/blacklist_title"
+             android:persistent="false">
+             <intent android:action="android.intent.action.MAIN"
+                 android:targetPackage="com.android.settings"
+                 android:targetClass="com.android.settings.Settings$BlacklistSettingsActivity" />
+         </PreferenceScreen>
+    </PreferenceCategory>
 </PreferenceScreen>
diff --git a/src/com/android/phone/Blacklist.java b/src/com/android/phone/Blacklist.java
new file mode 100644
index 0000000..80c5f06
--- /dev/null
+++ b/src/com/android/phone/Blacklist.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.phone;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.Telephony;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.util.HashSet;
+
+/**
+ * This class used to handle the blacklist data. Its
+ * only remaining purpose is legacy data migration
+ */
+class Blacklist {
+    private static class PhoneNumber implements Externalizable {
+        static final long serialVersionUID = 32847013274L;
+        String phone;
+
+        public PhoneNumber() {
+        }
+
+        public void writeExternal(ObjectOutput out) throws IOException {
+        }
+
+        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+            phone = (String) in.readObject();
+        }
+
+        @Override
+        public int hashCode() {
+            return phone != null ? phone.hashCode() : 0;
+        }
+    }
+
+    private static final String BLFILE = "blacklist.dat";
+    private static final int BLFILE_VER = 1;
+
+    public static void migrateOldDataIfPresent(Context context) {
+        ObjectInputStream ois = null;
+        HashSet<PhoneNumber> data = null;
+
+        try {
+            ois = new ObjectInputStream(context.openFileInput(BLFILE));
+            Object o = ois.readObject();
+            if (o != null && o instanceof Integer) {
+                // check the version
+                Integer version = (Integer) o;
+                if (version == BLFILE_VER) {
+                    Object numbers = ois.readObject();
+                    if (numbers instanceof HashSet) {
+                        data = (HashSet<PhoneNumber>) numbers;
+                    }
+                }
+            }
+        } catch (IOException e) {
+            // Do nothing
+        } catch (ClassNotFoundException e) {
+            // Do nothing
+        } finally {
+            if (ois != null) {
+                try {
+                    ois.close();
+                } catch (IOException e) {
+                    // Do nothing
+                }
+                context.deleteFile(BLFILE);
+            }
+        }
+        if (data != null) {
+            ContentResolver cr = context.getContentResolver();
+            ContentValues cv = new ContentValues();
+            cv.put(Telephony.Blacklist.PHONE_MODE, 1);
+
+            for (PhoneNumber number : data) {
+                Uri uri = Uri.withAppendedPath(
+                        Telephony.Blacklist.CONTENT_FILTER_BYNUMBER_URI, number.phone);
+                cv.put(Telephony.Blacklist.NUMBER, number.phone);
+                cr.update(uri, cv, null, null);
+            }
+        }
+    }
+}
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 6438175..50a71f4 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2014 The CyanogenMod Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -68,6 +69,7 @@
 import com.android.phone.common.util.SettingsUtil;
 import com.android.phone.settings.AccountSelectionPreference;
 import com.android.services.telephony.sip.SipUtil;
+import com.android.internal.telephony.util.BlacklistUtils;
 
 import java.lang.String;
 import java.util.Collection;
@@ -245,6 +247,9 @@
     private static final String VOICEMAIL_VIBRATION_ALWAYS = "always";
     private static final String VOICEMAIL_VIBRATION_NEVER = "never";
 
+    // Blacklist support
+    private static final String BUTTON_BLACKLIST = "button_blacklist";
+
     private EditPhoneNumberPreference mSubMenuVoicemailSettings;
 
     private Runnable mVoicemailRingtoneLookupRunnable;
@@ -271,6 +276,8 @@
     private Preference mVoicemailNotificationRingtone;
     private CheckBoxPreference mVoicemailNotificationVibrate;
     private AccountSelectionPreference mDefaultOutgoingAccount;
+    private boolean isSpeedDialListStarted = false;
+    private PreferenceScreen mButtonBlacklist;
 
     private class VoiceMailProvider {
         public VoiceMailProvider(String name, Intent intent) {
@@ -1768,6 +1775,9 @@
         updateVoiceNumberField();
         mVMProviderSettingsForced = false;
 
+        // Blacklist screen - Needed for setting summary
+        mButtonBlacklist = (PreferenceScreen) prefSet.findPreference(BUTTON_BLACKLIST);
+
         PreferenceScreen selectSub = (PreferenceScreen) findPreference(BUTTON_SELECT_SUB_KEY);
         if (selectSub != null) {
             Intent intent = selectSub.getIntent();
@@ -1820,6 +1830,17 @@
 
         // Look up the voicemail ringtone name asynchronously and update its preference.
         new Thread(mVoicemailRingtoneLookupRunnable).start();
+        updateBlacklistSummary();
+    }
+
+    private void updateBlacklistSummary() {
+        if (mButtonBlacklist != null) {
+            if (BlacklistUtils.isBlacklistEnabled(this)) {
+                mButtonBlacklist.setSummary(R.string.blacklist_summary);
+            } else {
+                mButtonBlacklist.setSummary(R.string.blacklist_summary_disabled);
+            }
+        }
     }
 
     // Migrate settings from BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_WHEN_KEY to
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index 8ffda6f..367e52e 100644
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2014 The CyanogenMod Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,6 +21,7 @@
 import com.android.internal.telephony.CallManager;
 import com.android.internal.telephony.CallerInfo;
 import com.android.internal.telephony.CallerInfoAsyncQuery;
+import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
@@ -29,6 +31,7 @@
 import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaDisplayInfoRec;
 import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaSignalInfoRec;
 import com.android.internal.telephony.cdma.SignalToneUtil;
+import com.android.internal.telephony.util.BlacklistUtils;
 
 import android.app.ActivityManagerNative;
 import android.bluetooth.BluetoothAdapter;
@@ -38,6 +41,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.database.Cursor;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.ToneGenerator;
@@ -49,6 +53,7 @@
 import android.os.SystemVibrator;
 import android.os.Vibrator;
 import android.provider.CallLog.Calls;
+import android.provider.ContactsContract;
 import android.provider.Settings;
 import android.telecom.TelecomManager;
 import android.telephony.DisconnectCause;
@@ -117,6 +122,10 @@
 
     private PhoneStateListener[] mPhoneStateListener;
 
+    // Blacklist handling
+    private static final String BLACKLIST = "Blacklist";
+    private boolean mSilentRingerRequested;
+
     /**
      * Initialize the singleton CallNotifier instance.
      * This is only done once, at startup, from PhoneApp.onCreate().
@@ -343,6 +352,11 @@
             return;
         }
 
+        // Blacklist handling
+        if (isConnectionBlacklisted(c)) {
+            return;
+        }
+
         // Stop any signalInfo tone being played on receiving a Call
         stopSignalInfoTone();
 
@@ -393,6 +407,81 @@
         if (VDBG) log("- onNewRingingConnection() done.");
     }
 
+    private static final String[] FAVORITE_PROJECTION = new String[] {
+        ContactsContract.PhoneLookup.STARRED
+    };
+    private static final String[] CONTACT_PROJECTION = new String[] {
+        ContactsContract.PhoneLookup.NUMBER
+    };
+
+    protected boolean isConnectionBlacklisted(Connection c) {
+        final String number = c.getAddress();
+        if (DBG) log("Incoming number is: " + number);
+        // See if the number is in the blacklist
+        // Result is one of: MATCH_NONE, MATCH_LIST or MATCH_REGEX
+        int listType = BlacklistUtils.isListed(mApplication, number, BlacklistUtils.BLOCK_CALLS);
+        if (listType != BlacklistUtils.MATCH_NONE) {
+            // We have a match, set the user and hang up the call and notify
+            if (DBG) log("Incoming call from " + number + " blocked.");
+            c.setUserData(new CallerInfo().markAsBlacklist());
+            try {
+                mSilentRingerRequested = true;
+                c.hangup();
+                mApplication.notificationMgr.notifyBlacklistedCall(number,
+                        c.getCreateTime(), listType);
+            } catch (CallStateException e) {
+                e.printStackTrace();
+                Log.w(LOG_TAG, "Invalid call state", e);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Helper function used to determine if calling number is from person in the Contacts
+     * Optionally, it can also check if the contact is a 'Starred'or favourite contact
+     */
+    private boolean isContact(String number, boolean checkFavorite) {
+        if (DBG) log("isContact(): checking if " + number + " is in the contact list.");
+
+        Uri lookupUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                Uri.encode(number));
+        Cursor cursor = mApplication.getContentResolver().query(lookupUri,
+                checkFavorite ? FAVORITE_PROJECTION : CONTACT_PROJECTION,
+                ContactsContract.PhoneLookup.NUMBER + "=?",
+                new String[] { number }, null);
+
+        if (cursor == null) {
+            if (DBG) log("Couldn't query contacts provider");
+            return false;
+        }
+
+        try {
+            if (cursor.moveToFirst() && !checkFavorite) {
+                // All we care about is that the number is in the Contacts list
+                if (DBG) log("Number belongs to a contact");
+                return true;
+            }
+
+            // Either no result or we should check for favorite.
+            // In the former case the loop won't be entered.
+            while (!cursor.isAfterLast()) {
+                if (cursor.getInt(cursor.getColumnIndex(
+                        ContactsContract.PhoneLookup.STARRED)) == 1) {
+                    if (DBG) log("Number belongs to a favorite");
+                    return true;
+                }
+                cursor.moveToNext();
+            }
+        } finally {
+            cursor.close();
+        }
+
+        if (DBG) log("A match for the number wasn't found");
+        return false;
+    }
+
     /**
      * Determines whether or not we're allowed to present incoming calls to the
      * user, based on the capabilities and/or current state of the device.
@@ -542,6 +631,11 @@
             Log.w(LOG_TAG, "onDisconnect: null connection");
         }
 
+        boolean disconnectedDueToBlacklist = false;
+        if (c != null) {
+            disconnectedDueToBlacklist = isDisconnectedDueToBlacklist(c);
+        }
+
         int autoretrySetting = 0;
         if ((c != null) &&
                 (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
@@ -580,7 +674,8 @@
         // TODO: We may eventually want to disable this via a preference.
         if ((toneToPlay == InCallTonePlayer.TONE_NONE)
             && (mCM.getState() == PhoneConstants.State.IDLE)
-            && (c != null)) {
+            && (c != null)
+            && !mSilentRingerRequested) {
             int cause = c.getDisconnectCause();
             if ((cause == DisconnectCause.NORMAL)  // remote hangup
                 || (cause == DisconnectCause.LOCAL)) {  // local hangup
@@ -600,7 +695,11 @@
         }
 
         if (c != null) {
-            mCallLogger.logCall(c);
+            if (disconnectedDueToBlacklist) {
+                mCallLogger.logCall(c, Calls.BLACKLIST_TYPE);
+            } else {
+                mCallLogger.logCall(c);
+            }
 
             final String number = c.getAddress();
             final Phone phone = c.getCall().getPhone();
@@ -629,6 +728,7 @@
             if (((mPreviousCdmaCallState == Call.State.DIALING)
                     || (mPreviousCdmaCallState == Call.State.ALERTING))
                     && (!isEmergencyNumber)
+                    && !disconnectedDueToBlacklist
                     && (cause != DisconnectCause.INCOMING_MISSED )
                     && (cause != DisconnectCause.NORMAL)
                     && (cause != DisconnectCause.LOCAL)
@@ -1157,4 +1257,11 @@
     private void log(String msg) {
         Log.d(LOG_TAG, msg);
     }
+
+    protected boolean isDisconnectedDueToBlacklist(Connection c) {
+        if (c == null) {
+            return false;
+        }
+        return ((CallerInfo) c.getUserData()).isBlacklistedNumber();
+    }
 }
diff --git a/src/com/android/phone/ClearBlacklistService.java b/src/com/android/phone/ClearBlacklistService.java
new file mode 100644
index 0000000..d969945
--- /dev/null
+++ b/src/com/android/phone/ClearBlacklistService.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 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.phone;
+
+import com.android.internal.telephony.util.BlacklistUtils;
+
+import android.app.IntentService;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.provider.CallLog.Calls;
+
+/**
+ * Handles the intent to clear the missed calls that is triggered when a notification is dismissed.
+ */
+public class ClearBlacklistService extends IntentService {
+    /** This action is used to clear blacklisted calls. */
+    public static final String ACTION_CLEAR_BLACKLISTED_CALLS =
+            "com.android.phone.intent.CLEAR_BLACKLISTED_CALLS";
+    /** This action is used to clear blacklisted messages. */
+    public static final String ACTION_CLEAR_BLACKLISTED_MESSAGES =
+            "com.android.phone.intent.CLEAR_BLACKLISTED_MESSAGES";
+
+    private PhoneGlobals mApp;
+
+    public ClearBlacklistService() {
+        super(ClearBlacklistService.class.getSimpleName());
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mApp = PhoneGlobals.getInstance();
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        final String action = intent.getAction();
+        if (ACTION_CLEAR_BLACKLISTED_CALLS.equals(action)) {
+            mApp.notificationMgr.cancelBlacklistedNotification(BlacklistUtils.BLOCK_CALLS);
+        } else if (ACTION_CLEAR_BLACKLISTED_MESSAGES.equals(action)) {
+            mApp.notificationMgr.cancelBlacklistedNotification(BlacklistUtils.BLOCK_MESSAGES);
+        }
+    }
+}
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index a027fd2..42a5f75 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -16,6 +16,8 @@
 
 package com.android.phone;
 
+import java.util.ArrayList;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -38,6 +40,9 @@
 import android.telecom.PhoneAccount;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
+import android.text.SpannableStringBuilder;
+import android.text.format.DateUtils;
+import android.text.style.RelativeSizeSpan;
 import android.text.TextUtils;
 import android.util.Log;
 import android.widget.Toast;
@@ -47,6 +52,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.PhoneBase;
 import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.internal.telephony.util.BlacklistUtils;
 
 import java.util.List;
 
@@ -75,6 +81,8 @@
     static final int CALL_FORWARD_NOTIFICATION = 4;
     static final int DATA_DISCONNECTED_ROAMING_NOTIFICATION = 5;
     static final int SELECTED_OPERATOR_FAIL_NOTIFICATION = 6;
+    static final int BLACKLISTED_CALL_NOTIFICATION = 7;
+    static final int BLACKLISTED_MESSAGE_NOTIFICATION = 8;
 
     static final int NOTIFICATION_ID_OFFSET = 50;
 
@@ -91,6 +99,23 @@
 
     public StatusBarHelper statusBarHelper;
 
+    // used to track blacklisted calls and messages
+    private static class BlacklistedItemInfo {
+        String number;
+        long date;
+        int matchType;
+
+        BlacklistedItemInfo(String number, long date, int matchType) {
+            this.number = number;
+            this.date = date;
+            this.matchType = matchType;
+        }
+    };
+    private ArrayList<BlacklistedItemInfo> mBlacklistedCalls =
+            new ArrayList<BlacklistedItemInfo>();
+    private ArrayList<BlacklistedItemInfo> mBlacklistedMessages =
+            new ArrayList<BlacklistedItemInfo>();
+
     // used to track the notification of selected network unavailable
     private boolean mSelectedUnavailableNotify = false;
 
@@ -235,6 +260,167 @@
         PhoneLookup._ID
     };
 
+    private static final RelativeSizeSpan TIME_SPAN = new RelativeSizeSpan(0.7f);
+
+    private CharSequence formatSingleCallLine(String caller, long date) {
+        int flags = DateUtils.FORMAT_SHOW_TIME;
+        if (!DateUtils.isToday(date)) {
+            flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
+        }
+
+        SpannableStringBuilder lineBuilder = new SpannableStringBuilder();
+        lineBuilder.append(caller);
+        lineBuilder.append("  ");
+
+        int timeIndex = lineBuilder.length();
+        lineBuilder.append(DateUtils.formatDateTime(mContext, date, flags));
+        lineBuilder.setSpan(TIME_SPAN, timeIndex, lineBuilder.length(), 0);
+
+        return lineBuilder;
+    }
+
+    /* package */ void notifyBlacklistedCall(String number, long date, int matchType) {
+        notifyBlacklistedItem(number, date, matchType, BLACKLISTED_CALL_NOTIFICATION);
+    }
+
+    /* package */ void notifyBlacklistedMessage(String number, long date, int matchType) {
+        notifyBlacklistedItem(number, date, matchType, BLACKLISTED_MESSAGE_NOTIFICATION);
+    }
+
+    private void notifyBlacklistedItem(String number, long date,
+            int matchType, int notificationId) {
+        if (!BlacklistUtils.isBlacklistNotifyEnabled(mContext)) {
+            return;
+        }
+
+        if (VDBG) {
+            log("notifyBlacklistedItem(). number: " + number
+                + ", match type: " + matchType + ", date: " + date + ", type: " + notificationId);
+        }
+
+        ArrayList<BlacklistedItemInfo> items = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                ? mBlacklistedCalls : mBlacklistedMessages;
+        PendingIntent clearIntent = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                ? createClearBlacklistedCallsIntent() : createClearBlacklistedMessagesIntent();
+        int iconDrawableResId = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                ? R.drawable.ic_block_contact_holo_dark : R.drawable.ic_block_message_holo_dark;
+
+        // Keep track of the call/message, keeping list sorted from newest to oldest
+        items.add(0, new BlacklistedItemInfo(number, date, matchType));
+
+        // Get the intent to open Blacklist settings if user taps on content ready
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName("com.android.settings",
+                "com.android.settings.Settings$BlacklistSettingsActivity");
+        PendingIntent blSettingsIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+
+        // Start building the notification
+        Notification.Builder builder = new Notification.Builder(mContext);
+        builder.setSmallIcon(iconDrawableResId)
+                .setContentIntent(blSettingsIntent)
+                .setAutoCancel(true)
+                .setContentTitle(mContext.getString(R.string.blacklist_title))
+                .setWhen(date)
+                .setDeleteIntent(clearIntent);
+
+        // Add the 'Remove block' notification action only for MATCH_LIST items since
+        // MATCH_REGEX and MATCH_PRIVATE items does not have an associated specific number
+        // to unblock, and MATCH_UNKNOWN unblock for a single number does not make sense.
+        boolean addUnblockAction = true;
+
+        if (items.size() == 1) {
+            int messageResId;
+
+            switch (matchType) {
+                case BlacklistUtils.MATCH_PRIVATE:
+                    messageResId = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                            ? R.string.blacklist_call_notification_private_number
+                            : R.string.blacklist_message_notification_private_number;
+                    break;
+                case BlacklistUtils.MATCH_UNKNOWN:
+                    messageResId = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                            ? R.string.blacklist_call_notification_unknown_number
+                            : R.string.blacklist_message_notification_unknown_number;
+                    break;
+                default:
+                    messageResId = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                            ? R.string.blacklist_call_notification
+                            : R.string.blacklist_message_notification;
+                    break;
+            }
+            builder.setContentText(mContext.getString(messageResId, number));
+
+            if (matchType != BlacklistUtils.MATCH_LIST) {
+                addUnblockAction = false;
+            }
+        } else {
+            int messageResId = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                    ? R.string.blacklist_call_notification_multiple
+                    : R.string.blacklist_message_notification_multiple;
+            String message = mContext.getString(messageResId, items.size());
+
+            builder.setContentText(message);
+            builder.setNumber(items.size());
+
+            Notification.InboxStyle style = new Notification.InboxStyle(builder);
+
+            for (BlacklistedItemInfo info : items) {
+                // Takes care of displaying "Private" instead of an empty string
+                String numberString = TextUtils.isEmpty(info.number)
+                        ? mContext.getString(R.string.blacklist_notification_list_private)
+                        : info.number;
+                style.addLine(formatSingleCallLine(numberString, info.date));
+
+                if (!TextUtils.equals(number, info.number)) {
+                    addUnblockAction = false;
+                } else if (info.matchType != BlacklistUtils.MATCH_LIST) {
+                    addUnblockAction = false;
+                }
+            }
+            style.setBigContentTitle(message);
+            style.setSummaryText(" ");
+            builder.setStyle(style);
+        }
+
+        if (addUnblockAction) {
+            int actionDrawableResId = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                    ? R.drawable.ic_unblock_contact_holo_dark
+                    : R.drawable.ic_unblock_message_holo_dark;
+            int unblockType = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                    ? BlacklistUtils.BLOCK_CALLS : BlacklistUtils.BLOCK_MESSAGES;
+            PendingIntent action = PhoneGlobals.getUnblockNumberFromNotificationPendingIntent(
+                    mContext, number, unblockType);
+
+            builder.addAction(actionDrawableResId,
+                    mContext.getString(R.string.unblock_number), action);
+        }
+
+        mNotificationManager.notify(notificationId, builder.getNotification());
+    }
+
+    private PendingIntent createClearBlacklistedCallsIntent() {
+        Intent intent = new Intent(mContext, ClearBlacklistService.class);
+        intent.setAction(ClearBlacklistService.ACTION_CLEAR_BLACKLISTED_CALLS);
+        return PendingIntent.getService(mContext, 0, intent, 0);
+    }
+
+    private PendingIntent createClearBlacklistedMessagesIntent() {
+        Intent intent = new Intent(mContext, ClearBlacklistService.class);
+        intent.setAction(ClearBlacklistService.ACTION_CLEAR_BLACKLISTED_MESSAGES);
+        return PendingIntent.getService(mContext, 0, intent, 0);
+    }
+
+    void cancelBlacklistedNotification(int type) {
+        if ((type & BlacklistUtils.BLOCK_CALLS) != 0) {
+            mBlacklistedCalls.clear();
+            mNotificationManager.cancel(BLACKLISTED_CALL_NOTIFICATION);
+        }
+        if ((type & BlacklistUtils.BLOCK_MESSAGES) != 0) {
+            mBlacklistedMessages.clear();
+            mNotificationManager.cancel(BLACKLISTED_MESSAGE_NOTIFICATION);
+        }
+    }
+
     /**
      * Updates the message waiting indicator (voicemail) notification.
      *
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 4982217..2180047 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -64,6 +64,7 @@
 import com.android.internal.telephony.PhoneProxy;
 import com.android.internal.telephony.TelephonyCapabilities;
 import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.util.BlacklistUtils;
 import com.android.phone.common.CallLogAsync;
 import com.android.server.sip.SipService;
 
@@ -213,6 +214,12 @@
     public OtaUtils.CdmaOtaScreenState cdmaOtaScreenState;
     public OtaUtils.CdmaOtaInCallScreenUiState cdmaOtaInCallScreenUiState;
 
+    // For adding to Blacklist from call log
+    private static final String REMOVE_BLACKLIST = "com.android.phone.REMOVE_BLACKLIST";
+    private static final String EXTRA_NUMBER = "number";
+    private static final String EXTRA_TYPE = "type";
+    private static final String EXTRA_FROM_NOTIFICATION = "fromNotification";
+
     /**
      * Set the restore mute state flag. Used when we are setting the mute state
      * OUTSIDE of user interaction {@link PhoneUtils#startNewCall(Phone)}
@@ -414,6 +421,9 @@
 
             phoneMgr = PhoneInterfaceManager.init(this, phone);
 
+            // Convert old blacklist to new format
+            Blacklist.migrateOldDataIfPresent(this);
+
             // Create the CallNotifer singleton, which handles
             // asynchronous events from the telephony layer (like
             // launching the incoming-call UI when an incoming call comes
@@ -444,6 +454,7 @@
             intentFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
             intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
             intentFilter.addAction(TelephonyIntents.ACTION_MANAGED_ROAMING_IND);
+            intentFilter.addAction(REMOVE_BLACKLIST);
             registerReceiver(mReceiver, intentFilter);
 
             //set the default values for the preferences in the phone.
@@ -549,6 +560,15 @@
         return PendingIntent.getBroadcast(context, 0, intent, 0);
     }
 
+    /* package */ static PendingIntent getUnblockNumberFromNotificationPendingIntent(
+            Context context, String number, int type) {
+        Intent intent = new Intent(REMOVE_BLACKLIST);
+        intent.putExtra(EXTRA_NUMBER, number);
+        intent.putExtra(EXTRA_FROM_NOTIFICATION, true);
+        intent.putExtra(EXTRA_TYPE, type);
+        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+
     boolean isSimPinEnabled() {
         return mIsSimPinEnabled;
     }
@@ -901,6 +921,14 @@
                 createIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 createIntent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subscription);
                 context.startActivity(createIntent);
+            } else if (action.equals(REMOVE_BLACKLIST)) {
+                if (intent.getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) {
+                    // Dismiss the notification that brought us here
+                    int blacklistType = intent.getIntExtra(EXTRA_TYPE, 0);
+                    notificationMgr.cancelBlacklistedNotification(blacklistType);
+                    BlacklistUtils.addOrUpdate(context, intent.getStringExtra(EXTRA_NUMBER),
+                            0, blacklistType);
+                }
             }
         }
     }
diff --git a/src/com/android/phone/RejectedSmsReceiver.java b/src/com/android/phone/RejectedSmsReceiver.java
new file mode 100644
index 0000000..076f095
--- /dev/null
+++ b/src/com/android/phone/RejectedSmsReceiver.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod 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.phone;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class RejectedSmsReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (!intent.getBooleanExtra("blacklisted", false)) {
+            return;
+        }
+
+        String sender = intent.getStringExtra("sender");
+        long timestamp = intent.getLongExtra("timestamp", 0);
+        int matchType = intent.getIntExtra("blacklistMatchType", -1);
+
+        PhoneGlobals.getInstance().notificationMgr.notifyBlacklistedMessage(
+                sender, timestamp, matchType);
+    }
+}