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