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