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