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