Merge "Enable SMS short code patterns to be updated from secure settings." into jb-dev

This commit is contained in:
Jake Hamby
2012-05-17 17:57:45 -07:00
committed by Android (Google) Code Review
2 changed files with 218 additions and 45 deletions

View File

@@ -4265,6 +4265,13 @@ public final class Settings {
public static final String 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.
*

View File

@@ -19,15 +19,21 @@ package com.android.internal.telephony;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -45,6 +51,8 @@ import java.util.regex.Pattern;
*/
public class 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. */
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 mMaxAllowed;
private final HashMap<String, ArrayList<Long>> mSmsStamp =
new HashMap<String, ArrayList<Long>>();
@@ -87,6 +96,12 @@ public class SmsUsageMonitor {
/** Cached short code pattern matcher for {@link #mCurrentCountry}. */
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. */
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.
* @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,
DEFAULT_SMS_CHECK_PERIOD);
mSettingsObserverHandler = new SettingsObserverHandler();
// system MMS app is always allowed to send to short codes
mApprovedShortCodeSenders.add("com.android.mms");
}
@@ -178,27 +263,7 @@ public class SmsUsageMonitor {
XmlResourceParser parser = mContext.getResources().getXml(id);
try {
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 getPatternMatcher(country, parser);
} catch (XmlPullParserException e) {
Log.e(TAG, "XML parser exception reading short code pattern resource", e);
} catch (IOException e) {
@@ -209,6 +274,60 @@ public class SmsUsageMonitor {
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. */
void dispose() {
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
*/
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
*/
public void addApprovedShortCodeSender(String appName) {
Log.d(TAG, "Adding " + appName + " to list of approved short code senders.");
mApprovedShortCodeSenders.add(appName);
if (DBG) log("Adding " + appName + " to list of approved short code senders.");
synchronized (mApprovedShortCodeSenders) {
mApprovedShortCodeSenders.add(appName);
}
}
/**
@@ -271,32 +394,71 @@ public class SmsUsageMonitor {
* {@link #CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE}, or {@link #CATEGORY_PREMIUM_SHORT_CODE}.
*/
public int checkDestination(String destAddress, String countryIso) {
// always allow emergency numbers
if (PhoneNumberUtils.isEmergencyNumber(destAddress, countryIso)) {
return CATEGORY_NOT_SHORT_CODE;
}
synchronized (mSettingsObserverHandler) {
// always allow emergency numbers
if (PhoneNumberUtils.isEmergencyNumber(destAddress, countryIso)) {
return CATEGORY_NOT_SHORT_CODE;
}
ShortCodePatternMatcher patternMatcher = null;
ShortCodePatternMatcher patternMatcher = null;
if (countryIso != null) {
if (countryIso.equals(mCurrentCountry)) {
patternMatcher = mCurrentPatternMatcher;
if (countryIso != null) {
// query secure settings and initialize content observer for updated regex patterns
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 {
patternMatcher = getPatternMatcher(countryIso);
mCurrentCountry = countryIso;
mCurrentPatternMatcher = patternMatcher; // may be null if not found
// 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) {
return CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
} else {
return CATEGORY_NOT_SHORT_CODE;
}
}
}
}
if (patternMatcher != null) {
return patternMatcher.getNumberCategory(destAddress);
} 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) {
return CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
} else {
return CATEGORY_NOT_SHORT_CODE;
private static String getSettingNameForCountry(String countryIso) {
return Settings.Secure.SMS_SHORT_CODES_PREFIX + countryIso;
}
/**
* Load regex patterns from secure settings if present.
* @param countryIso the country to search for
*/
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 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) {
sent.remove(0);
@@ -338,4 +500,8 @@ public class SmsUsageMonitor {
}
return false;
}
private static void log(String msg) {
Log.d(TAG, msg);
}
}