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