Home | History | Annotate | Download | only in receiver
      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