Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f9d5532f authored by DvTonder's avatar DvTonder Committed by Steve Kondik
Browse files

Telephony: Generic Blacklist support (5 of 5)

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
parent 1a28ef41
Loading
Loading
Loading
Loading
+93 −0
Original line number Diff line number Diff line
@@ -831,6 +831,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
@@ -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";
    }
}
+59 −5
Original line number Diff line number Diff line
@@ -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.
+262 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}