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