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