1 /* 2 * Copyright (C) 2016 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.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.app.Activity; 22 import android.app.AlertDialog; 23 import android.app.KeyguardManager; 24 import android.app.NotificationManager; 25 import android.app.PendingIntent; 26 import android.app.RemoteAction; 27 import android.app.StatusBarManager; 28 import android.content.BroadcastReceiver; 29 import android.content.ClipData; 30 import android.content.ClipboardManager; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.SharedPreferences; 35 import android.content.res.Configuration; 36 import android.content.res.Resources; 37 import android.graphics.Point; 38 import android.graphics.drawable.Drawable; 39 import android.os.Bundle; 40 import android.os.Handler; 41 import android.os.Message; 42 import android.os.PowerManager; 43 import android.preference.PreferenceManager; 44 import android.provider.Telephony; 45 import android.telephony.SmsCbCmasInfo; 46 import android.telephony.SmsCbMessage; 47 import android.text.Spannable; 48 import android.text.SpannableString; 49 import android.text.TextUtils; 50 import android.text.method.LinkMovementMethod; 51 import android.text.style.ClickableSpan; 52 import android.text.util.Linkify; 53 import android.util.Log; 54 import android.view.Display; 55 import android.view.Gravity; 56 import android.view.KeyEvent; 57 import android.view.LayoutInflater; 58 import android.view.View; 59 import android.view.ViewGroup; 60 import android.view.Window; 61 import android.view.WindowManager; 62 import android.view.textclassifier.TextClassification; 63 import android.view.textclassifier.TextClassification.Request; 64 import android.view.textclassifier.TextClassifier; 65 import android.view.textclassifier.TextLinks; 66 import android.view.textclassifier.TextLinks.TextLink; 67 import android.widget.ImageView; 68 import android.widget.TextView; 69 import android.widget.Toast; 70 71 import com.android.cellbroadcastreceiver.CellBroadcastChannelManager.CellBroadcastChannelRange; 72 import com.android.internal.annotations.VisibleForTesting; 73 74 import java.lang.annotation.Retention; 75 import java.lang.annotation.RetentionPolicy; 76 import java.lang.reflect.Method; 77 import java.text.SimpleDateFormat; 78 import java.util.ArrayList; 79 import java.util.Arrays; 80 import java.util.Collections; 81 import java.util.Comparator; 82 import java.util.Locale; 83 import java.util.concurrent.atomic.AtomicInteger; 84 85 /** 86 * Custom alert dialog with optional flashing warning icon. 87 * Alert audio and text-to-speech handled by {@link CellBroadcastAlertAudio}. 88 */ 89 public class CellBroadcastAlertDialog extends Activity { 90 91 private static final String TAG = "CellBroadcastAlertDialog"; 92 93 /** Intent extra indicate this intent should not dismiss the notification */ 94 @VisibleForTesting 95 public static final String DISMISS_NOTIFICATION_EXTRA = "dismiss_notification"; 96 97 // Intent extra to identify if notification was sent while trying to move away from the dialog 98 // without acknowledging the dialog 99 static final String FROM_SAVE_STATE_NOTIFICATION_EXTRA = "from_save_state_notification"; 100 101 /** Not link any text. */ 102 private static final int LINK_METHOD_NONE = 0; 103 104 private static final String LINK_METHOD_NONE_STRING = "none"; 105 106 /** Use {@link android.text.util.Linkify} to generate links. */ 107 private static final int LINK_METHOD_LEGACY_LINKIFY = 1; 108 109 private static final String LINK_METHOD_LEGACY_LINKIFY_STRING = "legacy_linkify"; 110 111 /** 112 * Use the machine learning based {@link TextClassifier} to generate links. Will fallback to 113 * {@link #LINK_METHOD_LEGACY_LINKIFY} if not enabled. 114 */ 115 private static final int LINK_METHOD_SMART_LINKIFY = 2; 116 117 private static final String LINK_METHOD_SMART_LINKIFY_STRING = "smart_linkify"; 118 119 /** 120 * Use the machine learning based {@link TextClassifier} to generate links but hiding copy 121 * option. Will fallback to 122 * {@link #LINK_METHOD_LEGACY_LINKIFY} if not enabled. 123 */ 124 private static final int LINK_METHOD_SMART_LINKIFY_NO_COPY = 3; 125 126 private static final String LINK_METHOD_SMART_LINKIFY_NO_COPY_STRING = "smart_linkify_no_copy"; 127 128 129 /** 130 * Text link method 131 * @hide 132 */ 133 @Retention(RetentionPolicy.SOURCE) 134 @IntDef(prefix = "LINK_METHOD_", 135 value = {LINK_METHOD_NONE, LINK_METHOD_LEGACY_LINKIFY, 136 LINK_METHOD_SMART_LINKIFY, LINK_METHOD_SMART_LINKIFY_NO_COPY}) 137 private @interface LinkMethod {} 138 139 140 /** List of cell broadcast messages to display (oldest to newest). */ 141 protected ArrayList<SmsCbMessage> mMessageList; 142 143 /** Whether a CMAS alert other than Presidential Alert was displayed. */ 144 private boolean mShowOptOutDialog; 145 146 /** Length of time for the warning icon to be visible. */ 147 private static final int WARNING_ICON_ON_DURATION_MSEC = 800; 148 149 /** Length of time for the warning icon to be off. */ 150 private static final int WARNING_ICON_OFF_DURATION_MSEC = 800; 151 152 /** Length of time to keep the screen turned on. */ 153 private static final int KEEP_SCREEN_ON_DURATION_MSEC = 60000; 154 155 /** Animation handler for the flashing warning icon (emergency alerts only). */ 156 @VisibleForTesting 157 public AnimationHandler mAnimationHandler = new AnimationHandler(); 158 159 /** Handler to add and remove screen on flags for emergency alerts. */ 160 private final ScreenOffHandler mScreenOffHandler = new ScreenOffHandler(); 161 162 // Show the opt-out dialog 163 private AlertDialog mOptOutDialog; 164 165 /** BroadcastReceiver for screen off events. When screen was off, remove FLAG_TURN_SCREEN_ON to 166 * start from a clean state. Otherwise, the window flags from the first alert will be 167 * automatically applied to the following alerts handled at onNewIntent. 168 */ 169 private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { 170 @Override 171 public void onReceive(Context context, Intent intent){ 172 Log.d(TAG, "onSreenOff: remove FLAG_TURN_SCREEN_ON flag"); 173 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); 174 } 175 }; 176 177 /** 178 * Animation handler for the flashing warning icon (emergency alerts only). 179 */ 180 @VisibleForTesting 181 public class AnimationHandler extends Handler { 182 /** Latest {@code message.what} value for detecting old messages. */ 183 @VisibleForTesting 184 public final AtomicInteger mCount = new AtomicInteger(); 185 186 /** Warning icon state: visible == true, hidden == false. */ 187 @VisibleForTesting 188 public boolean mWarningIconVisible; 189 190 /** The warning icon Drawable. */ 191 private Drawable mWarningIcon; 192 193 /** The View containing the warning icon. */ 194 private ImageView mWarningIconView; 195 196 /** Package local constructor (called from outer class). */ AnimationHandler()197 AnimationHandler() {} 198 199 /** Start the warning icon animation. */ 200 @VisibleForTesting startIconAnimation(int subId)201 public void startIconAnimation(int subId) { 202 if (!initDrawableAndImageView(subId)) { 203 return; // init failure 204 } 205 mWarningIconVisible = true; 206 mWarningIconView.setVisibility(View.VISIBLE); 207 updateIconState(); 208 queueAnimateMessage(); 209 } 210 211 /** Stop the warning icon animation. */ 212 @VisibleForTesting stopIconAnimation()213 public void stopIconAnimation() { 214 // Increment the counter so the handler will ignore the next message. 215 mCount.incrementAndGet(); 216 } 217 218 /** Update the visibility of the warning icon. */ updateIconState()219 private void updateIconState() { 220 mWarningIconView.setImageAlpha(mWarningIconVisible ? 255 : 0); 221 mWarningIconView.invalidateDrawable(mWarningIcon); 222 } 223 224 /** Queue a message to animate the warning icon. */ queueAnimateMessage()225 private void queueAnimateMessage() { 226 int msgWhat = mCount.incrementAndGet(); 227 sendEmptyMessageDelayed(msgWhat, mWarningIconVisible ? WARNING_ICON_ON_DURATION_MSEC 228 : WARNING_ICON_OFF_DURATION_MSEC); 229 } 230 231 @Override handleMessage(Message msg)232 public void handleMessage(Message msg) { 233 if (msg.what == mCount.get()) { 234 mWarningIconVisible = !mWarningIconVisible; 235 updateIconState(); 236 queueAnimateMessage(); 237 } 238 } 239 240 /** 241 * Initialize the Drawable and ImageView fields. 242 * 243 * @param subId Subscription index 244 * 245 * @return true if successful; false if any field failed to initialize 246 */ initDrawableAndImageView(int subId)247 private boolean initDrawableAndImageView(int subId) { 248 if (mWarningIcon == null) { 249 try { 250 mWarningIcon = CellBroadcastSettings.getResources(getApplicationContext(), 251 subId).getDrawable(R.drawable.ic_warning_googred); 252 } catch (Resources.NotFoundException e) { 253 Log.e(TAG, "warning icon resource not found", e); 254 return false; 255 } 256 } 257 if (mWarningIconView == null) { 258 mWarningIconView = (ImageView) findViewById(R.id.icon); 259 if (mWarningIconView != null) { 260 mWarningIconView.setImageDrawable(mWarningIcon); 261 } else { 262 Log.e(TAG, "failed to get ImageView for warning icon"); 263 return false; 264 } 265 } 266 return true; 267 } 268 } 269 270 /** 271 * Handler to add {@code FLAG_KEEP_SCREEN_ON} for emergency alerts. After a short delay, 272 * remove the flag so the screen can turn off to conserve the battery. 273 */ 274 private class ScreenOffHandler extends Handler { 275 /** Latest {@code message.what} value for detecting old messages. */ 276 private final AtomicInteger mCount = new AtomicInteger(); 277 278 /** Package local constructor (called from outer class). */ ScreenOffHandler()279 ScreenOffHandler() {} 280 281 /** Add screen on window flags and queue a delayed message to remove them later. */ startScreenOnTimer(@onNull SmsCbMessage message)282 void startScreenOnTimer(@NonNull SmsCbMessage message) { 283 // if screenOnDuration in milliseconds. if set to 0, do not turn screen on. 284 int screenOnDuration = KEEP_SCREEN_ON_DURATION_MSEC; 285 CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager( 286 getApplicationContext(), message.getSubscriptionId()); 287 CellBroadcastChannelRange range = channelManager 288 .getCellBroadcastChannelRangeFromMessage(message); 289 if (range!= null) { 290 screenOnDuration = range.mScreenOnDuration; 291 } 292 if (screenOnDuration == 0) { 293 Log.d(TAG, "screenOnDuration set to 0, do not turn screen on"); 294 return; 295 } 296 addWindowFlags(); 297 int msgWhat = mCount.incrementAndGet(); 298 removeMessages(msgWhat - 1); // Remove previous message, if any. 299 sendEmptyMessageDelayed(msgWhat, screenOnDuration); 300 Log.d(TAG, "added FLAG_KEEP_SCREEN_ON, queued screen off message id " + msgWhat); 301 } 302 303 /** Remove the screen on window flags and any queued screen off message. */ stopScreenOnTimer()304 void stopScreenOnTimer() { 305 removeMessages(mCount.get()); 306 clearWindowFlags(); 307 } 308 309 /** Set the screen on window flags. */ addWindowFlags()310 private void addWindowFlags() { 311 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON 312 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 313 } 314 315 /** 316 * Clear the keep screen on window flags in order for powersaving but keep TURN_ON_SCREEN_ON 317 * to make sure next wake up still turn screen on without unintended onStop triggered at 318 * the beginning. 319 */ clearWindowFlags()320 private void clearWindowFlags() { 321 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 322 } 323 324 @Override handleMessage(Message msg)325 public void handleMessage(Message msg) { 326 int msgWhat = msg.what; 327 if (msgWhat == mCount.get()) { 328 clearWindowFlags(); 329 Log.d(TAG, "removed FLAG_KEEP_SCREEN_ON with id " + msgWhat); 330 } else { 331 Log.e(TAG, "discarding screen off message with id " + msgWhat); 332 } 333 } 334 } 335 336 Comparator<SmsCbMessage> mPriorityBasedComparator = (Comparator) (o1, o2) -> { 337 boolean isPresidentialAlert1 = 338 ((SmsCbMessage) o1).isCmasMessage() 339 && ((SmsCbMessage) o1).getCmasWarningInfo() 340 .getMessageClass() == SmsCbCmasInfo 341 .CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT; 342 boolean isPresidentialAlert2 = 343 ((SmsCbMessage) o2).isCmasMessage() 344 && ((SmsCbMessage) o2).getCmasWarningInfo() 345 .getMessageClass() == SmsCbCmasInfo 346 .CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT; 347 if (isPresidentialAlert1 ^ isPresidentialAlert2) { 348 return isPresidentialAlert1 ? 1 : -1; 349 } 350 Long time1 = new Long(((SmsCbMessage) o1).getReceivedTime()); 351 Long time2 = new Long(((SmsCbMessage) o2).getReceivedTime()); 352 return time2.compareTo(time1); 353 }; 354 355 @Override onCreate(Bundle savedInstanceState)356 protected void onCreate(Bundle savedInstanceState) { 357 super.onCreate(savedInstanceState); 358 // if this is only to dismiss any pending alert dialog 359 if (getIntent().getBooleanExtra(CellBroadcastAlertService.DISMISS_DIALOG, false)) { 360 dismissAllFromNotification(getIntent()); 361 return; 362 } 363 364 final Window win = getWindow(); 365 366 // We use a custom title, so remove the standard dialog title bar 367 win.requestFeature(Window.FEATURE_NO_TITLE); 368 369 // Full screen alerts display above the keyguard and when device is locked. 370 win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN 371 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 372 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); 373 374 // Disable home button when alert dialog is showing if mute_by_physical_button is false. 375 if (!CellBroadcastSettings.getResourcesForDefaultSubId(getApplicationContext()) 376 .getBoolean(R.bool.mute_by_physical_button) && !CellBroadcastSettings 377 .getResourcesForDefaultSubId(getApplicationContext()) 378 .getBoolean(R.bool.disable_status_bar)) { 379 final View decorView = win.getDecorView(); 380 decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); 381 } 382 383 // Initialize the view. 384 LayoutInflater inflater = LayoutInflater.from(this); 385 setContentView(inflater.inflate(R.layout.cell_broadcast_alert, null)); 386 387 findViewById(R.id.dismissButton).setOnClickListener(v -> dismiss()); 388 389 // Get message list from saved Bundle or from Intent. 390 if (savedInstanceState != null) { 391 Log.d(TAG, "onCreate getting message list from saved instance state"); 392 mMessageList = savedInstanceState.getParcelableArrayList( 393 CellBroadcastAlertService.SMS_CB_MESSAGE_EXTRA); 394 } else { 395 Log.d(TAG, "onCreate getting message list from intent"); 396 Intent intent = getIntent(); 397 mMessageList = intent.getParcelableArrayListExtra( 398 CellBroadcastAlertService.SMS_CB_MESSAGE_EXTRA); 399 400 // If we were started from a notification, dismiss it. 401 clearNotification(intent); 402 } 403 404 registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); 405 406 if (mMessageList == null || mMessageList.size() == 0) { 407 Log.e(TAG, "onCreate failed as message list is null or empty"); 408 finish(); 409 } else { 410 Log.d(TAG, "onCreate loaded message list of size " + mMessageList.size()); 411 412 // For emergency alerts, keep screen on so the user can read it 413 SmsCbMessage message = getLatestMessage(); 414 415 if (message == null) { 416 Log.e(TAG, "message is null"); 417 finish(); 418 return; 419 } 420 421 CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager( 422 this, message.getSubscriptionId()); 423 if (channelManager.isEmergencyMessage(message)) { 424 Log.d(TAG, "onCreate setting screen on timer for emergency alert for sub " 425 + message.getSubscriptionId()); 426 mScreenOffHandler.startScreenOnTimer(message); 427 } 428 429 setFinishAlertOnTouchOutside(); 430 431 updateAlertText(message); 432 433 Resources res = CellBroadcastSettings.getResources(getApplicationContext(), 434 message.getSubscriptionId()); 435 if (res.getBoolean(R.bool.enable_text_copy)) { 436 TextView textView = findViewById(R.id.message); 437 if (textView != null) { 438 textView.setOnLongClickListener(v -> copyMessageToClipboard(message, 439 getApplicationContext())); 440 } 441 } 442 } 443 } 444 445 @Override onStart()446 public void onStart() { 447 super.onStart(); 448 getWindow().addSystemFlags( 449 android.view.WindowManager.LayoutParams 450 .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 451 } 452 453 /** 454 * Start animating warning icon. 455 */ 456 @Override 457 @VisibleForTesting onResume()458 public void onResume() { 459 super.onResume(); 460 setWindowBottom(); 461 setMaxHeightScrollView(); 462 SmsCbMessage message = getLatestMessage(); 463 if (message != null) { 464 int subId = message.getSubscriptionId(); 465 CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(this, 466 subId); 467 CellBroadcastChannelRange range = channelManager 468 .getCellBroadcastChannelRangeFromMessage(message); 469 if (channelManager.isEmergencyMessage(message) 470 && (range!= null && range.mDisplayIcon)) { 471 mAnimationHandler.startIconAnimation(subId); 472 } 473 } 474 // Some LATAM carriers mandate to disable navigation bars, quick settings etc when alert 475 // dialog is showing. This is to make sure users to ack the alert before switching to 476 // other activities. 477 setStatusBarDisabledIfNeeded(true); 478 } 479 480 /** 481 * Stop animating warning icon. 482 */ 483 @Override 484 @VisibleForTesting onPause()485 public void onPause() { 486 Log.d(TAG, "onPause called"); 487 mAnimationHandler.stopIconAnimation(); 488 setStatusBarDisabledIfNeeded(false); 489 super.onPause(); 490 } 491 492 @Override onUserLeaveHint()493 protected void onUserLeaveHint() { 494 Log.d(TAG, "onUserLeaveHint called"); 495 // When the activity goes in background (eg. clicking Home button, dismissed by outside 496 // touch if enabled), send notification. 497 // Avoid doing this when activity will be recreated because of orientation change or if 498 // screen goes off 499 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 500 ArrayList<SmsCbMessage> messageList = getNewMessageListIfNeeded(mMessageList, 501 CellBroadcastReceiverApp.getNewMessageList()); 502 SmsCbMessage latestMessage = (messageList == null || (messageList.size() < 1)) ? null 503 : messageList.get(messageList.size() - 1); 504 505 if (!(isChangingConfigurations() || latestMessage == null) && pm.isScreenOn()) { 506 Log.d(TAG, "call addToNotificationBar when activity goes in background"); 507 CellBroadcastAlertService.addToNotificationBar(latestMessage, messageList, 508 getApplicationContext(), true, true, false); 509 } 510 super.onUserLeaveHint(); 511 } 512 513 @Override onWindowFocusChanged(boolean hasFocus)514 public void onWindowFocusChanged(boolean hasFocus) { 515 super.onWindowFocusChanged(hasFocus); 516 517 if (hasFocus) { 518 Configuration config = getResources().getConfiguration(); 519 setPictogramAreaLayout(config.orientation); 520 } 521 } 522 523 @Override onConfigurationChanged(Configuration newConfig)524 public void onConfigurationChanged(Configuration newConfig) { 525 super.onConfigurationChanged(newConfig); 526 setPictogramAreaLayout(newConfig.orientation); 527 } 528 setWindowBottom()529 private void setWindowBottom() { 530 // some OEMs require that the alert window is moved to the bottom of the screen to avoid 531 // blocking other screen content 532 if (getResources().getBoolean(R.bool.alert_dialog_bottom)) { 533 Window window = getWindow(); 534 WindowManager.LayoutParams params = window.getAttributes(); 535 params.height = WindowManager.LayoutParams.WRAP_CONTENT; 536 params.gravity = params.gravity | Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; 537 params.verticalMargin = 0; 538 window.setAttributes(params); 539 } 540 } 541 542 /** Returns the currently displayed message. */ getLatestMessage()543 SmsCbMessage getLatestMessage() { 544 int index = mMessageList.size() - 1; 545 if (index >= 0) { 546 return mMessageList.get(index); 547 } else { 548 Log.d(TAG, "getLatestMessage returns null"); 549 return null; 550 } 551 } 552 553 /** Removes and returns the currently displayed message. */ removeLatestMessage()554 private SmsCbMessage removeLatestMessage() { 555 int index = mMessageList.size() - 1; 556 if (index >= 0) { 557 return mMessageList.remove(index); 558 } else { 559 return null; 560 } 561 } 562 563 /** 564 * Save the list of messages so the state can be restored later. 565 * @param outState Bundle in which to place the saved state. 566 */ 567 @Override onSaveInstanceState(Bundle outState)568 protected void onSaveInstanceState(Bundle outState) { 569 super.onSaveInstanceState(outState); 570 outState.putParcelableArrayList( 571 CellBroadcastAlertService.SMS_CB_MESSAGE_EXTRA, mMessageList); 572 } 573 574 /** 575 * Get link method 576 * 577 * @param subId Subscription index 578 * @return The link method 579 */ getLinkMethod(int subId)580 private @LinkMethod int getLinkMethod(int subId) { 581 Resources res = CellBroadcastSettings.getResources(getApplicationContext(), subId); 582 switch (res.getString(R.string.link_method)) { 583 case LINK_METHOD_NONE_STRING: return LINK_METHOD_NONE; 584 case LINK_METHOD_LEGACY_LINKIFY_STRING: return LINK_METHOD_LEGACY_LINKIFY; 585 case LINK_METHOD_SMART_LINKIFY_STRING: return LINK_METHOD_SMART_LINKIFY; 586 case LINK_METHOD_SMART_LINKIFY_NO_COPY_STRING: return LINK_METHOD_SMART_LINKIFY_NO_COPY; 587 } 588 return LINK_METHOD_NONE; 589 } 590 591 /** 592 * Add URL links to the applicable texts. 593 * 594 * @param textView Text view 595 * @param messageText The text string of the message 596 * @param linkMethod Link method 597 */ addLinks(@onNull TextView textView, @NonNull String messageText, @LinkMethod int linkMethod)598 private void addLinks(@NonNull TextView textView, @NonNull String messageText, 599 @LinkMethod int linkMethod) { 600 if (linkMethod == LINK_METHOD_LEGACY_LINKIFY) { 601 Spannable text = new SpannableString(messageText); 602 Linkify.addLinks(text, Linkify.ALL); 603 textView.setMovementMethod(LinkMovementMethod.getInstance()); 604 textView.setText(text); 605 } else if (linkMethod == LINK_METHOD_SMART_LINKIFY 606 || linkMethod == LINK_METHOD_SMART_LINKIFY_NO_COPY) { 607 // Text classification cannot be run in the main thread. 608 new Thread(() -> { 609 final TextClassifier classifier = textView.getTextClassifier(); 610 611 TextClassifier.EntityConfig entityConfig = 612 new TextClassifier.EntityConfig.Builder() 613 .setIncludedTypes(Arrays.asList( 614 TextClassifier.TYPE_URL, 615 TextClassifier.TYPE_EMAIL, 616 TextClassifier.TYPE_PHONE, 617 TextClassifier.TYPE_ADDRESS, 618 TextClassifier.TYPE_FLIGHT_NUMBER)) 619 .setExcludedTypes(Arrays.asList( 620 TextClassifier.TYPE_DATE, 621 TextClassifier.TYPE_DATE_TIME)) 622 .build(); 623 624 TextLinks.Request request = new TextLinks.Request.Builder(messageText) 625 .setEntityConfig(entityConfig) 626 .build(); 627 Spannable text; 628 if (linkMethod == LINK_METHOD_SMART_LINKIFY) { 629 text = new SpannableString(messageText); 630 // Add links to the spannable text. 631 classifier.generateLinks(request).apply( 632 text, TextLinks.APPLY_STRATEGY_REPLACE, null); 633 } else { 634 TextLinks textLinks = classifier.generateLinks(request); 635 // Add links to the spannable text. 636 text = applyTextLinksToSpannable(messageText, textLinks, classifier); 637 } 638 // UI can be only updated in the main thread. 639 runOnUiThread(() -> { 640 textView.setMovementMethod(LinkMovementMethod.getInstance()); 641 textView.setText(text); 642 }); 643 }).start(); 644 } 645 } 646 applyTextLinksToSpannable(String text, TextLinks textLinks, TextClassifier textClassifier)647 private Spannable applyTextLinksToSpannable(String text, TextLinks textLinks, 648 TextClassifier textClassifier) { 649 Spannable result = new SpannableString(text); 650 for (TextLink link : textLinks.getLinks()) { 651 TextClassification textClassification = textClassifier.classifyText( 652 new Request.Builder( 653 text, 654 link.getStart(), 655 link.getEnd()) 656 .build()); 657 if (textClassification.getActions().isEmpty()) { 658 continue; 659 } 660 RemoteAction remoteAction = textClassification.getActions().get(0); 661 result.setSpan(new RemoteActionSpan(remoteAction), link.getStart(), link.getEnd(), 662 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 663 } 664 return result; 665 } 666 667 private static class RemoteActionSpan extends ClickableSpan { 668 private final RemoteAction mRemoteAction; RemoteActionSpan(RemoteAction remoteAction)669 private RemoteActionSpan(RemoteAction remoteAction) { 670 mRemoteAction = remoteAction; 671 } 672 @Override onClick(@onNull View view)673 public void onClick(@NonNull View view) { 674 try { 675 mRemoteAction.getActionIntent().send(); 676 } catch (PendingIntent.CanceledException e) { 677 Log.e(TAG, "Failed to start the pendingintent."); 678 } 679 } 680 } 681 682 /** 683 * If the carrier or country is configured to show the alert dialog title text in the 684 * language matching the message, this method returns the string in that language. Otherwise 685 * this method returns the string in the device's current language 686 * 687 * @param resId resource Id 688 * @param res Resources for the subId 689 * @param languageCode the ISO-639-1 language code for this message, or null if unspecified 690 */ overrideTranslation(int resId, Resources res, String languageCode)691 private String overrideTranslation(int resId, Resources res, String languageCode) { 692 if (!TextUtils.isEmpty(languageCode) 693 && res.getBoolean(R.bool.override_alert_title_language_to_match_message_locale)) { 694 // TODO change resources to locale from message 695 Configuration conf = res.getConfiguration(); 696 conf = new Configuration(conf); 697 conf.setLocale(new Locale(languageCode)); 698 Context localizedContext = getApplicationContext().createConfigurationContext(conf); 699 return localizedContext.getResources().getText(resId).toString(); 700 } else { 701 return res.getText(resId).toString(); 702 } 703 } 704 705 /** 706 * Update alert text when a new emergency alert arrives. 707 * @param message CB message which is used to update alert text. 708 */ updateAlertText(@onNull SmsCbMessage message)709 private void updateAlertText(@NonNull SmsCbMessage message) { 710 if (message == null) { 711 return; 712 } 713 Context context = getApplicationContext(); 714 int titleId = CellBroadcastResources.getDialogTitleResource(context, message); 715 716 Resources res = CellBroadcastSettings.getResourcesByOperator(context, 717 message.getSubscriptionId(), 718 CellBroadcastReceiver.getRoamingOperatorSupported(context)); 719 720 CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager( 721 this, message.getSubscriptionId()); 722 CellBroadcastChannelRange range = channelManager 723 .getCellBroadcastChannelRangeFromMessage(message); 724 String languageCode; 725 if (range != null && !TextUtils.isEmpty(range.mLanguageCode)) { 726 languageCode = range.mLanguageCode; 727 } else { 728 languageCode = message.getLanguageCode(); 729 } 730 731 String title = overrideTranslation(titleId, res, languageCode); 732 TextView titleTextView = findViewById(R.id.alertTitle); 733 734 if (titleTextView != null) { 735 String timeFormat = res.getString(R.string.date_time_format); 736 if (!TextUtils.isEmpty(timeFormat)) { 737 titleTextView.setSingleLine(false); 738 title += "\n" + new SimpleDateFormat(timeFormat).format(message.getReceivedTime()); 739 } 740 setTitle(title); 741 titleTextView.setText(title); 742 } 743 744 TextView textView = findViewById(R.id.message); 745 String messageText = message.getMessageBody(); 746 if (textView != null && messageText != null) { 747 int linkMethod = getLinkMethod(message.getSubscriptionId()); 748 if (linkMethod != LINK_METHOD_NONE) { 749 addLinks(textView, messageText, linkMethod); 750 } else { 751 // Do not add any link to the message text. 752 textView.setText(messageText); 753 } 754 } 755 756 String dismissButtonText = getString(R.string.button_dismiss); 757 758 if (mMessageList.size() > 1) { 759 dismissButtonText += " (1/" + mMessageList.size() + ")"; 760 } 761 762 ((TextView) findViewById(R.id.dismissButton)).setText(dismissButtonText); 763 764 765 setPictogram(context, message); 766 } 767 768 /** 769 * Set pictogram image 770 * @param context 771 * @param message 772 */ setPictogram(Context context, SmsCbMessage message)773 private void setPictogram(Context context, SmsCbMessage message) { 774 int resId = CellBroadcastResources.getDialogPictogramResource(context, message); 775 ImageView image = findViewById(R.id.pictogramImage); 776 if (resId != -1) { 777 image.setImageResource(resId); 778 image.setVisibility(View.VISIBLE); 779 } else { 780 image.setVisibility(View.GONE); 781 } 782 } 783 784 /** 785 * Set pictogram to match orientation 786 * 787 * @param orientation The orientation of the pictogram. 788 */ setPictogramAreaLayout(int orientation)789 private void setPictogramAreaLayout(int orientation) { 790 ImageView image = findViewById(R.id.pictogramImage); 791 if (image.getVisibility() == View.VISIBLE) { 792 ViewGroup.LayoutParams params = image.getLayoutParams(); 793 794 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 795 Display display = getWindowManager().getDefaultDisplay(); 796 Point point = new Point(); 797 display.getSize(point); 798 params.width = (int) (point.x * 0.3); 799 params.height = (int) (point.y * 0.3); 800 } else { 801 params.width = ViewGroup.LayoutParams.WRAP_CONTENT; 802 params.height = ViewGroup.LayoutParams.WRAP_CONTENT; 803 } 804 805 image.setLayoutParams(params); 806 } 807 } 808 setMaxHeightScrollView()809 private void setMaxHeightScrollView() { 810 int contentPanelMaxHeight = getResources().getDimensionPixelSize( 811 R.dimen.alert_dialog_maxheight_content_panel); 812 if (contentPanelMaxHeight > 0) { 813 CustomHeightScrollView scrollView = (CustomHeightScrollView) findViewById( 814 R.id.scrollView); 815 if (scrollView != null) { 816 scrollView.setMaximumHeight(contentPanelMaxHeight); 817 } 818 } 819 } 820 821 /** 822 * Called by {@link CellBroadcastAlertService} to add a new alert to the stack. 823 * @param intent The new intent containing one or more {@link SmsCbMessage}. 824 */ 825 @Override 826 @VisibleForTesting onNewIntent(Intent intent)827 public void onNewIntent(Intent intent) { 828 if (intent.getBooleanExtra(CellBroadcastAlertService.DISMISS_DIALOG, false)) { 829 dismissAllFromNotification(intent); 830 return; 831 } 832 ArrayList<SmsCbMessage> newMessageList = intent.getParcelableArrayListExtra( 833 CellBroadcastAlertService.SMS_CB_MESSAGE_EXTRA); 834 if (newMessageList != null) { 835 if (intent.getBooleanExtra(FROM_SAVE_STATE_NOTIFICATION_EXTRA, false)) { 836 mMessageList = newMessageList; 837 } else { 838 // remove the duplicate messages 839 for (SmsCbMessage message : newMessageList) { 840 mMessageList.removeIf( 841 msg -> msg.getReceivedTime() == message.getReceivedTime()); 842 } 843 mMessageList.addAll(newMessageList); 844 if (CellBroadcastSettings.getResourcesForDefaultSubId(getApplicationContext()) 845 .getBoolean(R.bool.show_cmas_messages_in_priority_order)) { 846 // Sort message list to show messages in a different order than received by 847 // prioritizing them. Presidential Alert only has top priority. 848 Collections.sort(mMessageList, mPriorityBasedComparator); 849 } 850 } 851 Log.d(TAG, "onNewIntent called with message list of size " + newMessageList.size()); 852 853 // For emergency alerts, keep screen on so the user can read it 854 SmsCbMessage message = getLatestMessage(); 855 if (message != null) { 856 CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager( 857 this, message.getSubscriptionId()); 858 if (channelManager.isEmergencyMessage(message)) { 859 Log.d(TAG, "onCreate setting screen on timer for emergency alert for sub " 860 + message.getSubscriptionId()); 861 mScreenOffHandler.startScreenOnTimer(message); 862 } 863 } 864 865 hideOptOutDialog(); // Hide opt-out dialog when new alert coming 866 setFinishAlertOnTouchOutside(); 867 updateAlertText(getLatestMessage()); 868 // If the new intent was sent from a notification, dismiss it. 869 clearNotification(intent); 870 } else { 871 Log.e(TAG, "onNewIntent called without SMS_CB_MESSAGE_EXTRA, ignoring"); 872 } 873 } 874 875 /** 876 * Try to cancel any notification that may have started this activity. 877 * @param intent Intent containing extras used to identify if notification needs to be cleared 878 */ clearNotification(Intent intent)879 private void clearNotification(Intent intent) { 880 if (intent.getBooleanExtra(DISMISS_NOTIFICATION_EXTRA, false)) { 881 NotificationManager notificationManager = 882 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 883 notificationManager.cancel(CellBroadcastAlertService.NOTIFICATION_ID); 884 CellBroadcastReceiverApp.clearNewMessageList(); 885 } 886 } 887 888 /** 889 * This will be called when users swipe away the notification, this will 890 * 1. dismiss all foreground dialog, stop animating warning icon and stop the 891 * {@link CellBroadcastAlertAudio} service. 892 * 2. Does not mark message read. 893 */ dismissAllFromNotification(Intent intent)894 public void dismissAllFromNotification(Intent intent) { 895 Log.d(TAG, "dismissAllFromNotification"); 896 // Stop playing alert sound/vibration/speech (if started) 897 stopService(new Intent(this, CellBroadcastAlertAudio.class)); 898 // Cancel any pending alert reminder 899 CellBroadcastAlertReminder.cancelAlertReminder(); 900 // Remove the all current showing alert message from the list. 901 if (mMessageList != null) { 902 mMessageList.clear(); 903 } 904 // clear notifications. 905 clearNotification(intent); 906 // Remove pending screen-off messages (animation messages are removed in onPause()). 907 mScreenOffHandler.stopScreenOnTimer(); 908 finish(); 909 } 910 911 /** 912 * Stop animating warning icon and stop the {@link CellBroadcastAlertAudio} 913 * service if necessary. 914 */ 915 @VisibleForTesting dismiss()916 public void dismiss() { 917 Log.d(TAG, "dismiss"); 918 // Stop playing alert sound/vibration/speech (if started) 919 stopService(new Intent(this, CellBroadcastAlertAudio.class)); 920 921 // Cancel any pending alert reminder 922 CellBroadcastAlertReminder.cancelAlertReminder(); 923 924 // Remove the current alert message from the list. 925 SmsCbMessage lastMessage = removeLatestMessage(); 926 if (lastMessage == null) { 927 Log.e(TAG, "dismiss() called with empty message list!"); 928 finish(); 929 return; 930 } 931 932 // Remove the read message from the notification bar. 933 // e.g, read the message from emergency alert history, need to update the notification bar. 934 removeReadMessageFromNotificationBar(lastMessage, getApplicationContext()); 935 936 // Mark the alert as read. 937 final long deliveryTime = lastMessage.getReceivedTime(); 938 939 // Mark broadcast as read on a background thread. 940 new CellBroadcastContentProvider.AsyncCellBroadcastTask(getContentResolver()) 941 .execute((CellBroadcastContentProvider.CellBroadcastOperation) provider 942 -> provider.markBroadcastRead(Telephony.CellBroadcasts.DELIVERY_TIME, 943 deliveryTime)); 944 945 // Set the opt-out dialog flag if this is a CMAS alert (other than Always-on alert e.g, 946 // Presidential alert). 947 CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager( 948 getApplicationContext(), 949 lastMessage.getSubscriptionId()); 950 CellBroadcastChannelRange range = channelManager 951 .getCellBroadcastChannelRangeFromMessage(lastMessage); 952 953 if (!neverShowOptOutDialog(lastMessage.getSubscriptionId()) && range != null 954 && !range.mAlwaysOn) { 955 mShowOptOutDialog = true; 956 } 957 958 // If there are older emergency alerts to display, update the alert text and return. 959 SmsCbMessage nextMessage = getLatestMessage(); 960 if (nextMessage != null) { 961 setFinishAlertOnTouchOutside(); 962 updateAlertText(nextMessage); 963 int subId = nextMessage.getSubscriptionId(); 964 if (channelManager.isEmergencyMessage(nextMessage) 965 && (range!= null && range.mDisplayIcon)) { 966 mAnimationHandler.startIconAnimation(subId); 967 } else { 968 mAnimationHandler.stopIconAnimation(); 969 } 970 return; 971 } 972 973 // Remove pending screen-off messages (animation messages are removed in onPause()). 974 mScreenOffHandler.stopScreenOnTimer(); 975 976 // Show opt-in/opt-out dialog when the first CMAS alert is received. 977 if (mShowOptOutDialog) { 978 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 979 if (prefs.getBoolean(CellBroadcastSettings.KEY_SHOW_CMAS_OPT_OUT_DIALOG, true)) { 980 // Clear the flag so the user will only see the opt-out dialog once. 981 prefs.edit().putBoolean(CellBroadcastSettings.KEY_SHOW_CMAS_OPT_OUT_DIALOG, false) 982 .apply(); 983 984 KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); 985 if (km.inKeyguardRestrictedInputMode()) { 986 Log.d(TAG, "Showing opt-out dialog in new activity (secure keyguard)"); 987 Intent intent = new Intent(this, CellBroadcastOptOutActivity.class); 988 startActivity(intent); 989 } else { 990 Log.d(TAG, "Showing opt-out dialog in current activity"); 991 mOptOutDialog = CellBroadcastOptOutActivity.showOptOutDialog(this); 992 return; // don't call finish() until user dismisses the dialog 993 } 994 } 995 } 996 finish(); 997 } 998 999 @Override onDestroy()1000 public void onDestroy() { 1001 try { 1002 unregisterReceiver(mScreenOffReceiver); 1003 } catch (IllegalArgumentException e) { 1004 Log.e(TAG, "Unregister Receiver fail", e); 1005 } 1006 super.onDestroy(); 1007 } 1008 1009 @Override onKeyDown(int keyCode, KeyEvent event)1010 public boolean onKeyDown(int keyCode, KeyEvent event) { 1011 Log.d(TAG, "onKeyDown: " + event); 1012 SmsCbMessage message = getLatestMessage(); 1013 if (message != null && CellBroadcastSettings.getResources(getApplicationContext(), 1014 message.getSubscriptionId()).getBoolean(R.bool.mute_by_physical_button)) { 1015 switch (event.getKeyCode()) { 1016 // Volume keys and camera keys mute the alert sound/vibration (except ETWS). 1017 case KeyEvent.KEYCODE_VOLUME_UP: 1018 case KeyEvent.KEYCODE_VOLUME_DOWN: 1019 case KeyEvent.KEYCODE_VOLUME_MUTE: 1020 case KeyEvent.KEYCODE_CAMERA: 1021 case KeyEvent.KEYCODE_FOCUS: 1022 // Stop playing alert sound/vibration/speech (if started) 1023 stopService(new Intent(this, CellBroadcastAlertAudio.class)); 1024 return true; 1025 1026 default: 1027 break; 1028 } 1029 return super.onKeyDown(keyCode, event); 1030 } else { 1031 if (event.getKeyCode() == KeyEvent.KEYCODE_POWER) { 1032 // TODO: do something to prevent screen off 1033 } 1034 // Disable all physical keys if mute_by_physical_button is false 1035 return true; 1036 } 1037 } 1038 1039 @Override onBackPressed()1040 public void onBackPressed() { 1041 // Disable back key 1042 } 1043 1044 /** 1045 * Hide opt-out dialog. 1046 * In case of any emergency alert invisible, need to hide the opt-out dialog when 1047 * new alert coming. 1048 */ hideOptOutDialog()1049 private void hideOptOutDialog() { 1050 if (mOptOutDialog != null && mOptOutDialog.isShowing()) { 1051 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 1052 prefs.edit().putBoolean(CellBroadcastSettings.KEY_SHOW_CMAS_OPT_OUT_DIALOG, true) 1053 .apply(); 1054 mOptOutDialog.dismiss(); 1055 } 1056 } 1057 1058 /** 1059 * @return true if the device is configured to never show the opt out dialog for the mcc/mnc 1060 */ neverShowOptOutDialog(int subId)1061 private boolean neverShowOptOutDialog(int subId) { 1062 return CellBroadcastSettings.getResources(getApplicationContext(), subId) 1063 .getBoolean(R.bool.disable_opt_out_dialog); 1064 } 1065 1066 /** 1067 * Copy the message to clipboard. 1068 * 1069 * @param message Cell broadcast message. 1070 * 1071 * @return {@code true} if success, otherwise {@code false}; 1072 */ 1073 @VisibleForTesting copyMessageToClipboard(SmsCbMessage message, Context context)1074 public static boolean copyMessageToClipboard(SmsCbMessage message, Context context) { 1075 ClipboardManager cm = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE); 1076 if (cm == null) return false; 1077 1078 cm.setPrimaryClip(ClipData.newPlainText("Alert Message", message.getMessageBody())); 1079 1080 String msg = CellBroadcastSettings.getResources(context, 1081 message.getSubscriptionId()).getString(R.string.message_copied); 1082 Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); 1083 return true; 1084 } 1085 1086 /** 1087 * Remove read message from the notification bar, update the notification text, count or cancel 1088 * the notification if there is no un-read messages. 1089 * @param message The dismissed/read message to be removed from the notification bar 1090 * @param context 1091 */ removeReadMessageFromNotificationBar(SmsCbMessage message, Context context)1092 private void removeReadMessageFromNotificationBar(SmsCbMessage message, Context context) { 1093 Log.d(TAG, "removeReadMessageFromNotificationBar, msg: " + message.toString()); 1094 ArrayList<SmsCbMessage> unreadMessageList = CellBroadcastReceiverApp 1095 .removeReadMessage(message); 1096 if (unreadMessageList.isEmpty()) { 1097 Log.d(TAG, "removeReadMessageFromNotificationBar, cancel notification"); 1098 NotificationManager notificationManager = getSystemService(NotificationManager.class); 1099 notificationManager.cancel(CellBroadcastAlertService.NOTIFICATION_ID); 1100 } else { 1101 Log.d(TAG, "removeReadMessageFromNotificationBar, update count to " 1102 + unreadMessageList.size() ); 1103 // do not alert if remove unread messages from the notification bar. 1104 CellBroadcastAlertService.addToNotificationBar( 1105 CellBroadcastReceiverApp.getLatestMessage(), 1106 unreadMessageList, context,false, false, false); 1107 } 1108 } 1109 1110 /** 1111 * Finish alert dialog only if all messages are configured with DismissOnOutsideTouch. 1112 * When multiple messages are displayed, the message with dismissOnOutsideTouch(normally low 1113 * priority message) is displayed on top of other unread alerts without dismissOnOutsideTouch, 1114 * users can easily dismiss all messages by touching the screen. better way is to dismiss the 1115 * alert if and only if all messages with dismiss_on_outside_touch set true. 1116 */ setFinishAlertOnTouchOutside()1117 private void setFinishAlertOnTouchOutside() { 1118 if (mMessageList != null) { 1119 int dismissCount = 0; 1120 for (SmsCbMessage message : mMessageList) { 1121 CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager( 1122 this, message.getSubscriptionId()); 1123 CellBroadcastChannelManager.CellBroadcastChannelRange range = 1124 channelManager.getCellBroadcastChannelRangeFromMessage(message); 1125 if (range != null && range.mDismissOnOutsideTouch) { 1126 dismissCount++; 1127 } 1128 } 1129 setFinishOnTouchOutside(mMessageList.size() > 0 && mMessageList.size() == dismissCount); 1130 } 1131 } 1132 1133 /** 1134 * If message list of dialog does not have message which is included in newMessageList, 1135 * Create new list which includes both dialogMessageList and newMessageList 1136 * without the duplicated message, and Return the new list. 1137 * If not, just return dialogMessageList as default. 1138 * @param dialogMessageList message list which this dialog activity is having 1139 * @param newMessageList message list which is compared with dialogMessageList 1140 * @return message list which is created with dialogMessageList and newMessageList 1141 */ 1142 @VisibleForTesting getNewMessageListIfNeeded( @onNull ArrayList<SmsCbMessage> dialogMessageList, ArrayList<SmsCbMessage> newMessageList)1143 public ArrayList<SmsCbMessage> getNewMessageListIfNeeded( 1144 @NonNull ArrayList<SmsCbMessage> dialogMessageList, 1145 ArrayList<SmsCbMessage> newMessageList) { 1146 if (newMessageList == null) { 1147 return dialogMessageList; 1148 } 1149 ArrayList<SmsCbMessage> clonedNewMessageList = new ArrayList<>(newMessageList); 1150 for (SmsCbMessage message : dialogMessageList) { 1151 clonedNewMessageList.removeIf( 1152 msg -> msg.getReceivedTime() == message.getReceivedTime()); 1153 } 1154 Log.d(TAG, "clonedMessageList.size()=" + clonedNewMessageList.size()); 1155 if (clonedNewMessageList.size() > 0) { 1156 ArrayList<SmsCbMessage> resultList = new ArrayList<>(dialogMessageList); 1157 resultList.addAll(clonedNewMessageList); 1158 Comparator<SmsCbMessage> comparator = (Comparator) (o1, o2) -> { 1159 Long time1 = new Long(((SmsCbMessage) o1).getReceivedTime()); 1160 Long time2 = new Long(((SmsCbMessage) o2).getReceivedTime()); 1161 return time1.compareTo(time2); 1162 }; 1163 if (CellBroadcastSettings.getResourcesForDefaultSubId(getApplicationContext()) 1164 .getBoolean(R.bool.show_cmas_messages_in_priority_order)) { 1165 Log.d(TAG, "Use priority order Based Comparator"); 1166 comparator = mPriorityBasedComparator; 1167 } 1168 Collections.sort(resultList, comparator); 1169 return resultList; 1170 } 1171 return dialogMessageList; 1172 } 1173 1174 /** 1175 * To disable navigation bars, quick settings etc. Force users to engage with the alert dialog 1176 * before switching to other activities. 1177 * 1178 * @param disable if set to {@code true} to disable the status bar. {@code false} otherwise. 1179 */ setStatusBarDisabledIfNeeded(boolean disable)1180 private void setStatusBarDisabledIfNeeded(boolean disable) { 1181 if (!CellBroadcastSettings.getResourcesForDefaultSubId(getApplicationContext()) 1182 .getBoolean(R.bool.disable_status_bar)) { 1183 return; 1184 } 1185 try { 1186 // TODO change to system API in future. 1187 StatusBarManager statusBarManager = getSystemService(StatusBarManager.class); 1188 Method disableMethod = StatusBarManager.class.getDeclaredMethod( 1189 "disable", int.class); 1190 Method disableMethod2 = StatusBarManager.class.getDeclaredMethod( 1191 "disable2", int.class); 1192 if (disable) { 1193 // flags to be disabled 1194 int disableHome = StatusBarManager.class.getDeclaredField("DISABLE_HOME") 1195 .getInt(null); 1196 int disableRecent = StatusBarManager.class 1197 .getDeclaredField("DISABLE_RECENT").getInt(null); 1198 int disableBack = StatusBarManager.class.getDeclaredField("DISABLE_BACK") 1199 .getInt(null); 1200 int disableQuickSettings = StatusBarManager.class.getDeclaredField( 1201 "DISABLE2_QUICK_SETTINGS").getInt(null); 1202 int disableNotificationShaded = StatusBarManager.class.getDeclaredField( 1203 "DISABLE2_NOTIFICATION_SHADE").getInt(null); 1204 disableMethod.invoke(statusBarManager, disableHome | disableBack | disableRecent); 1205 disableMethod2.invoke(statusBarManager, disableQuickSettings 1206 | disableNotificationShaded); 1207 } else { 1208 int disableNone = StatusBarManager.class.getDeclaredField("DISABLE_NONE") 1209 .getInt(null); 1210 disableMethod.invoke(statusBarManager, disableNone); 1211 disableMethod2.invoke(statusBarManager, disableNone); 1212 } 1213 } catch (Exception e) { 1214 Log.e(TAG, "Failed to disable navigation when showing alert: ", e); 1215 } 1216 } 1217 } 1218