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