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