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 */ 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 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 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 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 { 246 SecondaryUserNotificationState() { 247 super(null); 248 } 249 250 @Override 251 protected Style build(Builder builder) { 252 return null; 253 } 254 255 @Override 256 public boolean getNotificationVibrate() { 257 return true; 258 } 259 } 260 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 */ 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 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 */ 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 */ 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 */ 372 public static boolean shouldIgnoreMessage(Intent intent) { 373 return getMessagesFromIntent(intent) == null; 374 } 375 } 376