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