• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onStartCommand(Intent intent, int flags, int startId)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 
handleCellBroadcastIntent(Intent intent)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      */
isMessageEnabledByUser(CellBroadcastMessage message)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 
addToNotificationBar(CellBroadcastMessage message)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 
createDisplayMessageIntent(Context context, CellBroadcastMessage message, int notificationId)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
onBind(Intent intent)230     public IBinder onBind(Intent intent) {
231         return null;    // clients can't bind to this service
232     }
233 }
234