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:
Jake Hamby
2012-05-17 17:59:43 -07:00
committed by Android Git Automerger
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 = 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.
* *

View File

@@ -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);
}
} }