| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.cellbroadcastreceiver; |
| |
| import android.app.Notification; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.app.Service; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.preference.PreferenceManager; |
| import android.provider.Telephony; |
| import android.telephony.SmsCbConstants; |
| import android.telephony.SmsCbMessage; |
| import android.util.Log; |
| |
| /** |
| * This service manages the display and animation of broadcast messages. |
| * Emergency messages display with a flashing animated exclamation mark icon, |
| * and an alert tone is played when the alert is first shown to the user |
| * (but not when the user views a previously received broadcast). |
| */ |
| public class CellBroadcastAlertService extends Service { |
| private static final String TAG = "CellBroadcastAlertService"; |
| |
| /** Identifier for notification ID extra. */ |
| public static final String SMS_CB_NOTIFICATION_ID_EXTRA = |
| "com.android.cellbroadcastreceiver.SMS_CB_NOTIFICATION_ID"; |
| |
| @Override |
| public int onStartCommand(Intent intent, int flags, int startId) { |
| String action = intent.getAction(); |
| if (Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION.equals(action) || |
| Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) { |
| handleCellBroadcastIntent(intent); |
| } else { |
| Log.e(TAG, "Unrecognized intent action: " + action); |
| } |
| stopSelf(); // this service always stops after processing the intent |
| return START_NOT_STICKY; |
| } |
| |
| private void handleCellBroadcastIntent(Intent intent) { |
| Bundle extras = intent.getExtras(); |
| if (extras == null) { |
| Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no extras!"); |
| return; |
| } |
| |
| Object[] pdus = (Object[]) extras.get("pdus"); |
| |
| if (pdus == null || pdus.length < 1) { |
| Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no pdus"); |
| return; |
| } |
| |
| // create message from first PDU |
| SmsCbMessage message = SmsCbMessage.createFromPdu((byte[]) pdus[0]); |
| if (message == null) { |
| Log.e(TAG, "failed to create SmsCbMessage from PDU: " + pdus[0]); |
| return; |
| } |
| |
| // append message bodies from any additional PDUs (GSM only) |
| for (int i = 1; i < pdus.length; i++) { |
| SmsCbMessage nextPage = SmsCbMessage.createFromPdu((byte[]) pdus[i]); |
| if (nextPage != null) { |
| message.appendToBody(nextPage.getMessageBody()); |
| } else { |
| Log.w(TAG, "failed to append to SmsCbMessage from PDU: " + pdus[i]); |
| // continue so we can show the first page of the broadcast |
| } |
| } |
| |
| final CellBroadcastMessage cbm = new CellBroadcastMessage(message); |
| if (!isMessageEnabledByUser(cbm)) { |
| Log.d(TAG, "ignoring alert of type " + cbm.getMessageIdentifier() + |
| " by user preference"); |
| return; |
| } |
| |
| // add notification to the bar |
| addToNotificationBar(cbm); |
| if (cbm.isEmergencyAlertMessage() || CellBroadcastConfigService |
| .isOperatorDefinedEmergencyId(cbm.getMessageIdentifier())) { |
| // start audio/vibration/speech service for emergency alerts |
| Intent audioIntent = new Intent(this, CellBroadcastAlertAudio.class); |
| audioIntent.setAction(CellBroadcastAlertAudio.ACTION_START_ALERT_AUDIO); |
| SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); |
| String duration = prefs.getString(CellBroadcastSettings.KEY_ALERT_SOUND_DURATION, |
| CellBroadcastSettings.ALERT_SOUND_DEFAULT_DURATION); |
| audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_DURATION_EXTRA, |
| Integer.parseInt(duration)); |
| |
| if (prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_SPEECH, true)) { |
| audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY, |
| cbm.getMessageBody()); |
| |
| String language = cbm.getLanguageCode(); |
| if (cbm.isEtwsMessage() && !"ja".equals(language)) { |
| Log.w(TAG, "bad language code for ETWS - using Japanese TTS"); |
| language = "ja"; |
| } else if (cbm.isCmasMessage() && !"en".equals(language)) { |
| Log.w(TAG, "bad language code for CMAS - using English TTS"); |
| language = "en"; |
| } |
| audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_LANGUAGE, |
| language); |
| } |
| startService(audioIntent); |
| } |
| // write to database on a separate service thread |
| Intent dbWriteIntent = new Intent(this, CellBroadcastDatabaseService.class); |
| dbWriteIntent.setAction(CellBroadcastDatabaseService.ACTION_INSERT_NEW_BROADCAST); |
| dbWriteIntent.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, cbm); |
| startService(dbWriteIntent); |
| } |
| |
| /** |
| * Filter out broadcasts on the test channels that the user has not enabled, |
| * and types of notifications that the user is not interested in receiving. |
| * This allows us to enable an entire range of message identifiers in the |
| * radio and not have to explicitly disable the message identifiers for |
| * test broadcasts. In the unlikely event that the default shared preference |
| * values were not initialized in CellBroadcastReceiverApp, the second parameter |
| * to the getBoolean() calls match the default values in res/xml/preferences.xml. |
| * |
| * @param message the message to check |
| * @return true if the user has enabled this message type; false otherwise |
| */ |
| private boolean isMessageEnabledByUser(CellBroadcastMessage message) { |
| switch (message.getMessageIdentifier()) { |
| case SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE: |
| return PreferenceManager.getDefaultSharedPreferences(this) |
| .getBoolean(CellBroadcastSettings.KEY_ENABLE_ETWS_TEST_ALERTS, false); |
| |
| case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: |
| case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: |
| case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: |
| case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: |
| case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: |
| case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: |
| case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: |
| case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: |
| return PreferenceManager.getDefaultSharedPreferences(this) |
| .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_IMMINENT_THREAT_ALERTS, |
| true); |
| |
| case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY: |
| return PreferenceManager.getDefaultSharedPreferences(this) |
| .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, false); |
| |
| case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST: |
| return PreferenceManager.getDefaultSharedPreferences(this) |
| .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_TEST_ALERTS, false); |
| |
| default: |
| return true; |
| } |
| } |
| |
| private void addToNotificationBar(CellBroadcastMessage message) { |
| int channelTitleId = message.getDialogTitleResource(); |
| CharSequence channelName = getText(channelTitleId); |
| String messageBody = message.getMessageBody(); |
| |
| Notification notification = new Notification(R.drawable.stat_color_warning, |
| channelName, System.currentTimeMillis()); |
| |
| int notificationId = CellBroadcastReceiverApp.getCellBroadcastReceiverApp() |
| .getNextNotificationId(); |
| |
| PendingIntent pi = PendingIntent.getActivity(this, 0, createDisplayMessageIntent( |
| this, message, notificationId), 0); |
| |
| notification.setLatestEventInfo(this, channelName, messageBody, pi); |
| |
| if (message.isEmergencyAlertMessage() || CellBroadcastConfigService |
| .isOperatorDefinedEmergencyId(message.getMessageIdentifier())) { |
| // Emergency: open notification immediately |
| notification.fullScreenIntent = pi; |
| // use default notification lights (CellBroadcastAlertAudio plays sound/vibration) |
| notification.defaults = Notification.DEFAULT_LIGHTS; |
| } else { |
| // use default sound/vibration/lights for non-emergency broadcasts |
| notification.defaults = Notification.DEFAULT_ALL; |
| } |
| |
| Log.i(TAG, "addToNotificationBar notificationId: " + notificationId); |
| |
| NotificationManager notificationManager = |
| (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); |
| |
| notificationManager.notify(notificationId, notification); |
| } |
| |
| static Intent createDisplayMessageIntent(Context context, |
| CellBroadcastMessage message, int notificationId) { |
| // Trigger the list activity to fire up a dialog that shows the received messages |
| Intent intent = new Intent(context, CellBroadcastListActivity.class); |
| intent.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, message); |
| intent.putExtra(SMS_CB_NOTIFICATION_ID_EXTRA, notificationId); |
| |
| // This line is needed to make this intent compare differently than the other intents |
| // created here for other messages. Without this line, the PendingIntent always gets the |
| // intent of a previous message and notification. |
| intent.setType(Integer.toString(notificationId)); |
| |
| return intent; |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| return null; // clients can't bind to this service |
| } |
| } |