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