blob: dafb3917a2a9842c00dcd7298c4156f3ae51b8ab [file] [log] [blame]
/*
* 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
}
}