• 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.ActivityManagerNative;
20 import android.app.KeyguardManager;
21 import android.app.Notification;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.app.Service;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.SharedPreferences;
28 import android.os.Bundle;
29 import android.os.IBinder;
30 import android.os.RemoteException;
31 import android.os.UserHandle;
32 import android.preference.PreferenceManager;
33 import android.provider.Telephony;
34 import android.telephony.CarrierConfigManager;
35 import android.telephony.CellBroadcastMessage;
36 import android.telephony.SmsCbCmasInfo;
37 import android.telephony.SmsCbEtwsInfo;
38 import android.telephony.SmsCbLocation;
39 import android.telephony.SmsCbMessage;
40 import android.util.Log;
41 
42 import com.android.cellbroadcastreceiver.CellBroadcastAlertAudio.ToneType;
43 import com.android.cellbroadcastreceiver.CellBroadcastOtherChannelsManager.CellBroadcastChannelRange;
44 
45 import java.util.ArrayList;
46 import java.util.HashSet;
47 import java.util.Locale;
48 
49 /**
50  * This service manages the display and animation of broadcast messages.
51  * Emergency messages display with a flashing animated exclamation mark icon,
52  * and an alert tone is played when the alert is first shown to the user
53  * (but not when the user views a previously received broadcast).
54  */
55 public class CellBroadcastAlertService extends Service {
56     private static final String TAG = "CBAlertService";
57 
58     /** Intent action to display alert dialog/notification, after verifying the alert is new. */
59     static final String SHOW_NEW_ALERT_ACTION = "cellbroadcastreceiver.SHOW_NEW_ALERT";
60 
61     /** Use the same notification ID for non-emergency alerts. */
62     static final int NOTIFICATION_ID = 1;
63 
64     /** Sticky broadcast for latest area info broadcast received. */
65     static final String CB_AREA_INFO_RECEIVED_ACTION =
66             "android.cellbroadcastreceiver.CB_AREA_INFO_RECEIVED";
67 
68     /**
69      *  Container for service category, serial number, location, body hash code, and ETWS primary/
70      *  secondary information for duplication detection.
71      */
72     private static final class MessageServiceCategoryAndScope {
73         private final int mServiceCategory;
74         private final int mSerialNumber;
75         private final SmsCbLocation mLocation;
76         private final int mBodyHash;
77         private final boolean mIsEtwsPrimary;
78 
MessageServiceCategoryAndScope(int serviceCategory, int serialNumber, SmsCbLocation location, int bodyHash, boolean isEtwsPrimary)79         MessageServiceCategoryAndScope(int serviceCategory, int serialNumber,
80                 SmsCbLocation location, int bodyHash, boolean isEtwsPrimary) {
81             mServiceCategory = serviceCategory;
82             mSerialNumber = serialNumber;
83             mLocation = location;
84             mBodyHash = bodyHash;
85             mIsEtwsPrimary = isEtwsPrimary;
86         }
87 
88         @Override
hashCode()89         public int hashCode() {
90             return mLocation.hashCode() + 5 * mServiceCategory + 7 * mSerialNumber + 13 * mBodyHash
91                     + 17 * Boolean.hashCode(mIsEtwsPrimary);
92         }
93 
94         @Override
equals(Object o)95         public boolean equals(Object o) {
96             if (o == this) {
97                 return true;
98             }
99             if (o instanceof MessageServiceCategoryAndScope) {
100                 MessageServiceCategoryAndScope other = (MessageServiceCategoryAndScope) o;
101                 return (mServiceCategory == other.mServiceCategory &&
102                         mSerialNumber == other.mSerialNumber &&
103                         mLocation.equals(other.mLocation) &&
104                         mBodyHash == other.mBodyHash &&
105                         mIsEtwsPrimary == other.mIsEtwsPrimary);
106             }
107             return false;
108         }
109 
110         @Override
toString()111         public String toString() {
112             return "{mServiceCategory: " + mServiceCategory + " serial number: " + mSerialNumber +
113                     " location: " + mLocation.toString() + " body hash: " + mBodyHash +
114                     " mIsEtwsPrimary: " + mIsEtwsPrimary + "}";
115         }
116     }
117 
118     /** Cache of received message IDs, for duplicate message detection. */
119     private static final HashSet<MessageServiceCategoryAndScope> sCmasIdSet =
120             new HashSet<MessageServiceCategoryAndScope>(8);
121 
122     /** Maximum number of message IDs to save before removing the oldest message ID. */
123     private static final int MAX_MESSAGE_ID_SIZE = 65535;
124 
125     /** List of message IDs received, for removing oldest ID when max message IDs are received. */
126     private static final ArrayList<MessageServiceCategoryAndScope> sCmasIdList =
127             new ArrayList<MessageServiceCategoryAndScope>(8);
128 
129     /** Index of message ID to replace with new message ID when max message IDs are received. */
130     private static int sCmasIdListIndex = 0;
131 
132     @Override
onStartCommand(Intent intent, int flags, int startId)133     public int onStartCommand(Intent intent, int flags, int startId) {
134         String action = intent.getAction();
135         if (Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION.equals(action) ||
136                 Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) {
137             handleCellBroadcastIntent(intent);
138         } else if (SHOW_NEW_ALERT_ACTION.equals(action)) {
139             try {
140                 if (UserHandle.myUserId() ==
141                         ActivityManagerNative.getDefault().getCurrentUser().id) {
142                     showNewAlert(intent);
143                 } else {
144                     Log.d(TAG,"Not active user, ignore the alert display");
145                 }
146             } catch (RemoteException e) {
147                 e.printStackTrace();
148             }
149         } else {
150             Log.e(TAG, "Unrecognized intent action: " + action);
151         }
152         return START_NOT_STICKY;
153     }
154 
handleCellBroadcastIntent(Intent intent)155     private void handleCellBroadcastIntent(Intent intent) {
156         Bundle extras = intent.getExtras();
157         if (extras == null) {
158             Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no extras!");
159             return;
160         }
161 
162         SmsCbMessage message = (SmsCbMessage) extras.get("message");
163 
164         if (message == null) {
165             Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no message extra");
166             return;
167         }
168 
169         final CellBroadcastMessage cbm = new CellBroadcastMessage(message);
170 
171         if (!isMessageEnabledByUser(cbm)) {
172             Log.d(TAG, "ignoring alert of type " + cbm.getServiceCategory() +
173                     " by user preference");
174             return;
175         }
176 
177         // If this is an ETWS message, then we want to include the body message to be a factor for
178         // duplication detection. We found that some Japanese carriers send ETWS messages
179         // with the same serial number, therefore the subsequent messages were all ignored.
180         // In the other hand, US carriers have the requirement that only serial number, location,
181         // and category should be used for duplicate detection.
182         int hashCode = message.isEtwsMessage() ? message.getMessageBody().hashCode() : 0;
183 
184         // If this is an ETWS message, we need to include primary/secondary message information to
185         // be a factor for duplication detection as well. Per 3GPP TS 23.041 section 8.2,
186         // duplicate message detection shall be performed independently for primary and secondary
187         // notifications.
188         boolean isEtwsPrimary = false;
189         if (message.isEtwsMessage()) {
190             SmsCbEtwsInfo etwsInfo = message.getEtwsWarningInfo();
191             if (etwsInfo != null) {
192                 isEtwsPrimary = etwsInfo.isPrimary();
193             } else {
194                 Log.w(TAG, "ETWS info is not available.");
195             }
196         }
197 
198         // Check for duplicate message IDs according to CMAS carrier requirements. Message IDs
199         // are stored in volatile memory. If the maximum of 65535 messages is reached, the
200         // message ID of the oldest message is deleted from the list.
201         MessageServiceCategoryAndScope newCmasId = new MessageServiceCategoryAndScope(
202                 message.getServiceCategory(), message.getSerialNumber(), message.getLocation(),
203                 hashCode, isEtwsPrimary);
204 
205         Log.d(TAG, "message ID = " + newCmasId);
206 
207         // Add the new message ID to the list. It's okay if this is a duplicate message ID,
208         // because the list is only used for removing old message IDs from the hash set.
209         if (sCmasIdList.size() < MAX_MESSAGE_ID_SIZE) {
210             sCmasIdList.add(newCmasId);
211         } else {
212             // Get oldest message ID from the list and replace with the new message ID.
213             MessageServiceCategoryAndScope oldestCmasId = sCmasIdList.get(sCmasIdListIndex);
214             sCmasIdList.set(sCmasIdListIndex, newCmasId);
215             Log.d(TAG, "message ID limit reached, removing oldest message ID " + oldestCmasId);
216             // Remove oldest message ID from the set.
217             sCmasIdSet.remove(oldestCmasId);
218             if (++sCmasIdListIndex >= MAX_MESSAGE_ID_SIZE) {
219                 sCmasIdListIndex = 0;
220             }
221         }
222         // Set.add() returns false if message ID has already been added
223         if (!sCmasIdSet.add(newCmasId)) {
224             Log.d(TAG, "ignoring duplicate alert with " + newCmasId);
225             return;
226         }
227 
228         final Intent alertIntent = new Intent(SHOW_NEW_ALERT_ACTION);
229         alertIntent.setClass(this, CellBroadcastAlertService.class);
230         alertIntent.putExtra("message", cbm);
231 
232         // write to database on a background thread
233         new CellBroadcastContentProvider.AsyncCellBroadcastTask(getContentResolver())
234                 .execute(new CellBroadcastContentProvider.CellBroadcastOperation() {
235                     @Override
236                     public boolean execute(CellBroadcastContentProvider provider) {
237                         if (provider.insertNewBroadcast(cbm)) {
238                             // new message, show the alert or notification on UI thread
239                             startService(alertIntent);
240                             return true;
241                         } else {
242                             return false;
243                         }
244                     }
245                 });
246     }
247 
showNewAlert(Intent intent)248     private void showNewAlert(Intent intent) {
249         Bundle extras = intent.getExtras();
250         if (extras == null) {
251             Log.e(TAG, "received SHOW_NEW_ALERT_ACTION with no extras!");
252             return;
253         }
254 
255         CellBroadcastMessage cbm = (CellBroadcastMessage) intent.getParcelableExtra("message");
256 
257         if (cbm == null) {
258             Log.e(TAG, "received SHOW_NEW_ALERT_ACTION with no message extra");
259             return;
260         }
261 
262         if (cbm.isEmergencyAlertMessage()) {
263             // start alert sound / vibration / TTS and display full-screen alert
264             openEmergencyAlertNotification(cbm);
265         } else {
266             // add notification to the bar by passing the list of unread non-emergency
267             // CellBroadcastMessages
268             ArrayList<CellBroadcastMessage> messageList = CellBroadcastReceiverApp
269                     .addNewMessageToList(cbm);
270             addToNotificationBar(cbm, messageList, this, false);
271         }
272     }
273 
274     /**
275      * Filter out broadcasts on the test channels that the user has not enabled,
276      * and types of notifications that the user is not interested in receiving.
277      * This allows us to enable an entire range of message identifiers in the
278      * radio and not have to explicitly disable the message identifiers for
279      * test broadcasts. In the unlikely event that the default shared preference
280      * values were not initialized in CellBroadcastReceiverApp, the second parameter
281      * to the getBoolean() calls match the default values in res/xml/preferences.xml.
282      *
283      * @param message the message to check
284      * @return true if the user has enabled this message type; false otherwise
285      */
isMessageEnabledByUser(CellBroadcastMessage message)286     private boolean isMessageEnabledByUser(CellBroadcastMessage message) {
287 
288         // Check if all emergency alerts are disabled.
289         boolean emergencyAlertEnabled = PreferenceManager.getDefaultSharedPreferences(this).
290                 getBoolean(CellBroadcastSettings.KEY_ENABLE_EMERGENCY_ALERTS, true);
291 
292         // Check if ETWS/CMAS test message is forced to disabled on the device.
293         boolean forceDisableEtwsCmasTest =
294                 CellBroadcastSettings.isFeatureEnabled(this,
295                         CarrierConfigManager.KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false);
296 
297         if (message.isEtwsTestMessage()) {
298             return emergencyAlertEnabled &&
299                     !forceDisableEtwsCmasTest &&
300                     PreferenceManager.getDefaultSharedPreferences(this)
301                     .getBoolean(CellBroadcastSettings.KEY_ENABLE_ETWS_TEST_ALERTS, false);
302         }
303 
304         if (message.isEtwsMessage()) {
305             // ETWS messages.
306             // Turn on/off emergency notifications is the only way to turn on/off ETWS messages.
307             return emergencyAlertEnabled;
308 
309         }
310 
311         if (message.isCmasMessage()) {
312             switch (message.getCmasMessageClass()) {
313                 case SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT:
314                     return emergencyAlertEnabled &&
315                             PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
316                             CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, true);
317 
318                 case SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT:
319                     return emergencyAlertEnabled &&
320                             PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
321                             CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, true);
322 
323                 case SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY:
324                     return emergencyAlertEnabled &&
325                             PreferenceManager.getDefaultSharedPreferences(this)
326                             .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, true);
327 
328                 case SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST:
329                 case SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE:
330                 case SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE:
331                     return emergencyAlertEnabled &&
332                             !forceDisableEtwsCmasTest &&
333                             PreferenceManager.getDefaultSharedPreferences(this)
334                                     .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_TEST_ALERTS,
335                                             false);
336                 default:
337                     return true;    // presidential-level CMAS alerts are always enabled
338             }
339         }
340 
341         if (message.getServiceCategory() == 50) {
342             // save latest area info broadcast for Settings display and send as broadcast
343             CellBroadcastReceiverApp.setLatestAreaInfo(message);
344             Intent intent = new Intent(CB_AREA_INFO_RECEIVED_ACTION);
345             intent.putExtra("message", message);
346             // Send broadcast twice, once for apps that have PRIVILEGED permission and once
347             // for those that have the runtime one
348             sendBroadcastAsUser(intent, UserHandle.ALL,
349                     android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
350             sendBroadcastAsUser(intent, UserHandle.ALL,
351                     android.Manifest.permission.READ_PHONE_STATE);
352             return false;   // area info broadcasts are displayed in Settings status screen
353         }
354 
355         return true;    // other broadcast messages are always enabled
356     }
357 
358     /**
359      * Display a full-screen alert message for emergency alerts.
360      * @param message the alert to display
361      */
openEmergencyAlertNotification(CellBroadcastMessage message)362     private void openEmergencyAlertNotification(CellBroadcastMessage message) {
363         // Acquire a CPU wake lock until the alert dialog and audio start playing.
364         CellBroadcastAlertWakeLock.acquireScreenCpuWakeLock(this);
365 
366         // Close dialogs and window shade
367         Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
368         sendBroadcast(closeDialogs);
369 
370         // start audio/vibration/speech service for emergency alerts
371         Intent audioIntent = new Intent(this, CellBroadcastAlertAudio.class);
372         audioIntent.setAction(CellBroadcastAlertAudio.ACTION_START_ALERT_AUDIO);
373         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
374 
375         ToneType toneType = ToneType.CMAS_DEFAULT;
376         if (message.isEtwsMessage()) {
377             // For ETWS, always vibrate, even in silent mode.
378             audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_VIBRATE_EXTRA, true);
379             audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_ETWS_VIBRATE_EXTRA, true);
380             toneType = ToneType.ETWS_DEFAULT;
381 
382             if (message.getEtwsWarningInfo() != null) {
383                 int warningType = message.getEtwsWarningInfo().getWarningType();
384 
385                 switch (warningType) {
386                     case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE:
387                     case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
388                         toneType = ToneType.EARTHQUAKE;
389                         break;
390                     case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI:
391                         toneType = ToneType.TSUNAMI;
392                         break;
393                     case SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY:
394                         toneType = ToneType.OTHER;
395                         break;
396                 }
397             }
398         } else {
399             // For other alerts, vibration can be disabled in app settings.
400             audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_VIBRATE_EXTRA,
401                     prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_VIBRATE, true));
402             int channel = message.getServiceCategory();
403             ArrayList<CellBroadcastChannelRange> ranges= CellBroadcastOtherChannelsManager.
404                     getInstance().getCellBroadcastChannelRanges(getApplicationContext(),
405                     message.getSubId());
406             if (ranges != null) {
407                 for (CellBroadcastChannelRange range : ranges) {
408                     if (channel >= range.mStartId && channel <= range.mEndId) {
409                         toneType = range.mToneType;
410                         break;
411                     }
412                 }
413             }
414         }
415         audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_TONE_TYPE, toneType);
416 
417         String messageBody = message.getMessageBody();
418 
419         if (prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_SPEECH, true)) {
420             audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY, messageBody);
421 
422             String preferredLanguage = message.getLanguageCode();
423             String defaultLanguage = null;
424             if (message.isEtwsMessage()) {
425                 // Only do TTS for ETWS secondary message.
426                 // There is no text in ETWS primary message. When we construct the ETWS primary
427                 // message, we hardcode "ETWS" as the body hence we don't want to speak that out
428                 // here.
429 
430                 // Also in many cases we see the secondary message comes few milliseconds after
431                 // the primary one. If we play TTS for the primary one, It will be overwritten by
432                 // the secondary one immediately anyway.
433                 if (!message.getEtwsWarningInfo().isPrimary()) {
434                     // Since only Japanese carriers are using ETWS, if there is no language
435                     // specified in the ETWS message, we'll use Japanese as the default language.
436                     defaultLanguage = "ja";
437                 }
438             } else {
439                 // If there is no language specified in the CMAS message, use device's
440                 // default language.
441                 defaultLanguage = Locale.getDefault().getLanguage();
442             }
443 
444             Log.d(TAG, "Preferred language = " + preferredLanguage +
445                     ", Default language = " + defaultLanguage);
446             audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_PREFERRED_LANGUAGE,
447                     preferredLanguage);
448             audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_DEFAULT_LANGUAGE,
449                     defaultLanguage);
450         }
451         startService(audioIntent);
452 
453         // Decide which activity to start based on the state of the keyguard.
454         Class c = CellBroadcastAlertDialog.class;
455         KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
456         if (km.inKeyguardRestrictedInputMode()) {
457             // Use the full screen activity for security.
458             c = CellBroadcastAlertFullScreen.class;
459         }
460 
461         ArrayList<CellBroadcastMessage> messageList = new ArrayList<CellBroadcastMessage>(1);
462         messageList.add(message);
463 
464         Intent alertDialogIntent = createDisplayMessageIntent(this, c, messageList);
465         alertDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
466         startActivity(alertDialogIntent);
467     }
468 
469     /**
470      * Add the new alert to the notification bar (non-emergency alerts), or launch a
471      * high-priority immediate intent for emergency alerts.
472      * @param message the alert to display
473      */
addToNotificationBar(CellBroadcastMessage message, ArrayList<CellBroadcastMessage> messageList, Context context, boolean fromSaveState)474     static void addToNotificationBar(CellBroadcastMessage message,
475                                      ArrayList<CellBroadcastMessage> messageList, Context context,
476                                      boolean fromSaveState) {
477         int channelTitleId = CellBroadcastResources.getDialogTitleResource(message);
478         CharSequence channelName = context.getText(channelTitleId);
479         String messageBody = message.getMessageBody();
480 
481         // Create intent to show the new messages when user selects the notification.
482         Intent intent = createDisplayMessageIntent(context, CellBroadcastAlertDialog.class,
483                 messageList);
484         intent.putExtra(CellBroadcastAlertFullScreen.FROM_NOTIFICATION_EXTRA, true);
485         intent.putExtra(CellBroadcastAlertFullScreen.FROM_SAVE_STATE_NOTIFICATION_EXTRA,
486                 fromSaveState);
487 
488         PendingIntent pi = PendingIntent.getActivity(context, NOTIFICATION_ID, intent,
489                 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
490 
491         // use default sound/vibration/lights for non-emergency broadcasts
492         Notification.Builder builder = new Notification.Builder(context)
493                 .setSmallIcon(R.drawable.ic_notify_alert)
494                 .setTicker(channelName)
495                 .setWhen(System.currentTimeMillis())
496                 .setContentIntent(pi)
497                 .setCategory(Notification.CATEGORY_SYSTEM)
498                 .setPriority(Notification.PRIORITY_HIGH)
499                 .setColor(context.getResources().getColor(R.color.notification_color))
500                 .setVisibility(Notification.VISIBILITY_PUBLIC)
501                 .setDefaults(Notification.DEFAULT_ALL);
502 
503         builder.setDefaults(Notification.DEFAULT_ALL);
504 
505         // increment unread alert count (decremented when user dismisses alert dialog)
506         int unreadCount = messageList.size();
507         if (unreadCount > 1) {
508             // use generic count of unread broadcasts if more than one unread
509             builder.setContentTitle(context.getString(R.string.notification_multiple_title));
510             builder.setContentText(context.getString(R.string.notification_multiple, unreadCount));
511         } else {
512             builder.setContentTitle(channelName).setContentText(messageBody);
513         }
514 
515         NotificationManager notificationManager = NotificationManager.from(context);
516 
517         notificationManager.notify(NOTIFICATION_ID, builder.build());
518     }
519 
createDisplayMessageIntent(Context context, Class intentClass, ArrayList<CellBroadcastMessage> messageList)520     static Intent createDisplayMessageIntent(Context context, Class intentClass,
521             ArrayList<CellBroadcastMessage> messageList) {
522         // Trigger the list activity to fire up a dialog that shows the received messages
523         Intent intent = new Intent(context, intentClass);
524         intent.putParcelableArrayListExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, messageList);
525         return intent;
526     }
527 
528     @Override
onBind(Intent intent)529     public IBinder onBind(Intent intent) {
530         return null;    // clients can't bind to this service
531     }
532 }
533