Home | History | Annotate | Download | only in cellbroadcastreceiver
      1 /*
      2  * Copyright (C) 2011 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.cellbroadcastreceiver;
     18 
     19 import android.app.Notification;
     20 import android.app.NotificationManager;
     21 import android.app.PendingIntent;
     22 import android.app.Service;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.SharedPreferences;
     26 import android.os.Bundle;
     27 import android.os.IBinder;
     28 import android.preference.PreferenceManager;
     29 import android.provider.Telephony;
     30 import android.telephony.SmsCbConstants;
     31 import android.telephony.SmsCbMessage;
     32 import android.util.Log;
     33 
     34 /**
     35  * This service manages the display and animation of broadcast messages.
     36  * Emergency messages display with a flashing animated exclamation mark icon,
     37  * and an alert tone is played when the alert is first shown to the user
     38  * (but not when the user views a previously received broadcast).
     39  */
     40 public class CellBroadcastAlertService extends Service {
     41     private static final String TAG = "CellBroadcastAlertService";
     42 
     43     /** Identifier for notification ID extra. */
     44     public static final String SMS_CB_NOTIFICATION_ID_EXTRA =
     45             "com.android.cellbroadcastreceiver.SMS_CB_NOTIFICATION_ID";
     46 
     47     @Override
     48     public int onStartCommand(Intent intent, int flags, int startId) {
     49         String action = intent.getAction();
     50         if (Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION.equals(action) ||
     51                 Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) {
     52             handleCellBroadcastIntent(intent);
     53         } else {
     54             Log.e(TAG, "Unrecognized intent action: " + action);
     55         }
     56         stopSelf(); // this service always stops after processing the intent
     57         return START_NOT_STICKY;
     58     }
     59 
     60     private void handleCellBroadcastIntent(Intent intent) {
     61         Bundle extras = intent.getExtras();
     62         if (extras == null) {
     63             Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no extras!");
     64             return;
     65         }
     66 
     67         Object[] pdus = (Object[]) extras.get("pdus");
     68 
     69         if (pdus == null || pdus.length < 1) {
     70             Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no pdus");
     71             return;
     72         }
     73 
     74         // create message from first PDU
     75         SmsCbMessage message = SmsCbMessage.createFromPdu((byte[]) pdus[0]);
     76         if (message == null) {
     77             Log.e(TAG, "failed to create SmsCbMessage from PDU: " + pdus[0]);
     78             return;
     79         }
     80 
     81         // append message bodies from any additional PDUs (GSM only)
     82         for (int i = 1; i < pdus.length; i++) {
     83             SmsCbMessage nextPage = SmsCbMessage.createFromPdu((byte[]) pdus[i]);
     84             if (nextPage != null) {
     85                 message.appendToBody(nextPage.getMessageBody());
     86             } else {
     87                 Log.w(TAG, "failed to append to SmsCbMessage from PDU: " + pdus[i]);
     88                 // continue so we can show the first page of the broadcast
     89             }
     90         }
     91 
     92         final CellBroadcastMessage cbm = new CellBroadcastMessage(message);
     93         if (!isMessageEnabledByUser(cbm)) {
     94             Log.d(TAG, "ignoring alert of type " + cbm.getMessageIdentifier() +
     95                     " by user preference");
     96             return;
     97         }
     98 
     99         // add notification to the bar
    100         addToNotificationBar(cbm);
    101         if (cbm.isEmergencyAlertMessage() || CellBroadcastConfigService
    102                 .isOperatorDefinedEmergencyId(cbm.getMessageIdentifier())) {
    103             // start audio/vibration/speech service for emergency alerts
    104             Intent audioIntent = new Intent(this, CellBroadcastAlertAudio.class);
    105             audioIntent.setAction(CellBroadcastAlertAudio.ACTION_START_ALERT_AUDIO);
    106             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
    107             String duration = prefs.getString(CellBroadcastSettings.KEY_ALERT_SOUND_DURATION,
    108                     CellBroadcastSettings.ALERT_SOUND_DEFAULT_DURATION);
    109             audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_DURATION_EXTRA,
    110                     Integer.parseInt(duration));
    111 
    112             if (prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_SPEECH, true)) {
    113                 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY,
    114                         cbm.getMessageBody());
    115 
    116                 String language = cbm.getLanguageCode();
    117                 if (cbm.isEtwsMessage() && !"ja".equals(language)) {
    118                     Log.w(TAG, "bad language code for ETWS - using Japanese TTS");
    119                     language = "ja";
    120                 } else if (cbm.isCmasMessage() && !"en".equals(language)) {
    121                     Log.w(TAG, "bad language code for CMAS - using English TTS");
    122                     language = "en";
    123                 }
    124                 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_LANGUAGE,
    125                         language);
    126             }
    127             startService(audioIntent);
    128         }
    129         // write to database on a separate service thread
    130         Intent dbWriteIntent = new Intent(this, CellBroadcastDatabaseService.class);
    131         dbWriteIntent.setAction(CellBroadcastDatabaseService.ACTION_INSERT_NEW_BROADCAST);
    132         dbWriteIntent.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, cbm);
    133         startService(dbWriteIntent);
    134     }
    135 
    136     /**
    137      * Filter out broadcasts on the test channels that the user has not enabled,
    138      * and types of notifications that the user is not interested in receiving.
    139      * This allows us to enable an entire range of message identifiers in the
    140      * radio and not have to explicitly disable the message identifiers for
    141      * test broadcasts. In the unlikely event that the default shared preference
    142      * values were not initialized in CellBroadcastReceiverApp, the second parameter
    143      * to the getBoolean() calls match the default values in res/xml/preferences.xml.
    144      *
    145      * @param message the message to check
    146      * @return true if the user has enabled this message type; false otherwise
    147      */
    148     private boolean isMessageEnabledByUser(CellBroadcastMessage message) {
    149         switch (message.getMessageIdentifier()) {
    150             case SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE:
    151                 return PreferenceManager.getDefaultSharedPreferences(this)
    152                         .getBoolean(CellBroadcastSettings.KEY_ENABLE_ETWS_TEST_ALERTS, false);
    153 
    154             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
    155             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
    156             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
    157             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
    158             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
    159             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
    160             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
    161             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
    162                 return PreferenceManager.getDefaultSharedPreferences(this)
    163                         .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_IMMINENT_THREAT_ALERTS,
    164                                 true);
    165 
    166             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
    167                 return PreferenceManager.getDefaultSharedPreferences(this)
    168                         .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, false);
    169 
    170             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
    171                 return PreferenceManager.getDefaultSharedPreferences(this)
    172                         .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_TEST_ALERTS, false);
    173 
    174             default:
    175                 return true;
    176         }
    177     }
    178 
    179     private void addToNotificationBar(CellBroadcastMessage message) {
    180         int channelTitleId = message.getDialogTitleResource();
    181         CharSequence channelName = getText(channelTitleId);
    182         String messageBody = message.getMessageBody();
    183 
    184         Notification notification = new Notification(R.drawable.stat_color_warning,
    185                 channelName, System.currentTimeMillis());
    186 
    187         int notificationId = CellBroadcastReceiverApp.getCellBroadcastReceiverApp()
    188                 .getNextNotificationId();
    189 
    190         PendingIntent pi = PendingIntent.getActivity(this, 0, createDisplayMessageIntent(
    191                 this, message, notificationId), 0);
    192 
    193         notification.setLatestEventInfo(this, channelName, messageBody, pi);
    194 
    195         if (message.isEmergencyAlertMessage() || CellBroadcastConfigService
    196                 .isOperatorDefinedEmergencyId(message.getMessageIdentifier())) {
    197             // Emergency: open notification immediately
    198             notification.fullScreenIntent = pi;
    199             // use default notification lights (CellBroadcastAlertAudio plays sound/vibration)
    200             notification.defaults = Notification.DEFAULT_LIGHTS;
    201         } else {
    202             // use default sound/vibration/lights for non-emergency broadcasts
    203             notification.defaults = Notification.DEFAULT_ALL;
    204         }
    205 
    206         Log.i(TAG, "addToNotificationBar notificationId: " + notificationId);
    207 
    208         NotificationManager notificationManager =
    209             (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
    210 
    211         notificationManager.notify(notificationId, notification);
    212     }
    213 
    214     static Intent createDisplayMessageIntent(Context context,
    215             CellBroadcastMessage message, int notificationId) {
    216         // Trigger the list activity to fire up a dialog that shows the received messages
    217         Intent intent = new Intent(context, CellBroadcastListActivity.class);
    218         intent.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, message);
    219         intent.putExtra(SMS_CB_NOTIFICATION_ID_EXTRA, notificationId);
    220 
    221         // This line is needed to make this intent compare differently than the other intents
    222         // created here for other messages. Without this line, the PendingIntent always gets the
    223         // intent of a previous message and notification.
    224         intent.setType(Integer.toString(notificationId));
    225 
    226         return intent;
    227     }
    228 
    229     @Override
    230     public IBinder onBind(Intent intent) {
    231         return null;    // clients can't bind to this service
    232     }
    233 }
    234