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