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 androidx.core.app.NotificationCompat;
31 import androidx.core.app.NotificationCompat.Builder;
32 import androidx.core.app.NotificationCompat.Style;
33 import androidx.core.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 =
190                 intent.getIntExtra(EXTRA_ERROR_CODE, SendStatusReceiver.NO_ERROR_CODE);
191         // Always convert negative subIds into -1
192         int subId = PhoneUtils.getDefault().getEffectiveIncomingSubIdFromSystem(
193                 intent, EXTRA_SUB_ID);
194         deliverSmsMessages(context, subId, errorCode, messages);
195         if (MmsUtils.isDumpSmsEnabled()) {
196             final String format = intent.getStringExtra("format");
197             DebugUtils.dumpSms(messages[0].getTimestampMillis(), messages, format);
198         }
199     }
200 
deliverSmsMessages(final Context context, final int subId, final int errorCode, final android.telephony.SmsMessage[] messages)201     public static void deliverSmsMessages(final Context context, final int subId,
202             final int errorCode, final android.telephony.SmsMessage[] messages) {
203         final ContentValues messageValues =
204                 MmsUtils.parseReceivedSmsMessage(context, messages, errorCode);
205 
206         LogUtil.v(TAG, "SmsReceiver.deliverSmsMessages");
207 
208         final long nowInMillis =  System.currentTimeMillis();
209         final long receivedTimestampMs = MmsUtils.getMessageDate(messages[0], nowInMillis);
210 
211         messageValues.put(Sms.Inbox.DATE, receivedTimestampMs);
212         // Default to unread and unseen for us but ReceiveSmsMessageAction will override
213         // seen for the telephony db.
214         messageValues.put(Sms.Inbox.READ, 0);
215         messageValues.put(Sms.Inbox.SEEN, 0);
216         if (OsUtil.isAtLeastL_MR1()) {
217             messageValues.put(Sms.SUBSCRIPTION_ID, subId);
218         }
219 
220         if (messages[0].getMessageClass() == android.telephony.SmsMessage.MessageClass.CLASS_0 ||
221                 DebugUtils.debugClassZeroSmsEnabled()) {
222             Factory.get().getUIIntents().launchClassZeroActivity(context, messageValues);
223         } else {
224             final ReceiveSmsMessageAction action = new ReceiveSmsMessageAction(messageValues);
225             action.start();
226         }
227     }
228 
229     @Override
onReceive(final Context context, final Intent intent)230     public void onReceive(final Context context, final Intent intent) {
231         LogUtil.v(TAG, "SmsReceiver.onReceive " + intent);
232         // On KLP+ we only take delivery of SMS messages in SmsDeliverReceiver.
233         if (PhoneUtils.getDefault().isSmsEnabled()) {
234             final String action = intent.getAction();
235             if (OsUtil.isSecondaryUser() &&
236                     (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(action) ||
237                             // TODO: update this with the actual constant from Telephony
238                             "android.provider.Telephony.MMS_DOWNLOADED".equals(action))) {
239                 postNewMessageSecondaryUserNotification();
240             } else if (!OsUtil.isAtLeastKLP()) {
241                 deliverSmsIntent(context, intent);
242             }
243         }
244     }
245 
246     private static class SecondaryUserNotificationState extends MessageNotificationState {
SecondaryUserNotificationState()247         SecondaryUserNotificationState() {
248             super(null);
249         }
250 
251         @Override
build(Builder builder)252         protected Style build(Builder builder) {
253             return null;
254         }
255 
256         @Override
getNotificationVibrate()257         public boolean getNotificationVibrate() {
258             return true;
259         }
260     }
261 
postNewMessageSecondaryUserNotification()262     public static void postNewMessageSecondaryUserNotification() {
263         final Context context = Factory.get().getApplicationContext();
264         final Resources resources = context.getResources();
265         final PendingIntent pendingIntent = UIIntents.get()
266                 .getPendingIntentForSecondaryUserNewMessageNotification(context);
267 
268         final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
269         builder.setContentTitle(resources.getString(R.string.secondary_user_new_message_title))
270                 .setTicker(resources.getString(R.string.secondary_user_new_message_ticker))
271                 .setSmallIcon(R.drawable.ic_sms_light)
272         // Returning PRIORITY_HIGH causes L to put up a HUD notification. Without it, the ticker
273         // isn't displayed.
274                 .setPriority(Notification.PRIORITY_HIGH)
275                 .setContentIntent(pendingIntent);
276 
277         final NotificationCompat.BigTextStyle bigTextStyle =
278                 new NotificationCompat.BigTextStyle(builder);
279         bigTextStyle.bigText(resources.getString(R.string.secondary_user_new_message_title));
280         final Notification notification = bigTextStyle.build();
281 
282         final NotificationManagerCompat notificationManager =
283                 NotificationManagerCompat.from(Factory.get().getApplicationContext());
284 
285         int defaults = Notification.DEFAULT_LIGHTS;
286         if (BugleNotifications.shouldVibrate(new SecondaryUserNotificationState())) {
287             defaults |= Notification.DEFAULT_VIBRATE;
288         }
289         notification.defaults = defaults;
290 
291         notificationManager.notify(getNotificationTag(),
292                 PendingIntentConstants.SMS_SECONDARY_USER_NOTIFICATION_ID, notification);
293     }
294 
295     /**
296      * Cancel the notification
297      */
cancelSecondaryUserNotification()298     public static void cancelSecondaryUserNotification() {
299         final NotificationManagerCompat notificationManager =
300                 NotificationManagerCompat.from(Factory.get().getApplicationContext());
301         notificationManager.cancel(getNotificationTag(),
302                 PendingIntentConstants.SMS_SECONDARY_USER_NOTIFICATION_ID);
303     }
304 
getNotificationTag()305     private static String getNotificationTag() {
306         return Factory.get().getApplicationContext().getPackageName() + ":secondaryuser";
307     }
308 
309     /**
310      * Compile all of the patterns we check for to ignore system SMS messages.
311      */
compileIgnoreSmsPatterns()312     private static void compileIgnoreSmsPatterns() {
313         // Get the pattern set from GServices
314         final String smsIgnoreRegex = BugleGservices.get().getString(
315                 BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX,
316                 BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX_DEFAULT);
317         if (smsIgnoreRegex != null) {
318             final String[] ignoreSmsExpressions = smsIgnoreRegex.split("\n");
319             if (ignoreSmsExpressions.length != 0) {
320                 sIgnoreSmsPatterns = new ArrayList<Pattern>();
321                 for (int i = 0; i < ignoreSmsExpressions.length; i++) {
322                     try {
323                         sIgnoreSmsPatterns.add(Pattern.compile(ignoreSmsExpressions[i]));
324                     } catch (PatternSyntaxException e) {
325                         LogUtil.e(TAG, "compileIgnoreSmsPatterns: Skipping bad expression: " +
326                                 ignoreSmsExpressions[i]);
327                     }
328                 }
329             }
330         }
331     }
332 
333     /**
334      * Get the SMS messages from the specified SMS intent.
335      * @return the messages. If there is an error or the message should be ignored, return null.
336      */
getMessagesFromIntent(Intent intent)337     public static android.telephony.SmsMessage[] getMessagesFromIntent(Intent intent) {
338         final android.telephony.SmsMessage[] messages = Sms.Intents.getMessagesFromIntent(intent);
339 
340         // Check messages for validity
341         if (messages == null || messages.length < 1) {
342             return null;
343         }
344         // Sometimes, SmsMessage.mWrappedSmsMessage is null causing NPE when we access
345         // the methods on it although the SmsMessage itself is not null. So do this check
346         // before we do anything on the parsed SmsMessages.
347         try {
348             final String messageBody = messages[0].getDisplayMessageBody();
349             if (messageBody != null) {
350                 // Compile patterns if necessary
351                 if (sIgnoreSmsPatterns == null) {
352                     compileIgnoreSmsPatterns();
353                 }
354                 // Check against filters
355                 for (final Pattern pattern : sIgnoreSmsPatterns) {
356                     if (pattern.matcher(messageBody).matches()) {
357                         return null;
358                     }
359                 }
360             }
361         } catch (final NullPointerException e) {
362             LogUtil.e(TAG, "shouldIgnoreMessage: NPE inside SmsMessage");
363             return null;
364         }
365         return messages;
366     }
367 
368 
369     /**
370      * Check the specified SMS intent to see if the message should be ignored
371      * @return true if the message should be ignored
372      */
shouldIgnoreMessage(Intent intent)373     public static boolean shouldIgnoreMessage(Intent intent) {
374         return getMessagesFromIntent(intent) == null;
375     }
376 }
377