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