SmsUsageMonitor.java revision 4658a1a8c23111d5cc89feb040ce547a7b65dfb0
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.telephony; 18 19import android.app.AppGlobals; 20import android.content.ContentResolver; 21import android.content.Context; 22import android.content.pm.ApplicationInfo; 23import android.content.res.XmlResourceParser; 24import android.database.ContentObserver; 25import android.os.Binder; 26import android.os.Handler; 27import android.os.Message; 28import android.os.Process; 29import android.os.RemoteException; 30import android.os.UserHandle; 31import android.provider.Settings; 32import android.telephony.PhoneNumberUtils; 33import android.util.AtomicFile; 34import android.util.Log; 35import android.util.Xml; 36 37import com.android.internal.util.FastXmlSerializer; 38import com.android.internal.util.XmlUtils; 39 40import org.xmlpull.v1.XmlPullParser; 41import org.xmlpull.v1.XmlPullParserException; 42import org.xmlpull.v1.XmlPullParserFactory; 43import org.xmlpull.v1.XmlSerializer; 44 45import java.io.File; 46import java.io.FileInputStream; 47import java.io.FileNotFoundException; 48import java.io.FileOutputStream; 49import java.io.IOException; 50import java.io.StringReader; 51import java.util.ArrayList; 52import java.util.HashMap; 53import java.util.Iterator; 54import java.util.Map; 55import java.util.regex.Pattern; 56 57/** 58 * Implement the per-application based SMS control, which limits the number of 59 * SMS/MMS messages an app can send in the checking period. 60 * 61 * This code was formerly part of {@link SMSDispatcher}, and has been moved 62 * into a separate class to support instantiation of multiple SMSDispatchers on 63 * dual-mode devices that require support for both 3GPP and 3GPP2 format messages. 64 */ 65public class SmsUsageMonitor { 66 private static final String TAG = "SmsUsageMonitor"; 67 private static final boolean VDBG = false; 68 69 /** Default checking period for SMS sent without user permission. */ 70 private static final int DEFAULT_SMS_CHECK_PERIOD = 1800000; // 30 minutes 71 72 /** Default number of SMS sent in checking period without user permission. */ 73 private static final int DEFAULT_SMS_MAX_COUNT = 30; 74 75 /** Return value from {@link #checkDestination} for regular phone numbers. */ 76 static final int CATEGORY_NOT_SHORT_CODE = 0; 77 78 /** Return value from {@link #checkDestination} for free (no cost) short codes. */ 79 static final int CATEGORY_FREE_SHORT_CODE = 1; 80 81 /** Return value from {@link #checkDestination} for standard rate (non-premium) short codes. */ 82 static final int CATEGORY_STANDARD_SHORT_CODE = 2; 83 84 /** Return value from {@link #checkDestination} for possible premium short codes. */ 85 static final int CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3; 86 87 /** Return value from {@link #checkDestination} for premium short codes. */ 88 static final int CATEGORY_PREMIUM_SHORT_CODE = 4; 89 90 /** Premium SMS permission for a new package (ask user when first premium SMS sent). */ 91 public static final int PREMIUM_SMS_PERMISSION_UNKNOWN = 0; 92 93 /** Default premium SMS permission (ask user for each premium SMS sent). */ 94 public static final int PREMIUM_SMS_PERMISSION_ASK_USER = 1; 95 96 /** Premium SMS permission when the owner has denied the app from sending premium SMS. */ 97 public static final int PREMIUM_SMS_PERMISSION_NEVER_ALLOW = 2; 98 99 /** Premium SMS permission when the owner has allowed the app to send premium SMS. */ 100 public static final int PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW = 3; 101 102 private final int mCheckPeriod; 103 private final int mMaxAllowed; 104 105 private final HashMap<String, ArrayList<Long>> mSmsStamp = 106 new HashMap<String, ArrayList<Long>>(); 107 108 /** Context for retrieving regexes from XML resource. */ 109 private final Context mContext; 110 111 /** Country code for the cached short code pattern matcher. */ 112 private String mCurrentCountry; 113 114 /** Cached short code pattern matcher for {@link #mCurrentCountry}. */ 115 private ShortCodePatternMatcher mCurrentPatternMatcher; 116 117 /** Cached short code regex patterns from secure settings for {@link #mCurrentCountry}. */ 118 private String mSettingsShortCodePatterns; 119 120 /** Handler for responding to content observer updates. */ 121 private final SettingsObserverHandler mSettingsObserverHandler; 122 123 /** SMS short code blocking feature enabled (can be disabled in Gservices). */ 124 private boolean mEnableShortCodeConfirmation = true; 125 126 /** Directory for per-app SMS permission XML file. */ 127 private static final String SMS_POLICY_FILE_DIRECTORY = "/data/radio"; 128 129 /** Per-app SMS permission XML filename. */ 130 private static final String SMS_POLICY_FILE_NAME = "premium_sms_policy.xml"; 131 132 /** XML tag for root element. */ 133 private static final String TAG_SHORTCODES = "shortcodes"; 134 135 /** XML tag for short code patterns for a specific country. */ 136 private static final String TAG_SHORTCODE = "shortcode"; 137 138 /** XML attribute for the country code. */ 139 private static final String ATTR_COUNTRY = "country"; 140 141 /** XML attribute for the short code regex pattern. */ 142 private static final String ATTR_PATTERN = "pattern"; 143 144 /** XML attribute for the premium short code regex pattern. */ 145 private static final String ATTR_PREMIUM = "premium"; 146 147 /** XML attribute for the free short code regex pattern. */ 148 private static final String ATTR_FREE = "free"; 149 150 /** XML attribute for the standard rate short code regex pattern. */ 151 private static final String ATTR_STANDARD = "standard"; 152 153 /** Stored copy of premium SMS package permissions. */ 154 private AtomicFile mPolicyFile; 155 156 /** Loaded copy of premium SMS package permissions. */ 157 private final HashMap<String, Integer> mPremiumSmsPolicy = new HashMap<String, Integer>(); 158 159 /** XML tag for root element of premium SMS permissions. */ 160 private static final String TAG_SMS_POLICY_BODY = "premium-sms-policy"; 161 162 /** XML tag for a package. */ 163 private static final String TAG_PACKAGE = "package"; 164 165 /** XML attribute for the package name. */ 166 private static final String ATTR_PACKAGE_NAME = "name"; 167 168 /** XML attribute for the package's premium SMS permission (integer type). */ 169 private static final String ATTR_PACKAGE_SMS_POLICY = "sms-policy"; 170 171 /** 172 * SMS short code regex pattern matcher for a specific country. 173 */ 174 private static final class ShortCodePatternMatcher { 175 private final Pattern mShortCodePattern; 176 private final Pattern mPremiumShortCodePattern; 177 private final Pattern mFreeShortCodePattern; 178 private final Pattern mStandardShortCodePattern; 179 180 ShortCodePatternMatcher(String shortCodeRegex, String premiumShortCodeRegex, 181 String freeShortCodeRegex, String standardShortCodeRegex) { 182 mShortCodePattern = (shortCodeRegex != null ? Pattern.compile(shortCodeRegex) : null); 183 mPremiumShortCodePattern = (premiumShortCodeRegex != null ? 184 Pattern.compile(premiumShortCodeRegex) : null); 185 mFreeShortCodePattern = (freeShortCodeRegex != null ? 186 Pattern.compile(freeShortCodeRegex) : null); 187 mStandardShortCodePattern = (standardShortCodeRegex != null ? 188 Pattern.compile(standardShortCodeRegex) : null); 189 } 190 191 int getNumberCategory(String phoneNumber) { 192 if (mFreeShortCodePattern != null && mFreeShortCodePattern.matcher(phoneNumber) 193 .matches()) { 194 return CATEGORY_FREE_SHORT_CODE; 195 } 196 if (mStandardShortCodePattern != null && mStandardShortCodePattern.matcher(phoneNumber) 197 .matches()) { 198 return CATEGORY_STANDARD_SHORT_CODE; 199 } 200 if (mPremiumShortCodePattern != null && mPremiumShortCodePattern.matcher(phoneNumber) 201 .matches()) { 202 return CATEGORY_PREMIUM_SHORT_CODE; 203 } 204 if (mShortCodePattern != null && mShortCodePattern.matcher(phoneNumber).matches()) { 205 return CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE; 206 } 207 return CATEGORY_NOT_SHORT_CODE; 208 } 209 } 210 211 /** 212 * Observe the secure setting for updated regex patterns. 213 */ 214 private static class SettingsObserver extends ContentObserver { 215 private final int mWhat; 216 private final Handler mHandler; 217 218 SettingsObserver(Handler handler, int what) { 219 super(handler); 220 mHandler = handler; 221 mWhat = what; 222 } 223 224 @Override 225 public void onChange(boolean selfChange) { 226 mHandler.obtainMessage(mWhat).sendToTarget(); 227 } 228 } 229 230 /** 231 * Handler to update regex patterns when secure setting for the current country is updated. 232 */ 233 private class SettingsObserverHandler extends Handler { 234 /** Current content observer, or null. */ 235 SettingsObserver mSettingsObserver; 236 237 /** Current country code to watch for settings updates. */ 238 private String mCountryIso; 239 240 /** Request to start observing a secure setting. */ 241 static final int OBSERVE_SETTING = 1; 242 243 /** Handler event for updated secure settings. */ 244 static final int GLOBAL_SETTINGS_CHANGED = 2; 245 246 SettingsObserverHandler() { 247 ContentResolver resolver = mContext.getContentResolver(); 248 ContentObserver globalObserver = new SettingsObserver(this, GLOBAL_SETTINGS_CHANGED); 249 resolver.registerContentObserver(Settings.Global.getUriFor( 250 Settings.Global.SMS_SHORT_CODE_CONFIRMATION), false, globalObserver); 251 } 252 253 /** Send a message to this handler requesting to observe the setting for a new country. */ 254 void observeSettingForCountry(String countryIso) { 255 obtainMessage(OBSERVE_SETTING, countryIso).sendToTarget(); 256 } 257 258 @Override 259 public void handleMessage(Message msg) { 260 switch (msg.what) { 261 case OBSERVE_SETTING: 262 if (msg.obj != null && msg.obj instanceof String) { 263 mCountryIso = (String) msg.obj; 264 String settingName = getSettingNameForCountry(mCountryIso); 265 ContentResolver resolver = mContext.getContentResolver(); 266 267 if (mSettingsObserver != null) { 268 if (VDBG) log("Unregistering old content observer"); 269 resolver.unregisterContentObserver(mSettingsObserver); 270 } 271 272 mSettingsObserver = new SettingsObserver(this, GLOBAL_SETTINGS_CHANGED); 273 resolver.registerContentObserver( 274 Settings.Global.getUriFor(settingName), false, mSettingsObserver); 275 if (VDBG) log("Registered content observer for " + settingName); 276 } 277 break; 278 279 case GLOBAL_SETTINGS_CHANGED: 280 loadPatternsFromSettings(mCountryIso); 281 break; 282 } 283 } 284 } 285 286 /** 287 * Create SMS usage monitor. 288 * @param context the context to use to load resources and get TelephonyManager service 289 */ 290 public SmsUsageMonitor(Context context) { 291 mContext = context; 292 ContentResolver resolver = context.getContentResolver(); 293 294 mMaxAllowed = Settings.Global.getInt(resolver, 295 Settings.Global.SMS_OUTGOING_CHECK_MAX_COUNT, 296 DEFAULT_SMS_MAX_COUNT); 297 298 mCheckPeriod = Settings.Global.getInt(resolver, 299 Settings.Global.SMS_OUTGOING_CHECK_INTERVAL_MS, 300 DEFAULT_SMS_CHECK_PERIOD); 301 302 mSettingsObserverHandler = new SettingsObserverHandler(); 303 304 loadPremiumSmsPolicyDb(); 305 } 306 307 /** 308 * Return a pattern matcher object for the specified country. 309 * @param country the country to search for 310 * @return a {@link ShortCodePatternMatcher} for the specified country, or null if not found 311 */ 312 private ShortCodePatternMatcher getPatternMatcher(String country) { 313 int id = com.android.internal.R.xml.sms_short_codes; 314 XmlResourceParser parser = mContext.getResources().getXml(id); 315 316 try { 317 return getPatternMatcher(country, parser); 318 } catch (XmlPullParserException e) { 319 Log.e(TAG, "XML parser exception reading short code pattern resource", e); 320 } catch (IOException e) { 321 Log.e(TAG, "I/O exception reading short code pattern resource", e); 322 } finally { 323 parser.close(); 324 } 325 return null; // country not found 326 } 327 328 /** 329 * Return a pattern matcher object for the specified country from a secure settings string. 330 * @return a {@link ShortCodePatternMatcher} for the specified country, or null if not found 331 */ 332 private static ShortCodePatternMatcher getPatternMatcher(String country, String settingsPattern) { 333 // embed pattern tag into an XML document. 334 String document = "<shortcodes>" + settingsPattern + "</shortcodes>"; 335 if (VDBG) log("loading updated patterns from: " + document); 336 337 try { 338 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 339 XmlPullParser parser = factory.newPullParser(); 340 parser.setInput(new StringReader(document)); 341 return getPatternMatcher(country, parser); 342 } catch (XmlPullParserException e) { 343 Log.e(TAG, "XML parser exception reading short code pattern from settings", e); 344 } catch (IOException e) { 345 Log.e(TAG, "I/O exception reading short code pattern from settings", e); 346 } 347 return null; // country not found 348 } 349 350 /** 351 * Return a pattern matcher object for the specified country and pattern XML parser. 352 * @param country the country to search for 353 * @return a {@link ShortCodePatternMatcher} for the specified country, or null if not found 354 */ 355 private static ShortCodePatternMatcher getPatternMatcher(String country, XmlPullParser parser) 356 throws XmlPullParserException, IOException 357 { 358 XmlUtils.beginDocument(parser, TAG_SHORTCODES); 359 360 while (true) { 361 XmlUtils.nextElement(parser); 362 363 String element = parser.getName(); 364 if (element == null) break; 365 366 if (element.equals(TAG_SHORTCODE)) { 367 String currentCountry = parser.getAttributeValue(null, ATTR_COUNTRY); 368 if (country.equals(currentCountry)) { 369 String pattern = parser.getAttributeValue(null, ATTR_PATTERN); 370 String premium = parser.getAttributeValue(null, ATTR_PREMIUM); 371 String free = parser.getAttributeValue(null, ATTR_FREE); 372 String standard = parser.getAttributeValue(null, ATTR_STANDARD); 373 return new ShortCodePatternMatcher(pattern, premium, free, standard); 374 } 375 } else { 376 Log.e(TAG, "Error: skipping unknown XML tag " + element); 377 } 378 } 379 return null; // country not found 380 } 381 382 /** Clear the SMS application list for disposal. */ 383 void dispose() { 384 mSmsStamp.clear(); 385 } 386 387 /** 388 * Check to see if an application is allowed to send new SMS messages, and confirm with 389 * user if the send limit was reached or if a non-system app is potentially sending to a 390 * premium SMS short code or number. 391 * 392 * @param appName the package name of the app requesting to send an SMS 393 * @param smsWaiting the number of new messages desired to send 394 * @return true if application is allowed to send the requested number 395 * of new sms messages 396 */ 397 public boolean check(String appName, int smsWaiting) { 398 synchronized (mSmsStamp) { 399 removeExpiredTimestamps(); 400 401 ArrayList<Long> sentList = mSmsStamp.get(appName); 402 if (sentList == null) { 403 sentList = new ArrayList<Long>(); 404 mSmsStamp.put(appName, sentList); 405 } 406 407 return isUnderLimit(sentList, smsWaiting); 408 } 409 } 410 411 /** 412 * Check if the destination is a possible premium short code. 413 * NOTE: the caller is expected to strip non-digits from the destination number with 414 * {@link PhoneNumberUtils#extractNetworkPortion} before calling this method. 415 * This happens in {@link SMSDispatcher#sendRawPdu} so that we use the same phone number 416 * for testing and in the user confirmation dialog if the user needs to confirm the number. 417 * This makes it difficult for malware to fool the user or the short code pattern matcher 418 * by using non-ASCII characters to make the number appear to be different from the real 419 * destination phone number. 420 * 421 * @param destAddress the destination address to test for possible short code 422 * @return {@link #CATEGORY_NOT_SHORT_CODE}, {@link #CATEGORY_FREE_SHORT_CODE}, 423 * {@link #CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE}, or {@link #CATEGORY_PREMIUM_SHORT_CODE}. 424 */ 425 public int checkDestination(String destAddress, String countryIso) { 426 synchronized (mSettingsObserverHandler) { 427 // always allow emergency numbers 428 if (PhoneNumberUtils.isEmergencyNumber(destAddress, countryIso)) { 429 return CATEGORY_NOT_SHORT_CODE; 430 } 431 // always allow if the feature is disabled 432 if (!mEnableShortCodeConfirmation) { 433 return CATEGORY_NOT_SHORT_CODE; 434 } 435 436 ShortCodePatternMatcher patternMatcher = null; 437 438 if (countryIso != null) { 439 // query secure settings and initialize content observer for updated regex patterns 440 if (mCurrentCountry == null || !countryIso.equals(mCurrentCountry)) { 441 loadPatternsFromSettings(countryIso); 442 mSettingsObserverHandler.observeSettingForCountry(countryIso); 443 } 444 445 if (countryIso.equals(mCurrentCountry)) { 446 patternMatcher = mCurrentPatternMatcher; 447 } else { 448 patternMatcher = getPatternMatcher(countryIso); 449 mCurrentCountry = countryIso; 450 mCurrentPatternMatcher = patternMatcher; // may be null if not found 451 } 452 } 453 454 if (patternMatcher != null) { 455 return patternMatcher.getNumberCategory(destAddress); 456 } else { 457 // Generic rule: numbers of 5 digits or less are considered potential short codes 458 Log.e(TAG, "No patterns for \"" + countryIso + "\": using generic short code rule"); 459 if (destAddress.length() <= 5) { 460 return CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE; 461 } else { 462 return CATEGORY_NOT_SHORT_CODE; 463 } 464 } 465 } 466 } 467 468 private static String getSettingNameForCountry(String countryIso) { 469 return Settings.Global.SMS_SHORT_CODES_PREFIX + countryIso; 470 } 471 472 /** 473 * Load regex patterns from secure settings if present. 474 * @param countryIso the country to search for 475 */ 476 void loadPatternsFromSettings(String countryIso) { 477 synchronized (mSettingsObserverHandler) { 478 if (VDBG) log("loadPatternsFromSettings(" + countryIso + ") called"); 479 mEnableShortCodeConfirmation = Settings.Global.getInt(mContext.getContentResolver(), 480 Settings.Global.SMS_SHORT_CODE_CONFIRMATION, 1) != 0; 481 if (!mEnableShortCodeConfirmation) { 482 log("Short code blocking disabled: not loading patterns"); 483 return; 484 } 485 String settingsPatterns = Settings.Global.getString( 486 mContext.getContentResolver(), getSettingNameForCountry(countryIso)); 487 if (settingsPatterns != null && !settingsPatterns.equals( 488 mSettingsShortCodePatterns)) { 489 // settings pattern string has changed: update the pattern matcher 490 mSettingsShortCodePatterns = settingsPatterns; 491 ShortCodePatternMatcher matcher = getPatternMatcher(countryIso, settingsPatterns); 492 if (matcher != null) { 493 mCurrentCountry = countryIso; 494 mCurrentPatternMatcher = matcher; 495 } 496 } else if (settingsPatterns == null && mSettingsShortCodePatterns != null) { 497 // pattern string was removed: caller will load default patterns from XML resource 498 mCurrentCountry = null; 499 mCurrentPatternMatcher = null; 500 mSettingsShortCodePatterns = null; 501 } 502 } 503 } 504 505 /** 506 * Load the premium SMS policy from an XML file. 507 * Based on code from NotificationManagerService. 508 */ 509 private void loadPremiumSmsPolicyDb() { 510 synchronized (mPremiumSmsPolicy) { 511 if (mPolicyFile == null) { 512 File dir = new File(SMS_POLICY_FILE_DIRECTORY); 513 mPolicyFile = new AtomicFile(new File(dir, SMS_POLICY_FILE_NAME)); 514 515 mPremiumSmsPolicy.clear(); 516 517 FileInputStream infile = null; 518 try { 519 infile = mPolicyFile.openRead(); 520 final XmlPullParser parser = Xml.newPullParser(); 521 parser.setInput(infile, null); 522 523 XmlUtils.beginDocument(parser, TAG_SMS_POLICY_BODY); 524 525 while (true) { 526 XmlUtils.nextElement(parser); 527 528 String element = parser.getName(); 529 if (element == null) break; 530 531 if (element.equals(TAG_PACKAGE)) { 532 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); 533 String policy = parser.getAttributeValue(null, ATTR_PACKAGE_SMS_POLICY); 534 if (packageName == null) { 535 Log.e(TAG, "Error: missing package name attribute"); 536 } else if (policy == null) { 537 Log.e(TAG, "Error: missing package policy attribute"); 538 } else try { 539 mPremiumSmsPolicy.put(packageName, Integer.parseInt(policy)); 540 } catch (NumberFormatException e) { 541 Log.e(TAG, "Error: non-numeric policy type " + policy); 542 } 543 } else { 544 Log.e(TAG, "Error: skipping unknown XML tag " + element); 545 } 546 } 547 } catch (FileNotFoundException e) { 548 // No data yet 549 } catch (IOException e) { 550 Log.e(TAG, "Unable to read premium SMS policy database", e); 551 } catch (NumberFormatException e) { 552 Log.e(TAG, "Unable to parse premium SMS policy database", e); 553 } catch (XmlPullParserException e) { 554 Log.e(TAG, "Unable to parse premium SMS policy database", e); 555 } finally { 556 if (infile != null) { 557 try { 558 infile.close(); 559 } catch (IOException ignored) { 560 } 561 } 562 } 563 } 564 } 565 } 566 567 /** 568 * Persist the premium SMS policy to an XML file. 569 * Based on code from NotificationManagerService. 570 */ 571 private void writePremiumSmsPolicyDb() { 572 synchronized (mPremiumSmsPolicy) { 573 FileOutputStream outfile = null; 574 try { 575 outfile = mPolicyFile.startWrite(); 576 577 XmlSerializer out = new FastXmlSerializer(); 578 out.setOutput(outfile, "utf-8"); 579 580 out.startDocument(null, true); 581 582 out.startTag(null, TAG_SMS_POLICY_BODY); 583 584 for (Map.Entry<String, Integer> policy : mPremiumSmsPolicy.entrySet()) { 585 out.startTag(null, TAG_PACKAGE); 586 out.attribute(null, ATTR_PACKAGE_NAME, policy.getKey()); 587 out.attribute(null, ATTR_PACKAGE_SMS_POLICY, policy.getValue().toString()); 588 out.endTag(null, TAG_PACKAGE); 589 } 590 591 out.endTag(null, TAG_SMS_POLICY_BODY); 592 out.endDocument(); 593 594 mPolicyFile.finishWrite(outfile); 595 } catch (IOException e) { 596 Log.e(TAG, "Unable to write premium SMS policy database", e); 597 if (outfile != null) { 598 mPolicyFile.failWrite(outfile); 599 } 600 } 601 } 602 } 603 604 /** 605 * Returns the premium SMS permission for the specified package. If the package has never 606 * been seen before, the default {@link #PREMIUM_SMS_PERMISSION_ASK_USER} 607 * will be returned. 608 * @param packageName the name of the package to query permission 609 * @return one of {@link #PREMIUM_SMS_PERMISSION_UNKNOWN}, 610 * {@link #PREMIUM_SMS_PERMISSION_ASK_USER}, 611 * {@link #PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or 612 * {@link #PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW} 613 * @throws SecurityException if the caller is not a system process 614 */ 615 public int getPremiumSmsPermission(String packageName) { 616 checkCallerIsSystemOrSameApp(packageName); 617 synchronized (mPremiumSmsPolicy) { 618 Integer policy = mPremiumSmsPolicy.get(packageName); 619 if (policy == null) { 620 return PREMIUM_SMS_PERMISSION_UNKNOWN; 621 } else { 622 return policy; 623 } 624 } 625 } 626 627 /** 628 * Sets the premium SMS permission for the specified package and save the value asynchronously 629 * to persistent storage. 630 * @param packageName the name of the package to set permission 631 * @param permission one of {@link #PREMIUM_SMS_PERMISSION_ASK_USER}, 632 * {@link #PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or 633 * {@link #PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW} 634 * @throws SecurityException if the caller is not a system process 635 */ 636 public void setPremiumSmsPermission(String packageName, int permission) { 637 checkCallerIsSystemOrPhoneApp(); 638 if (permission < PREMIUM_SMS_PERMISSION_ASK_USER 639 || permission > PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW) { 640 throw new IllegalArgumentException("invalid SMS permission type " + permission); 641 } 642 synchronized (mPremiumSmsPolicy) { 643 mPremiumSmsPolicy.put(packageName, permission); 644 } 645 // write policy file in the background 646 new Thread(new Runnable() { 647 @Override 648 public void run() { 649 writePremiumSmsPolicyDb(); 650 } 651 }).start(); 652 } 653 654 private static void checkCallerIsSystemOrSameApp(String pkg) { 655 int uid = Binder.getCallingUid(); 656 if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) { 657 return; 658 } 659 try { 660 ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo( 661 pkg, 0, UserHandle.getCallingUserId()); 662 if (!UserHandle.isSameApp(ai.uid, uid)) { 663 throw new SecurityException("Calling uid " + uid + " gave package" 664 + pkg + " which is owned by uid " + ai.uid); 665 } 666 } catch (RemoteException re) { 667 throw new SecurityException("Unknown package " + pkg + "\n" + re); 668 } 669 } 670 671 private static void checkCallerIsSystemOrPhoneApp() { 672 int uid = Binder.getCallingUid(); 673 int appId = UserHandle.getAppId(uid); 674 if (appId == Process.SYSTEM_UID || appId == Process.PHONE_UID || uid == 0) { 675 return; 676 } 677 throw new SecurityException("Disallowed call for uid " + uid); 678 } 679 680 /** 681 * Remove keys containing only old timestamps. This can happen if an SMS app is used 682 * to send messages and then uninstalled. 683 */ 684 private void removeExpiredTimestamps() { 685 long beginCheckPeriod = System.currentTimeMillis() - mCheckPeriod; 686 687 synchronized (mSmsStamp) { 688 Iterator<Map.Entry<String, ArrayList<Long>>> iter = mSmsStamp.entrySet().iterator(); 689 while (iter.hasNext()) { 690 Map.Entry<String, ArrayList<Long>> entry = iter.next(); 691 ArrayList<Long> oldList = entry.getValue(); 692 if (oldList.isEmpty() || oldList.get(oldList.size() - 1) < beginCheckPeriod) { 693 iter.remove(); 694 } 695 } 696 } 697 } 698 699 private boolean isUnderLimit(ArrayList<Long> sent, int smsWaiting) { 700 Long ct = System.currentTimeMillis(); 701 long beginCheckPeriod = ct - mCheckPeriod; 702 703 if (VDBG) log("SMS send size=" + sent.size() + " time=" + ct); 704 705 while (!sent.isEmpty() && sent.get(0) < beginCheckPeriod) { 706 sent.remove(0); 707 } 708 709 if ((sent.size() + smsWaiting) <= mMaxAllowed) { 710 for (int i = 0; i < smsWaiting; i++ ) { 711 sent.add(ct); 712 } 713 return true; 714 } 715 return false; 716 } 717 718 private static void log(String msg) { 719 Log.d(TAG, msg); 720 } 721} 722