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