aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDvTonder <david.vantonder@gmail.com>2014-11-14 09:11:57 -0800
committerSteve Kondik <steve@cyngn.com>2016-09-02 04:59:34 -0700
commitf9d5532f1bece3f5031937e30f87aa77049425a7 (patch)
treeb9bd18df3ddd7c62e26c4d4147a47dacf6c679f0
parent1a28ef41b28c8cad1644e4f3a3b93a24598f7733 (diff)
downloadandroid_frameworks_opt_telephony-staging/cm-14.0-caf.tar.gz
android_frameworks_opt_telephony-staging/cm-14.0-caf.tar.bz2
android_frameworks_opt_telephony-staging/cm-14.0-caf.zip
Telephony: Generic Blacklist support (5 of 5)staging/cm-14.0-caf
Change-Id: Ida51f9b1aa1f11b6179f623ede89c314d3f92f65 Move message blacklist to framework (1/3). Change-Id: I321e3a3c40dd49772978258e8a27208868a22332 Telephony : Ensure private numbers are blocked if block unknown numbers is enabled When a private number comes through, its value is either null or empty. Make sure that if the user has blocking of unknown numbers enabled, that its blocked Change-Id: Ica629215f584572c701fd10464d8828c3f099694 Blacklist : Add helper methods issue-id: CYNGNOS-980 Change-Id: I806b70da553adad9e9e03a39c63049ae2c73d7fa Store phone blacklist in content provider (1/4) Change-Id: I9dafdb3e10bd2a71695c2a02cc483c594d86c543
-rw-r--r--src/java/android/provider/Telephony.java93
-rw-r--r--src/java/com/android/internal/telephony/InboundSmsHandler.java64
-rw-r--r--src/java/com/android/internal/telephony/util/BlacklistUtils.java262
3 files changed, 414 insertions, 5 deletions
diff --git a/src/java/android/provider/Telephony.java b/src/java/android/provider/Telephony.java
index a7e779d6c..bcff354e5 100644
--- a/src/java/android/provider/Telephony.java
+++ b/src/java/android/provider/Telephony.java
@@ -832,6 +832,27 @@ public final class Telephony {
public static final int RESULT_SMS_DUPLICATED = 5;
/**
+ * Used internally: The sender of the SMS was blacklisted
+ * for not being listed in the contact list
+ * @hide
+ */
+ public static final int RESULT_SMS_BLACKLISTED_UNKNOWN = 6;
+
+ /**
+ * Used internally: The sender of the SMS was blacklisted
+ * for being listed in the blacklist
+ * @hide
+ */
+ public static final int RESULT_SMS_BLACKLISTED_LIST = 7;
+
+ /**
+ * Used internally: The sender of the SMS was blacklisted
+ * for matching a blacklist regex entry
+ * @hide
+ */
+ public static final int RESULT_SMS_BLACKLISTED_REGEX = 8;
+
+ /**
* Activity action: Ask the user to change the default
* SMS application. This will show a dialog that asks the
* user whether they want to replace the current default
@@ -3023,4 +3044,76 @@ public final class Telephony {
CMAS_CERTAINTY
};
}
+
+ /**
+ * Contains phone numbers that are blacklisted
+ * for phone and/or message purposes.
+ * @hide
+ */
+ public static final class Blacklist implements BaseColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI =
+
+ Uri.parse("content://blacklist");
+
+ /**
+ * The content:// style URL for filtering this table by number.
+ * When using this, make sure the number is correctly encoded
+ * when appended to the Uri.
+ */
+ public static final Uri CONTENT_FILTER_BYNUMBER_URI =
+ Uri.parse("content://blacklist/bynumber");
+
+ /**
+ * The content:// style URL for filtering this table on phone numbers
+ */
+ public static final Uri CONTENT_PHONE_URI =
+ Uri.parse("content://blacklist/phone");
+
+ /**
+ * The content:// style URL for filtering this table on message numbers
+ */
+ public static final Uri CONTENT_MESSAGE_URI =
+ Uri.parse("content://blacklist/message");
+
+
+ /**
+ * Query parameter used to match numbers by regular-expression like
+ * matching. Supported are the '*' and the '.' operators.
+ * <p>
+ * TYPE: boolean
+ */
+ public static final String REGEX_KEY = "regex";
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "number ASC";
+
+ /**
+ * The phone number as the user entered it.
+ * <P>Type: TEXT</P>
+ */
+ public static final String NUMBER = "number";
+
+ /**
+ * Whether the number contains a regular expression pattern
+ * <P>Type: BOOLEAN (read only)</P>
+ */
+ public static final String IS_REGEX = "is_regex";
+
+ /**
+ * Blacklisting mode for phone calls
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String PHONE_MODE = "phone";
+
+ /**
+ * Blacklisting mode for messages
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String MESSAGE_MODE = "message";
+ }
}
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index dde1c4d51..50bc6b37c 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -72,6 +72,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.uicc.UiccCard;
import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.util.BlacklistUtils;
import com.android.internal.util.HexDump;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -558,20 +559,40 @@ public abstract class InboundSmsHandler extends StateMachine {
return;
}
- int result;
+ int result, blacklistMatchType = -1;
+ SmsMessage sms = null;
+
try {
- SmsMessage sms = (SmsMessage) ar.result;
+ sms = (SmsMessage) ar.result;
result = dispatchMessage(sms.mWrappedSmsMessage);
} catch (RuntimeException ex) {
loge("Exception dispatching message", ex);
result = Intents.RESULT_SMS_GENERIC_ERROR;
}
+ // Translate (internal) blacklist check results to
+ // RESULT_SMS_HANDLED + match type
+ switch (result) {
+ case Intents.RESULT_SMS_BLACKLISTED_UNKNOWN:
+ blacklistMatchType = BlacklistUtils.MATCH_UNKNOWN;
+ result = Intents.RESULT_SMS_HANDLED;
+ break;
+ case Intents.RESULT_SMS_BLACKLISTED_LIST:
+ blacklistMatchType = BlacklistUtils.MATCH_LIST;
+ result = Intents.RESULT_SMS_HANDLED;
+ break;
+ case Intents.RESULT_SMS_BLACKLISTED_REGEX:
+ blacklistMatchType = BlacklistUtils.MATCH_REGEX;
+ result = Intents.RESULT_SMS_HANDLED;
+ break;
+ }
+
+
// RESULT_OK means that the SMS will be acknowledged by special handling,
// e.g. for SMS-PP data download. Any other result, we should ack here.
if (result != Activity.RESULT_OK) {
boolean handled = (result == Intents.RESULT_SMS_HANDLED);
- notifyAndAcknowledgeLastIncomingSms(handled, result, null);
+ notifyAndAcknowledgeLastIncomingSms(handled, result, blacklistMatchType, sms, null);
}
}
@@ -678,14 +699,26 @@ public abstract class InboundSmsHandler extends StateMachine {
* and send an acknowledge message to the network.
* @param success indicates that last message was successfully received.
* @param result result code indicating any error
+ * @param blacklistMatchType blacklist type if the message was blacklisted,
+ * -1 if it wasn't blacklisted
+ * @param sms incoming SMS
* @param response callback message sent when operation completes.
*/
private void notifyAndAcknowledgeLastIncomingSms(boolean success,
- int result, Message response) {
- if (!success) {
+ int result, int blacklistMatchType, SmsMessage sms, Message response) {
+ if (!success || blacklistMatchType >= 0) {
// broadcast SMS_REJECTED_ACTION intent
Intent intent = new Intent(Intents.SMS_REJECTED_ACTION);
intent.putExtra("result", result);
+ intent.putExtra("blacklisted", blacklistMatchType >= 0);
+ if (blacklistMatchType >= 0) {
+ intent.putExtra("blacklistMatchType", blacklistMatchType);
+ }
+ if (sms != null) {
+ intent.putExtra("sender", sms.getOriginatingAddress());
+ intent.putExtra("timestamp", sms.getTimestampMillis());
+ }
+ if (DBG) log("notifyAndAcknowledgeLastIncomingSms(): reject intent= " + intent);
mContext.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
}
acknowledgeLastIncomingSms(success, result, response);
@@ -707,6 +740,11 @@ public abstract class InboundSmsHandler extends StateMachine {
* @return {@link Intents#RESULT_SMS_HANDLED} if the message was accepted, or an error status
*/
protected int dispatchNormalMessage(SmsMessageBase sms) {
+ int blacklistResult = checkIfBlacklisted(sms);
+ if (blacklistResult != Intents.RESULT_SMS_HANDLED) {
+ return blacklistResult;
+ }
+
SmsHeader smsHeader = sms.getUserDataHeader();
InboundSmsTracker tracker;
@@ -742,6 +780,22 @@ public abstract class InboundSmsHandler extends StateMachine {
tracker.getDestPort() == -1 /* de-dup if text message */);
}
+ private int checkIfBlacklisted(SmsMessageBase sms) {
+ int result = BlacklistUtils.isListed(mContext,
+ sms.getOriginatingAddress(), BlacklistUtils.BLOCK_MESSAGES);
+
+ switch (result) {
+ case BlacklistUtils.MATCH_UNKNOWN:
+ return Intents.RESULT_SMS_BLACKLISTED_UNKNOWN;
+ case BlacklistUtils.MATCH_LIST:
+ return Intents.RESULT_SMS_BLACKLISTED_LIST;
+ case BlacklistUtils.MATCH_REGEX:
+ return Intents.RESULT_SMS_BLACKLISTED_REGEX;
+ }
+
+ return Intents.RESULT_SMS_HANDLED;
+ }
+
/**
* Helper to add the tracker to the raw table and then send a message to broadcast it, if
* successful. Returns the SMS intent status to return to the SMSC.
diff --git a/src/java/com/android/internal/telephony/util/BlacklistUtils.java b/src/java/com/android/internal/telephony/util/BlacklistUtils.java
new file mode 100644
index 000000000..f264f8d50
--- /dev/null
+++ b/src/java/com/android/internal/telephony/util/BlacklistUtils.java
@@ -0,0 +1,262 @@
+/*
+ * 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.internal.telephony.util;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Telephony.Blacklist;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.Locale;
+
+import com.android.internal.telephony.CallerInfo;
+
+/**
+ * Blacklist Utility Class
+ * @hide
+ */
+public class BlacklistUtils {
+ private static final String TAG = "BlacklistUtils";
+ private static final boolean DEBUG = false;
+
+ // Blacklist matching type
+ public final static int MATCH_NONE = 0;
+ public final static int MATCH_PRIVATE = 1;
+ public final static int MATCH_UNKNOWN = 2;
+ public final static int MATCH_LIST = 3;
+ public final static int MATCH_REGEX = 4;
+
+ public final static int BLOCK_CALLS =
+ Settings.System.BLACKLIST_BLOCK << Settings.System.BLACKLIST_PHONE_SHIFT;
+ public final static int BLOCK_MESSAGES =
+ Settings.System.BLACKLIST_BLOCK << Settings.System.BLACKLIST_MESSAGE_SHIFT;
+
+ public static boolean addOrUpdate(Context context, String number, int flags, int valid) {
+ ContentValues cv = new ContentValues();
+
+ if ((valid & BLOCK_CALLS) != 0) {
+ cv.put(Blacklist.PHONE_MODE, (flags & BLOCK_CALLS) != 0 ? 1 : 0);
+ }
+ if ((valid & BLOCK_MESSAGES) != 0) {
+ cv.put(Blacklist.MESSAGE_MODE, (flags & BLOCK_MESSAGES) != 0 ? 1 : 0);
+ }
+
+ Uri uri = Uri.withAppendedPath(Blacklist.CONTENT_FILTER_BYNUMBER_URI, number);
+ int count = context.getContentResolver().update(uri, cv, null, null);
+
+ return count > 0;
+ }
+
+ /**
+ * Check if the number is in the blacklist
+ * @param number: Number to check
+ * @return one of: MATCH_NONE, MATCH_PRIVATE, MATCH_UNKNOWN, MATCH_LIST or MATCH_REGEX
+ */
+ public static int isListed(Context context, String number, int mode) {
+ if (!isBlacklistEnabled(context)) {
+ return MATCH_NONE;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Checking number " + number + " against the Blacklist for mode " + mode);
+ }
+
+ final String type;
+
+ if (mode == BLOCK_CALLS) {
+ if (DEBUG) Log.d(TAG, "Checking if an incoming call should be blocked");
+ type = Blacklist.PHONE_MODE;
+ } else if (mode == BLOCK_MESSAGES) {
+ if (DEBUG) Log.d(TAG, "Checking if an incoming message should be blocked");
+ type = Blacklist.MESSAGE_MODE;
+ } else {
+ Log.e(TAG, "Invalid mode " + mode);
+ return MATCH_NONE;
+ }
+
+ if (isBlacklistUnknownNumberEnabled(context, mode)) {
+ CallerInfo ci = CallerInfo.getCallerInfo(context, number);
+ if (ci == null || !ci.contactExists) {
+ if (DEBUG) Log.d(TAG, "Blacklist matched due to unknown number");
+ return MATCH_UNKNOWN;
+ }
+ }
+
+ // Private and unknown number matching
+ if (TextUtils.isEmpty(number)) {
+ if (isBlacklistPrivateNumberEnabled(context, mode)) {
+ if (DEBUG) Log.d(TAG, "Blacklist matched due to private number");
+ return MATCH_PRIVATE;
+ }
+ return MATCH_NONE;
+ }
+
+ Uri.Builder builder = Blacklist.CONTENT_FILTER_BYNUMBER_URI.buildUpon();
+ builder.appendPath(number);
+ if (isBlacklistRegexEnabled(context)) {
+ builder.appendQueryParameter(Blacklist.REGEX_KEY, "1");
+ }
+
+ int result = MATCH_NONE;
+ Cursor c = context.getContentResolver().query(builder.build(),
+ new String[]{Blacklist.IS_REGEX, type}, null, null, null);
+
+ if (c != null) {
+ if (DEBUG) Log.d(TAG, "Blacklist query successful, " + c.getCount() + " matches");
+ int regexColumnIndex = c.getColumnIndexOrThrow(Blacklist.IS_REGEX);
+ int modeColumnIndex = c.getColumnIndexOrThrow(type);
+ boolean whitelisted = false;
+
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ boolean isRegex = c.getInt(regexColumnIndex) != 0;
+ boolean blocked = c.getInt(modeColumnIndex) != 0;
+
+ if (!isRegex) {
+ whitelisted = !blocked;
+ result = MATCH_LIST;
+ if (blocked) {
+ break;
+ }
+ } else if (blocked) {
+ result = MATCH_REGEX;
+ }
+ }
+ if (whitelisted) {
+ result = MATCH_NONE;
+ }
+ c.close();
+ }
+
+ if (DEBUG) Log.d(TAG, "Blacklist check result for number " + number + " is " + result);
+ return result;
+ }
+
+ public static boolean isBlacklistEnabled(Context context) {
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.PHONE_BLACKLIST_ENABLED, 1,
+ UserHandle.USER_CURRENT_OR_SELF) != 0;
+ }
+
+ public static boolean isBlacklistNotifyEnabled(Context context) {
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.PHONE_BLACKLIST_NOTIFY_ENABLED, 1,
+ UserHandle.USER_CURRENT_OR_SELF) != 0;
+ }
+
+ public static boolean isBlacklistPrivateNumberEnabled(Context context, int mode) {
+ return (Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.PHONE_BLACKLIST_PRIVATE_NUMBER_MODE, 0,
+ UserHandle.USER_CURRENT_OR_SELF) & mode) != 0;
+ }
+
+ public static boolean isBlacklistUnknownNumberEnabled(Context context, int mode) {
+ return (Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.PHONE_BLACKLIST_UNKNOWN_NUMBER_MODE, 0,
+ UserHandle.USER_CURRENT_OR_SELF) & mode) != 0;
+ }
+
+ public static boolean isBlacklistRegexEnabled(Context context) {
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.PHONE_BLACKLIST_REGEX_ENABLED, 0,
+ UserHandle.USER_CURRENT_OR_SELF) != 0;
+ }
+
+ public static Pair<String, Boolean> isValidBlacklistInput(Context context, String number) {
+ final Pair<String, Boolean> normalizeResult = BlacklistUtils.normalizeNumber(
+ context, number);
+ final String normalizedNumber = normalizeResult.first;
+ boolean isRegex = normalizedNumber.indexOf('%') >= 0 ||
+ normalizedNumber.indexOf('_') >= 0;
+ // For non-regex numbers, apply additional validity checking if
+ // they didn't pass e164 normalization
+ if (!isRegex && !normalizeResult.second && !BlacklistUtils.isValidPhoneNumber(number)) {
+ // number was invalid
+ return new Pair<String, Boolean>(normalizedNumber, false);
+ }
+ return new Pair<String, Boolean>(normalizedNumber, true);
+ }
+
+ /**
+ * Normalizes the passed in number and tries to format it according to E164.
+ * Returns a pair of
+ * - normalized number
+ * - boolean indicating whether the number is a E164 number or not
+ */
+ public static Pair<String, Boolean> normalizeNumber(Context context, String number) {
+ int len = number.length();
+ StringBuilder ret = new StringBuilder(len);
+
+ for (int i = 0; i < len; i++) {
+ char c = number.charAt(i);
+ // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
+ int digit = Character.digit(c, 10);
+ if (digit != -1) {
+ ret.append(digit);
+ } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+ String actualNumber = PhoneNumberUtils.convertKeypadLettersToDigits(number);
+ return normalizeNumber(context, actualNumber);
+ } else if (i == 0 && c == '+') {
+ ret.append(c);
+ } else if (c == '*') {
+ // replace regex match-multiple character by SQL equivalent
+ ret.append('%');
+ } else if (c == '.') {
+ // replace regex-match-single character by SQL equivalent
+ ret.append('_');
+ }
+ }
+
+ String normalizedNumber = ret.toString();
+ String e164Number = toE164Number(context, normalizedNumber);
+ return Pair.create(e164Number != null ? e164Number : normalizedNumber, e164Number != null);
+ }
+
+ public static String toE164Number(Context context, String src) {
+ // Try to retrieve the current ISO Country code
+ TelephonyManager tm = (TelephonyManager)
+ context.getSystemService(Context.TELEPHONY_SERVICE);
+ String countryCode = tm.getSimCountryIso();
+ Locale numberLocale = TextUtils.isEmpty(countryCode)
+ ? context.getResources().getConfiguration().locale
+ : new Locale("", countryCode);
+
+ return PhoneNumberUtils.formatNumberToE164(src, numberLocale.getCountry());
+ }
+
+ public static boolean isValidPhoneNumber(String address) {
+ for (int i = 0, count = address.length(); i < count; i++) {
+ if (!PhoneNumberUtils.isISODigit(address.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isInputRegex(String input) {
+ return input.indexOf('%') >= 0 ||
+ input.indexOf('_') >= 0;
+ }
+}