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