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