summaryrefslogtreecommitdiff
path: root/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
blob: dafb3917a2a9842c00dcd7298c4156f3ae51b8ab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/*
 * 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
    }
}