• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
mergeShortCodeCategories(int type1, int type2)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 
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 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 
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      */
SmsUsageMonitor(Context context)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      */
getPatternMatcherFromFile(String country)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 
getPatternMatcherFromResource(String country)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 
getPatternMatcherFromXmlParser(XmlPullParser parser, String country)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. */
dispose()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      */
check(String appName, int smsWaiting)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      */
checkDestination(String destAddress, String countryIso)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      */
loadPremiumSmsPolicyDb()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      */
writePremiumSmsPolicyDb()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      */
getPremiumSmsPermission(String packageName)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      */
setPremiumSmsPermission(String packageName, int permission)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 
checkCallerIsSystemOrPhoneOrSameApp(String pkg)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 
checkCallerIsSystemOrPhoneApp()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      */
removeExpiredTimestamps()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 
isUnderLimit(ArrayList<Long> sent, int smsWaiting)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 
log(String msg)647     private static void log(String msg) {
648         Rlog.d(TAG, msg);
649     }
650 }
651