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