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