1 /*
2  * Copyright (C) 2015 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.messaging.receiver;
18 
19 import android.app.Notification;
20 import android.app.PendingIntent;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.PackageManager;
27 import android.content.res.Resources;
28 import android.provider.Telephony;
29 import android.provider.Telephony.Sms;
30 import android.support.v4.app.NotificationCompat;
31 import android.support.v4.app.NotificationCompat.Builder;
32 import android.support.v4.app.NotificationCompat.Style;
33 import android.support.v4.app.NotificationManagerCompat;
34 
35 import java.util.ArrayList;
36 import java.util.regex.Pattern;
37 import java.util.regex.PatternSyntaxException;
38 
39 import com.android.messaging.Factory;
40 import com.android.messaging.R;
41 import com.android.messaging.datamodel.BugleNotifications;
42 import com.android.messaging.datamodel.MessageNotificationState;
43 import com.android.messaging.datamodel.NoConfirmationSmsSendService;
44 import com.android.messaging.datamodel.action.ReceiveSmsMessageAction;
45 import com.android.messaging.sms.MmsUtils;
46 import com.android.messaging.ui.UIIntents;
47 import com.android.messaging.util.BugleGservices;
48 import com.android.messaging.util.BugleGservicesKeys;
49 import com.android.messaging.util.DebugUtils;
50 import com.android.messaging.util.LogUtil;
51 import com.android.messaging.util.OsUtil;
52 import com.android.messaging.util.PendingIntentConstants;
53 import com.android.messaging.util.PhoneUtils;
54 
55 /**
56  * Class that receives incoming SMS messages through android.provider.Telephony.SMS_RECEIVED
57  *
58  * This class serves two purposes:
59  * - Process phone verification SMS messages
60  * - Handle SMS messages when the user has enabled us to be the default SMS app (Pre-KLP)
61  */
62 public final class SmsReceiver extends BroadcastReceiver {
63     private static final String TAG = LogUtil.BUGLE_TAG;
64 
65     private static ArrayList<Pattern> sIgnoreSmsPatterns;
66 
67     /**
68      * Enable or disable the SmsReceiver as appropriate. Pre-KLP we use this receiver for
69      * receiving incoming SMS messages. For KLP+ this receiver is not used when running as the
70      * primary user and the SmsDeliverReceiver is used for receiving incoming SMS messages.
71      * When running as a secondary user, this receiver is still used to trigger the incoming
72      * notification.
73      */
updateSmsReceiveHandler(final Context context)74     public static void updateSmsReceiveHandler(final Context context) {
75         boolean smsReceiverEnabled;
76         boolean mmsWapPushReceiverEnabled;
77         boolean respondViaMessageEnabled;
78         boolean broadcastAbortEnabled;
79 
80         if (OsUtil.isAtLeastKLP()) {
81             // When we're running as the secondary user, we don't get the new SMS_DELIVER intent,
82             // only the primary user receives that. As secondary, we need to go old-school and
83             // listen for the SMS_RECEIVED intent. For the secondary user, use this SmsReceiver
84             // for both sms and mms notification. For the primary user on KLP (and above), we don't
85             // use the SmsReceiver.
86             smsReceiverEnabled = OsUtil.isSecondaryUser();
87             // On KLP use the new deliver event for mms
88             mmsWapPushReceiverEnabled = false;
89             // On KLP we need to always enable this handler to show in the list of sms apps
90             respondViaMessageEnabled = true;
91             // On KLP we don't need to abort the broadcast
92             broadcastAbortEnabled = false;
93         } else {
94             // On JB we use the sms receiver for both sms/mms delivery
95             final boolean carrierSmsEnabled = PhoneUtils.getDefault().isSmsEnabled();
96             smsReceiverEnabled = carrierSmsEnabled;
97 
98             // On JB we use the mms receiver when sms/mms is enabled
99             mmsWapPushReceiverEnabled = carrierSmsEnabled;
100             // On JB this is dynamic to make sure we don't show in dialer if sms is disabled
101             respondViaMessageEnabled = carrierSmsEnabled;
102             // On JB we need to abort broadcasts if SMS is enabled
103             broadcastAbortEnabled = carrierSmsEnabled;
104         }
105 
106         final PackageManager packageManager = context.getPackageManager();
107         final boolean logv = LogUtil.isLoggable(TAG, LogUtil.VERBOSE);
108         if (smsReceiverEnabled) {
109             if (logv) {
110                 LogUtil.v(TAG, "Enabling SMS message receiving");
111             }
112             packageManager.setComponentEnabledSetting(
113                     new ComponentName(context, SmsReceiver.class),
114                     PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
115 
116         } else {
117             if (logv) {
118                 LogUtil.v(TAG, "Disabling SMS message receiving");
119             }
120             packageManager.setComponentEnabledSetting(
121                     new ComponentName(context, SmsReceiver.class),
122                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
123         }
124         if (mmsWapPushReceiverEnabled) {
125             if (logv) {
126                 LogUtil.v(TAG, "Enabling MMS message receiving");
127             }
128             packageManager.setComponentEnabledSetting(
129                     new ComponentName(context, MmsWapPushReceiver.class),
130                     PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
131         } else {
132             if (logv) {
133                 LogUtil.v(TAG, "Disabling MMS message receiving");
134             }
135             packageManager.setComponentEnabledSetting(
136                     new ComponentName(context, MmsWapPushReceiver.class),
137                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
138         }
139         if (broadcastAbortEnabled) {
140             if (logv) {
141                 LogUtil.v(TAG, "Enabling SMS/MMS broadcast abort");
142             }
143             packageManager.setComponentEnabledSetting(
144                     new ComponentName(context, AbortSmsReceiver.class),
145                     PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
146             packageManager.setComponentEnabledSetting(
147                     new ComponentName(context, AbortMmsWapPushReceiver.class),
148                     PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
149         } else {
150             if (logv) {
151                 LogUtil.v(TAG, "Disabling SMS/MMS broadcast abort");
152             }
153             packageManager.setComponentEnabledSetting(
154                     new ComponentName(context, AbortSmsReceiver.class),
155                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
156             packageManager.setComponentEnabledSetting(
157                     new ComponentName(context, AbortMmsWapPushReceiver.class),
158                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
159         }
160         if (respondViaMessageEnabled) {
161             if (logv) {
162                 LogUtil.v(TAG, "Enabling respond via message intent");
163             }
164             packageManager.setComponentEnabledSetting(
165                     new ComponentName(context, NoConfirmationSmsSendService.class),
166                     PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
167         } else {
168             if (logv) {
169                 LogUtil.v(TAG, "Disabling respond via message intent");
170             }
171             packageManager.setComponentEnabledSetting(
172                     new ComponentName(context, NoConfirmationSmsSendService.class),
173                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
174         }
175     }
176 
177     private static final String EXTRA_ERROR_CODE = "errorCode";
178     private static final String EXTRA_SUB_ID = "subscription";
179 
deliverSmsIntent(final Context context, final Intent intent)180     public static void deliverSmsIntent(final Context context, final Intent intent) {
181         final android.telephony.SmsMessage[] messages = getMessagesFromIntent(intent);
182 
183         // Check messages for validity
184         if (messages == null || messages.length < 1) {
185             LogUtil.e(TAG, "processReceivedSms: null or zero or ignored message");
186             return;
187         }
188 
189         final int errorCode = intent.getIntExtra(EXTRA_ERROR_CODE, 0);
190         // Always convert negative subIds into -1
191         int subId = PhoneUtils.getDefault().getEffectiveIncomingSubIdFromSystem(
192                 intent, EXTRA_SUB_ID);
193         deliverSmsMessages(context, subId, errorCode, messages);
194         if (MmsUtils.isDumpSmsEnabled()) {
195             final String format = null;
196             DebugUtils.dumpSms(messages[0].getTimestampMillis(), messages, format);
197         }
198     }
199 
deliverSmsMessages(final Context context, final int subId, final int errorCode, final android.telephony.SmsMessage[] messages)200     public static void deliverSmsMessages(final Context context, final int subId,
201             final int errorCode, final android.telephony.SmsMessage[] messages) {
202         final ContentValues messageValues =
203                 MmsUtils.parseReceivedSmsMessage(context, messages, errorCode);
204 
205         LogUtil.v(TAG, "SmsReceiver.deliverSmsMessages");
206 
207         final long nowInMillis =  System.currentTimeMillis();
208         final long receivedTimestampMs = MmsUtils.getMessageDate(messages[0], nowInMillis);
209 
210         messageValues.put(Sms.Inbox.DATE, receivedTimestampMs);
211         // Default to unread and unseen for us but ReceiveSmsMessageAction will override
212         // seen for the telephony db.
213         messageValues.put(Sms.Inbox.READ, 0);
214         messageValues.put(Sms.Inbox.SEEN, 0);
215         if (OsUtil.isAtLeastL_MR1()) {
216             messageValues.put(Sms.SUBSCRIPTION_ID, subId);
217         }
218 
219         if (messages[0].getMessageClass() == android.telephony.SmsMessage.MessageClass.CLASS_0 ||
220                 DebugUtils.debugClassZeroSmsEnabled()) {
221             Factory.get().getUIIntents().launchClassZeroActivity(context, messageValues);
222         } else {
223             final ReceiveSmsMessageAction action = new ReceiveSmsMessageAction(messageValues);
224             action.start();
225         }
226     }
227 
228     @Override
onReceive(final Context context, final Intent intent)229     public void onReceive(final Context context, final Intent intent) {
230         LogUtil.v(TAG, "SmsReceiver.onReceive " + intent);
231         // On KLP+ we only take delivery of SMS messages in SmsDeliverReceiver.
232         if (PhoneUtils.getDefault().isSmsEnabled()) {
233             final String action = intent.getAction();
234             if (OsUtil.isSecondaryUser() &&
235                     (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(action) ||
236                             // TODO: update this with the actual constant from Telephony
237                             "android.provider.Telephony.MMS_DOWNLOADED".equals(action))) {
238                 postNewMessageSecondaryUserNotification();
239             } else if (!OsUtil.isAtLeastKLP()) {
240                 deliverSmsIntent(context, intent);
241             }
242         }
243     }
244 
245     private static class SecondaryUserNotificationState extends MessageNotificationState {
SecondaryUserNotificationState()246         SecondaryUserNotificationState() {
247             super(null);
248         }
249 
250         @Override
build(Builder builder)251         protected Style build(Builder builder) {
252             return null;
253         }
254 
255         @Override
getNotificationVibrate()256         public boolean getNotificationVibrate() {
257             return true;
258         }
259     }
260 
postNewMessageSecondaryUserNotification()261     public static void postNewMessageSecondaryUserNotification() {
262         final Context context = Factory.get().getApplicationContext();
263         final Resources resources = context.getResources();
264         final PendingIntent pendingIntent = UIIntents.get()
265                 .getPendingIntentForSecondaryUserNewMessageNotification(context);
266 
267         final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
268         builder.setContentTitle(resources.getString(R.string.secondary_user_new_message_title))
269                 .setTicker(resources.getString(R.string.secondary_user_new_message_ticker))
270                 .setSmallIcon(R.drawable.ic_sms_light)
271         // Returning PRIORITY_HIGH causes L to put up a HUD notification. Without it, the ticker
272         // isn't displayed.
273                 .setPriority(Notification.PRIORITY_HIGH)
274                 .setContentIntent(pendingIntent);
275 
276         final NotificationCompat.BigTextStyle bigTextStyle =
277                 new NotificationCompat.BigTextStyle(builder);
278         bigTextStyle.bigText(resources.getString(R.string.secondary_user_new_message_title));
279         final Notification notification = bigTextStyle.build();
280 
281         final NotificationManagerCompat notificationManager =
282                 NotificationManagerCompat.from(Factory.get().getApplicationContext());
283 
284         int defaults = Notification.DEFAULT_LIGHTS;
285         if (BugleNotifications.shouldVibrate(new SecondaryUserNotificationState())) {
286             defaults |= Notification.DEFAULT_VIBRATE;
287         }
288         notification.defaults = defaults;
289 
290         notificationManager.notify(getNotificationTag(),
291                 PendingIntentConstants.SMS_SECONDARY_USER_NOTIFICATION_ID, notification);
292     }
293 
294     /**
295      * Cancel the notification
296      */
cancelSecondaryUserNotification()297     public static void cancelSecondaryUserNotification() {
298         final NotificationManagerCompat notificationManager =
299                 NotificationManagerCompat.from(Factory.get().getApplicationContext());
300         notificationManager.cancel(getNotificationTag(),
301                 PendingIntentConstants.SMS_SECONDARY_USER_NOTIFICATION_ID);
302     }
303 
getNotificationTag()304     private static String getNotificationTag() {
305         return Factory.get().getApplicationContext().getPackageName() + ":secondaryuser";
306     }
307 
308     /**
309      * Compile all of the patterns we check for to ignore system SMS messages.
310      */
compileIgnoreSmsPatterns()311     private static void compileIgnoreSmsPatterns() {
312         // Get the pattern set from GServices
313         final String smsIgnoreRegex = BugleGservices.get().getString(
314                 BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX,
315                 BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX_DEFAULT);
316         if (smsIgnoreRegex != null) {
317             final String[] ignoreSmsExpressions = smsIgnoreRegex.split("\n");
318             if (ignoreSmsExpressions.length != 0) {
319                 sIgnoreSmsPatterns = new ArrayList<Pattern>();
320                 for (int i = 0; i < ignoreSmsExpressions.length; i++) {
321                     try {
322                         sIgnoreSmsPatterns.add(Pattern.compile(ignoreSmsExpressions[i]));
323                     } catch (PatternSyntaxException e) {
324                         LogUtil.e(TAG, "compileIgnoreSmsPatterns: Skipping bad expression: " +
325                                 ignoreSmsExpressions[i]);
326                     }
327                 }
328             }
329         }
330     }
331 
332     /**
333      * Get the SMS messages from the specified SMS intent.
334      * @return the messages. If there is an error or the message should be ignored, return null.
335      */
getMessagesFromIntent(Intent intent)336     public static android.telephony.SmsMessage[] getMessagesFromIntent(Intent intent) {
337         final android.telephony.SmsMessage[] messages = Sms.Intents.getMessagesFromIntent(intent);
338 
339         // Check messages for validity
340         if (messages == null || messages.length < 1) {
341             return null;
342         }
343         // Sometimes, SmsMessage.mWrappedSmsMessage is null causing NPE when we access
344         // the methods on it although the SmsMessage itself is not null. So do this check
345         // before we do anything on the parsed SmsMessages.
346         try {
347             final String messageBody = messages[0].getDisplayMessageBody();
348             if (messageBody != null) {
349                 // Compile patterns if necessary
350                 if (sIgnoreSmsPatterns == null) {
351                     compileIgnoreSmsPatterns();
352                 }
353                 // Check against filters
354                 for (final Pattern pattern : sIgnoreSmsPatterns) {
355                     if (pattern.matcher(messageBody).matches()) {
356                         return null;
357                     }
358                 }
359             }
360         } catch (final NullPointerException e) {
361             LogUtil.e(TAG, "shouldIgnoreMessage: NPE inside SmsMessage");
362             return null;
363         }
364         return messages;
365     }
366 
367 
368     /**
369      * Check the specified SMS intent to see if the message should be ignored
370      * @return true if the message should be ignored
371      */
shouldIgnoreMessage(Intent intent)372     public static boolean shouldIgnoreMessage(Intent intent) {
373         return getMessagesFromIntent(intent) == null;
374     }
375 }
376