Loading core/java/android/provider/Settings.java +7 −0 Original line number Diff line number Diff line Loading @@ -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. * Loading telephony/java/com/android/internal/telephony/SmsUsageMonitor.java +211 −45 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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>>(); Loading @@ -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"; Loading Loading @@ -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 Loading @@ -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"); } Loading @@ -178,6 +263,47 @@ public class SmsUsageMonitor { XmlResourceParser parser = mContext.getResources().getXml(id); try { return getPatternMatcher(country, parser); } catch (XmlPullParserException e) { Log.e(TAG, "XML parser exception reading short code pattern resource", e); } catch (IOException e) { Log.e(TAG, "I/O exception reading short code pattern resource", e); } finally { parser.close(); } 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) { Loading @@ -199,13 +325,6 @@ public class SmsUsageMonitor { Log.e(TAG, "Error: skipping unknown XML tag " + element); } } } catch (XmlPullParserException e) { Log.e(TAG, "XML parser exception reading short code pattern resource", e); } catch (IOException e) { Log.e(TAG, "I/O exception reading short code pattern resource", e); } finally { parser.close(); } return null; // country not found } Loading Loading @@ -244,17 +363,21 @@ public class SmsUsageMonitor { * @return true if the app is approved; false if we need to confirm short code destinations */ public boolean isApprovedShortCodeSender(String appName) { synchronized (mApprovedShortCodeSenders) { return mApprovedShortCodeSenders.contains(appName); } } /** * Add app package name to the list of approved short code senders. * @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."); if (DBG) log("Adding " + appName + " to list of approved short code senders."); synchronized (mApprovedShortCodeSenders) { mApprovedShortCodeSenders.add(appName); } } /** * Check if the destination is a possible premium short code. Loading @@ -271,6 +394,7 @@ public class SmsUsageMonitor { * {@link #CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE}, or {@link #CATEGORY_PREMIUM_SHORT_CODE}. */ public int checkDestination(String destAddress, String countryIso) { synchronized (mSettingsObserverHandler) { // always allow emergency numbers if (PhoneNumberUtils.isEmergencyNumber(destAddress, countryIso)) { return CATEGORY_NOT_SHORT_CODE; Loading @@ -279,6 +403,12 @@ public class SmsUsageMonitor { ShortCodePatternMatcher patternMatcher = null; 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 { Loading @@ -300,6 +430,38 @@ public class SmsUsageMonitor { } } } } 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; } } } /** * Remove keys containing only old timestamps. This can happen if an SMS app is used Loading @@ -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); Loading @@ -338,4 +500,8 @@ public class SmsUsageMonitor { } return false; } private static void log(String msg) { Log.d(TAG, msg); } } Loading
core/java/android/provider/Settings.java +7 −0 Original line number Diff line number Diff line Loading @@ -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. * Loading
telephony/java/com/android/internal/telephony/SmsUsageMonitor.java +211 −45 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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>>(); Loading @@ -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"; Loading Loading @@ -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 Loading @@ -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"); } Loading @@ -178,6 +263,47 @@ public class SmsUsageMonitor { XmlResourceParser parser = mContext.getResources().getXml(id); try { return getPatternMatcher(country, parser); } catch (XmlPullParserException e) { Log.e(TAG, "XML parser exception reading short code pattern resource", e); } catch (IOException e) { Log.e(TAG, "I/O exception reading short code pattern resource", e); } finally { parser.close(); } 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) { Loading @@ -199,13 +325,6 @@ public class SmsUsageMonitor { Log.e(TAG, "Error: skipping unknown XML tag " + element); } } } catch (XmlPullParserException e) { Log.e(TAG, "XML parser exception reading short code pattern resource", e); } catch (IOException e) { Log.e(TAG, "I/O exception reading short code pattern resource", e); } finally { parser.close(); } return null; // country not found } Loading Loading @@ -244,17 +363,21 @@ public class SmsUsageMonitor { * @return true if the app is approved; false if we need to confirm short code destinations */ public boolean isApprovedShortCodeSender(String appName) { synchronized (mApprovedShortCodeSenders) { return mApprovedShortCodeSenders.contains(appName); } } /** * Add app package name to the list of approved short code senders. * @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."); if (DBG) log("Adding " + appName + " to list of approved short code senders."); synchronized (mApprovedShortCodeSenders) { mApprovedShortCodeSenders.add(appName); } } /** * Check if the destination is a possible premium short code. Loading @@ -271,6 +394,7 @@ public class SmsUsageMonitor { * {@link #CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE}, or {@link #CATEGORY_PREMIUM_SHORT_CODE}. */ public int checkDestination(String destAddress, String countryIso) { synchronized (mSettingsObserverHandler) { // always allow emergency numbers if (PhoneNumberUtils.isEmergencyNumber(destAddress, countryIso)) { return CATEGORY_NOT_SHORT_CODE; Loading @@ -279,6 +403,12 @@ public class SmsUsageMonitor { ShortCodePatternMatcher patternMatcher = null; 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 { Loading @@ -300,6 +430,38 @@ public class SmsUsageMonitor { } } } } 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; } } } /** * Remove keys containing only old timestamps. This can happen if an SMS app is used Loading @@ -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); Loading @@ -338,4 +500,8 @@ public class SmsUsageMonitor { } return false; } private static void log(String msg) { Log.d(TAG, msg); } }