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