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