am 665935c1: Merge "Enable SMS short code patterns to be updated from secure settings." into jb-dev
* commit '665935c10ad281721c495621fab7561f2a28842e': Enable SMS short code patterns to be updated from secure settings.
This commit is contained in:
@@ -4265,6 +4265,13 @@ public final class Settings {
|
|||||||
public static final String CONTACTS_PREAUTH_URI_EXPIRATION =
|
public static final String CONTACTS_PREAUTH_URI_EXPIRATION =
|
||||||
"contacts_preauth_uri_expiration";
|
"contacts_preauth_uri_expiration";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefix for SMS short code regex patterns (country code is appended).
|
||||||
|
* @see com.android.internal.telephony.SmsUsageMonitor
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final String SMS_SHORT_CODES_PREFIX = "sms_short_codes_";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This are the settings to be backed up.
|
* This are the settings to be backed up.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -19,15 +19,21 @@ package com.android.internal.telephony;
|
|||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.XmlResourceParser;
|
import android.content.res.XmlResourceParser;
|
||||||
|
import android.database.ContentObserver;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.telephony.PhoneNumberUtils;
|
import android.telephony.PhoneNumberUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.internal.util.XmlUtils;
|
import com.android.internal.util.XmlUtils;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
import org.xmlpull.v1.XmlPullParserFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -45,6 +51,8 @@ import java.util.regex.Pattern;
|
|||||||
*/
|
*/
|
||||||
public class SmsUsageMonitor {
|
public class SmsUsageMonitor {
|
||||||
private static final String TAG = "SmsUsageMonitor";
|
private static final String TAG = "SmsUsageMonitor";
|
||||||
|
private static final boolean DBG = true;
|
||||||
|
private static final boolean VDBG = false;
|
||||||
|
|
||||||
/** Default checking period for SMS sent without user permission. */
|
/** Default checking period for SMS sent without user permission. */
|
||||||
private static final int DEFAULT_SMS_CHECK_PERIOD = 1800000; // 30 minutes
|
private static final int DEFAULT_SMS_CHECK_PERIOD = 1800000; // 30 minutes
|
||||||
@@ -69,6 +77,7 @@ public class SmsUsageMonitor {
|
|||||||
|
|
||||||
private final int mCheckPeriod;
|
private final int mCheckPeriod;
|
||||||
private final int mMaxAllowed;
|
private final int mMaxAllowed;
|
||||||
|
|
||||||
private final HashMap<String, ArrayList<Long>> mSmsStamp =
|
private final HashMap<String, ArrayList<Long>> mSmsStamp =
|
||||||
new HashMap<String, ArrayList<Long>>();
|
new HashMap<String, ArrayList<Long>>();
|
||||||
|
|
||||||
@@ -87,6 +96,12 @@ public class SmsUsageMonitor {
|
|||||||
/** Cached short code pattern matcher for {@link #mCurrentCountry}. */
|
/** Cached short code pattern matcher for {@link #mCurrentCountry}. */
|
||||||
private ShortCodePatternMatcher mCurrentPatternMatcher;
|
private ShortCodePatternMatcher mCurrentPatternMatcher;
|
||||||
|
|
||||||
|
/** Cached short code regex patterns from secure settings for {@link #mCurrentCountry}. */
|
||||||
|
private String mSettingsShortCodePatterns;
|
||||||
|
|
||||||
|
/** Handler for responding to content observer updates. */
|
||||||
|
private final SettingsObserverHandler mSettingsObserverHandler;
|
||||||
|
|
||||||
/** XML tag for root element. */
|
/** XML tag for root element. */
|
||||||
private static final String TAG_SHORTCODES = "shortcodes";
|
private static final String TAG_SHORTCODES = "shortcodes";
|
||||||
|
|
||||||
@@ -148,6 +163,74 @@ public class SmsUsageMonitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe the secure setting for updated regex patterns.
|
||||||
|
*/
|
||||||
|
private static class SettingsObserver extends ContentObserver {
|
||||||
|
private final int mWhat;
|
||||||
|
private final Handler mHandler;
|
||||||
|
|
||||||
|
SettingsObserver(Handler handler, int what) {
|
||||||
|
super(handler);
|
||||||
|
mHandler = handler;
|
||||||
|
mWhat = what;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange) {
|
||||||
|
mHandler.obtainMessage(mWhat).sendToTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to update regex patterns when secure setting for the current country is updated.
|
||||||
|
*/
|
||||||
|
private class SettingsObserverHandler extends Handler {
|
||||||
|
/** Current content observer, or null. */
|
||||||
|
SettingsObserver mSettingsObserver;
|
||||||
|
|
||||||
|
/** Current country code to watch for settings updates. */
|
||||||
|
private String mCountryIso;
|
||||||
|
|
||||||
|
/** Request to start observing a secure setting. */
|
||||||
|
static final int OBSERVE_SETTING = 1;
|
||||||
|
|
||||||
|
/** Handler event for updated secure settings. */
|
||||||
|
static final int SECURE_SETTINGS_CHANGED = 2;
|
||||||
|
|
||||||
|
/** Send a message to this handler requesting to observe the setting for a new country. */
|
||||||
|
void observeSettingForCountry(String countryIso) {
|
||||||
|
obtainMessage(OBSERVE_SETTING, countryIso).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case OBSERVE_SETTING:
|
||||||
|
if (msg.obj != null && msg.obj instanceof String) {
|
||||||
|
mCountryIso = (String) msg.obj;
|
||||||
|
String settingName = getSettingNameForCountry(mCountryIso);
|
||||||
|
ContentResolver resolver = mContext.getContentResolver();
|
||||||
|
|
||||||
|
if (mSettingsObserver != null) {
|
||||||
|
if (VDBG) log("Unregistering old content observer");
|
||||||
|
resolver.unregisterContentObserver(mSettingsObserver);
|
||||||
|
}
|
||||||
|
|
||||||
|
mSettingsObserver = new SettingsObserver(this, SECURE_SETTINGS_CHANGED);
|
||||||
|
resolver.registerContentObserver(
|
||||||
|
Settings.Secure.getUriFor(settingName), false, mSettingsObserver);
|
||||||
|
if (VDBG) log("Registered content observer for " + settingName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SECURE_SETTINGS_CHANGED:
|
||||||
|
loadPatternsFromSettings(mCountryIso);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create SMS usage monitor.
|
* Create SMS usage monitor.
|
||||||
* @param context the context to use to load resources and get TelephonyManager service
|
* @param context the context to use to load resources and get TelephonyManager service
|
||||||
@@ -164,6 +247,8 @@ public class SmsUsageMonitor {
|
|||||||
Settings.Secure.SMS_OUTGOING_CHECK_INTERVAL_MS,
|
Settings.Secure.SMS_OUTGOING_CHECK_INTERVAL_MS,
|
||||||
DEFAULT_SMS_CHECK_PERIOD);
|
DEFAULT_SMS_CHECK_PERIOD);
|
||||||
|
|
||||||
|
mSettingsObserverHandler = new SettingsObserverHandler();
|
||||||
|
|
||||||
// system MMS app is always allowed to send to short codes
|
// system MMS app is always allowed to send to short codes
|
||||||
mApprovedShortCodeSenders.add("com.android.mms");
|
mApprovedShortCodeSenders.add("com.android.mms");
|
||||||
}
|
}
|
||||||
@@ -178,27 +263,7 @@ public class SmsUsageMonitor {
|
|||||||
XmlResourceParser parser = mContext.getResources().getXml(id);
|
XmlResourceParser parser = mContext.getResources().getXml(id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
XmlUtils.beginDocument(parser, TAG_SHORTCODES);
|
return getPatternMatcher(country, parser);
|
||||||
|
|
||||||
while (true) {
|
|
||||||
XmlUtils.nextElement(parser);
|
|
||||||
|
|
||||||
String element = parser.getName();
|
|
||||||
if (element == null) break;
|
|
||||||
|
|
||||||
if (element.equals(TAG_SHORTCODE)) {
|
|
||||||
String currentCountry = parser.getAttributeValue(null, ATTR_COUNTRY);
|
|
||||||
if (country.equals(currentCountry)) {
|
|
||||||
String pattern = parser.getAttributeValue(null, ATTR_PATTERN);
|
|
||||||
String premium = parser.getAttributeValue(null, ATTR_PREMIUM);
|
|
||||||
String free = parser.getAttributeValue(null, ATTR_FREE);
|
|
||||||
String standard = parser.getAttributeValue(null, ATTR_STANDARD);
|
|
||||||
return new ShortCodePatternMatcher(pattern, premium, free, standard);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Error: skipping unknown XML tag " + element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (XmlPullParserException e) {
|
} catch (XmlPullParserException e) {
|
||||||
Log.e(TAG, "XML parser exception reading short code pattern resource", e);
|
Log.e(TAG, "XML parser exception reading short code pattern resource", e);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -209,6 +274,60 @@ public class SmsUsageMonitor {
|
|||||||
return null; // country not found
|
return null; // country not found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a pattern matcher object for the specified country from a secure settings string.
|
||||||
|
* @return a {@link ShortCodePatternMatcher} for the specified country, or null if not found
|
||||||
|
*/
|
||||||
|
private static ShortCodePatternMatcher getPatternMatcher(String country, String settingsPattern) {
|
||||||
|
// embed pattern tag into an XML document.
|
||||||
|
String document = "<shortcodes>" + settingsPattern + "</shortcodes>";
|
||||||
|
if (VDBG) log("loading updated patterns from: " + document);
|
||||||
|
|
||||||
|
try {
|
||||||
|
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||||||
|
XmlPullParser parser = factory.newPullParser();
|
||||||
|
parser.setInput(new StringReader(document));
|
||||||
|
return getPatternMatcher(country, parser);
|
||||||
|
} catch (XmlPullParserException e) {
|
||||||
|
Log.e(TAG, "XML parser exception reading short code pattern from settings", e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "I/O exception reading short code pattern from settings", e);
|
||||||
|
}
|
||||||
|
return null; // country not found
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a pattern matcher object for the specified country and pattern XML parser.
|
||||||
|
* @param country the country to search for
|
||||||
|
* @return a {@link ShortCodePatternMatcher} for the specified country, or null if not found
|
||||||
|
*/
|
||||||
|
private static ShortCodePatternMatcher getPatternMatcher(String country, XmlPullParser parser)
|
||||||
|
throws XmlPullParserException, IOException
|
||||||
|
{
|
||||||
|
XmlUtils.beginDocument(parser, TAG_SHORTCODES);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
XmlUtils.nextElement(parser);
|
||||||
|
|
||||||
|
String element = parser.getName();
|
||||||
|
if (element == null) break;
|
||||||
|
|
||||||
|
if (element.equals(TAG_SHORTCODE)) {
|
||||||
|
String currentCountry = parser.getAttributeValue(null, ATTR_COUNTRY);
|
||||||
|
if (country.equals(currentCountry)) {
|
||||||
|
String pattern = parser.getAttributeValue(null, ATTR_PATTERN);
|
||||||
|
String premium = parser.getAttributeValue(null, ATTR_PREMIUM);
|
||||||
|
String free = parser.getAttributeValue(null, ATTR_FREE);
|
||||||
|
String standard = parser.getAttributeValue(null, ATTR_STANDARD);
|
||||||
|
return new ShortCodePatternMatcher(pattern, premium, free, standard);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Error: skipping unknown XML tag " + element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // country not found
|
||||||
|
}
|
||||||
|
|
||||||
/** Clear the SMS application list for disposal. */
|
/** Clear the SMS application list for disposal. */
|
||||||
void dispose() {
|
void dispose() {
|
||||||
mSmsStamp.clear();
|
mSmsStamp.clear();
|
||||||
@@ -244,7 +363,9 @@ public class SmsUsageMonitor {
|
|||||||
* @return true if the app is approved; false if we need to confirm short code destinations
|
* @return true if the app is approved; false if we need to confirm short code destinations
|
||||||
*/
|
*/
|
||||||
public boolean isApprovedShortCodeSender(String appName) {
|
public boolean isApprovedShortCodeSender(String appName) {
|
||||||
return mApprovedShortCodeSenders.contains(appName);
|
synchronized (mApprovedShortCodeSenders) {
|
||||||
|
return mApprovedShortCodeSenders.contains(appName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -252,8 +373,10 @@ public class SmsUsageMonitor {
|
|||||||
* @param appName the package name of the app to add
|
* @param appName the package name of the app to add
|
||||||
*/
|
*/
|
||||||
public void addApprovedShortCodeSender(String appName) {
|
public void addApprovedShortCodeSender(String appName) {
|
||||||
Log.d(TAG, "Adding " + appName + " to list of approved short code senders.");
|
if (DBG) log("Adding " + appName + " to list of approved short code senders.");
|
||||||
mApprovedShortCodeSenders.add(appName);
|
synchronized (mApprovedShortCodeSenders) {
|
||||||
|
mApprovedShortCodeSenders.add(appName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -271,32 +394,71 @@ public class SmsUsageMonitor {
|
|||||||
* {@link #CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE}, or {@link #CATEGORY_PREMIUM_SHORT_CODE}.
|
* {@link #CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE}, or {@link #CATEGORY_PREMIUM_SHORT_CODE}.
|
||||||
*/
|
*/
|
||||||
public int checkDestination(String destAddress, String countryIso) {
|
public int checkDestination(String destAddress, String countryIso) {
|
||||||
// always allow emergency numbers
|
synchronized (mSettingsObserverHandler) {
|
||||||
if (PhoneNumberUtils.isEmergencyNumber(destAddress, countryIso)) {
|
// always allow emergency numbers
|
||||||
return CATEGORY_NOT_SHORT_CODE;
|
if (PhoneNumberUtils.isEmergencyNumber(destAddress, countryIso)) {
|
||||||
}
|
return CATEGORY_NOT_SHORT_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
ShortCodePatternMatcher patternMatcher = null;
|
ShortCodePatternMatcher patternMatcher = null;
|
||||||
|
|
||||||
if (countryIso != null) {
|
if (countryIso != null) {
|
||||||
if (countryIso.equals(mCurrentCountry)) {
|
// query secure settings and initialize content observer for updated regex patterns
|
||||||
patternMatcher = mCurrentPatternMatcher;
|
if (mCurrentCountry == null || !countryIso.equals(mCurrentCountry)) {
|
||||||
|
loadPatternsFromSettings(countryIso);
|
||||||
|
mSettingsObserverHandler.observeSettingForCountry(countryIso);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (countryIso.equals(mCurrentCountry)) {
|
||||||
|
patternMatcher = mCurrentPatternMatcher;
|
||||||
|
} else {
|
||||||
|
patternMatcher = getPatternMatcher(countryIso);
|
||||||
|
mCurrentCountry = countryIso;
|
||||||
|
mCurrentPatternMatcher = patternMatcher; // may be null if not found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patternMatcher != null) {
|
||||||
|
return patternMatcher.getNumberCategory(destAddress);
|
||||||
} else {
|
} else {
|
||||||
patternMatcher = getPatternMatcher(countryIso);
|
// Generic rule: numbers of 5 digits or less are considered potential short codes
|
||||||
mCurrentCountry = countryIso;
|
Log.e(TAG, "No patterns for \"" + countryIso + "\": using generic short code rule");
|
||||||
mCurrentPatternMatcher = patternMatcher; // may be null if not found
|
if (destAddress.length() <= 5) {
|
||||||
|
return CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
|
||||||
|
} else {
|
||||||
|
return CATEGORY_NOT_SHORT_CODE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (patternMatcher != null) {
|
private static String getSettingNameForCountry(String countryIso) {
|
||||||
return patternMatcher.getNumberCategory(destAddress);
|
return Settings.Secure.SMS_SHORT_CODES_PREFIX + countryIso;
|
||||||
} else {
|
}
|
||||||
// Generic rule: numbers of 5 digits or less are considered potential short codes
|
|
||||||
Log.e(TAG, "No patterns for \"" + countryIso + "\": using generic short code rule");
|
/**
|
||||||
if (destAddress.length() <= 5) {
|
* Load regex patterns from secure settings if present.
|
||||||
return CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
|
* @param countryIso the country to search for
|
||||||
} else {
|
*/
|
||||||
return CATEGORY_NOT_SHORT_CODE;
|
void loadPatternsFromSettings(String countryIso) {
|
||||||
|
synchronized (mSettingsObserverHandler) {
|
||||||
|
if (VDBG) log("loadPatternsFromSettings(" + countryIso + ") called");
|
||||||
|
String settingsPatterns = Settings.Secure.getString(
|
||||||
|
mContext.getContentResolver(), getSettingNameForCountry(countryIso));
|
||||||
|
if (settingsPatterns != null && !settingsPatterns.equals(
|
||||||
|
mSettingsShortCodePatterns)) {
|
||||||
|
// settings pattern string has changed: update the pattern matcher
|
||||||
|
mSettingsShortCodePatterns = settingsPatterns;
|
||||||
|
ShortCodePatternMatcher matcher = getPatternMatcher(countryIso, settingsPatterns);
|
||||||
|
if (matcher != null) {
|
||||||
|
mCurrentCountry = countryIso;
|
||||||
|
mCurrentPatternMatcher = matcher;
|
||||||
|
}
|
||||||
|
} else if (settingsPatterns == null && mSettingsShortCodePatterns != null) {
|
||||||
|
// pattern string was removed: caller will load default patterns from XML resource
|
||||||
|
mCurrentCountry = null;
|
||||||
|
mCurrentPatternMatcher = null;
|
||||||
|
mSettingsShortCodePatterns = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,7 +486,7 @@ public class SmsUsageMonitor {
|
|||||||
Long ct = System.currentTimeMillis();
|
Long ct = System.currentTimeMillis();
|
||||||
long beginCheckPeriod = ct - mCheckPeriod;
|
long beginCheckPeriod = ct - mCheckPeriod;
|
||||||
|
|
||||||
Log.d(TAG, "SMS send size=" + sent.size() + " time=" + ct);
|
if (VDBG) log("SMS send size=" + sent.size() + " time=" + ct);
|
||||||
|
|
||||||
while (!sent.isEmpty() && sent.get(0) < beginCheckPeriod) {
|
while (!sent.isEmpty() && sent.get(0) < beginCheckPeriod) {
|
||||||
sent.remove(0);
|
sent.remove(0);
|
||||||
@@ -338,4 +500,8 @@ public class SmsUsageMonitor {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void log(String msg) {
|
||||||
|
Log.d(TAG, msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user