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 static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 20 21 import android.annotation.Nullable; 22 import android.app.ActionBar; 23 import android.app.AlertDialog; 24 import android.app.Dialog; 25 import android.app.DialogFragment; 26 import android.app.FragmentManager; 27 import android.app.ListFragment; 28 import android.app.LoaderManager; 29 import android.content.Context; 30 import android.content.CursorLoader; 31 import android.content.DialogInterface; 32 import android.content.DialogInterface.OnClickListener; 33 import android.content.Intent; 34 import android.content.Loader; 35 import android.database.Cursor; 36 import android.net.Uri; 37 import android.os.Bundle; 38 import android.os.UserManager; 39 import android.provider.Telephony; 40 import android.telephony.SmsCbMessage; 41 import android.util.ArrayMap; 42 import android.util.Log; 43 import android.view.ActionMode; 44 import android.view.LayoutInflater; 45 import android.view.Menu; 46 import android.view.MenuInflater; 47 import android.view.MenuItem; 48 import android.view.View; 49 import android.view.ViewGroup; 50 import android.widget.AbsListView.MultiChoiceModeListener; 51 import android.widget.CursorAdapter; 52 import android.widget.ListView; 53 import android.widget.TextView; 54 55 import com.android.internal.annotations.VisibleForTesting; 56 import com.android.modules.utils.build.SdkLevel; 57 import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; 58 59 import java.util.ArrayList; 60 61 /** 62 * This activity provides a list view of received cell broadcasts. Most of the work is handled 63 * in the inner CursorLoaderListFragment class. 64 */ 65 public class CellBroadcastListActivity extends CollapsingToolbarBaseActivity { 66 67 @VisibleForTesting 68 public CursorLoaderListFragment mListFragment; 69 70 @Override onCreate(Bundle savedInstanceState)71 protected void onCreate(Bundle savedInstanceState) { 72 // for backward compatibility on R devices 73 if (!SdkLevel.isAtLeastS()) { 74 setCustomizeContentView(R.layout.cell_broadcast_list_collapsing_no_toobar); 75 } 76 super.onCreate(savedInstanceState); 77 // for backward compatibility on R devices 78 if (!SdkLevel.isAtLeastS()) { 79 ActionBar actionBar = getActionBar(); 80 if (actionBar != null) { 81 // android.R.id.home will be triggered in onOptionsItemSelected() 82 actionBar.setDisplayHomeAsUpEnabled(true); 83 } 84 } 85 86 setTitle(getString(R.string.cb_list_activity_title)); 87 88 FragmentManager fm = getFragmentManager(); 89 90 // Create the list fragment and add it as our sole content. 91 if (fm.findFragmentById(com.android.settingslib.collapsingtoolbar.R.id.content_frame) 92 == null) { 93 mListFragment = new CursorLoaderListFragment(); 94 mListFragment.setActivity(this); 95 fm.beginTransaction().add(com.android.settingslib.collapsingtoolbar.R.id.content_frame, 96 mListFragment).commit(); 97 } 98 } 99 100 @Override onStart()101 public void onStart() { 102 super.onStart(); 103 getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 104 } 105 106 @Override onOptionsItemSelected(MenuItem item)107 public boolean onOptionsItemSelected(MenuItem item) { 108 switch (item.getItemId()) { 109 // Respond to the action bar's Up/Home button 110 case android.R.id.home: 111 finish(); 112 return true; 113 } 114 return super.onOptionsItemSelected(item); 115 } 116 117 /** 118 * List fragment queries SQLite database on worker thread. 119 */ 120 public static class CursorLoaderListFragment extends ListFragment 121 implements LoaderManager.LoaderCallbacks<Cursor> { 122 private static final String TAG = CellBroadcastListActivity.class.getSimpleName(); 123 private static final boolean DBG = true; 124 125 // IDs of the main menu items. 126 @VisibleForTesting 127 public static final int MENU_DELETE_ALL = 3; 128 @VisibleForTesting 129 public static final int MENU_SHOW_REGULAR_MESSAGES = 4; 130 @VisibleForTesting 131 public static final int MENU_SHOW_ALL_MESSAGES = 5; 132 @VisibleForTesting 133 public static final int MENU_PREFERENCES = 6; 134 135 // Load the history from cell broadcast receiver database 136 private static final int LOADER_NORMAL_HISTORY = 1; 137 // Load the history from cell broadcast service. This will include all non-shown messages. 138 @VisibleForTesting 139 public static final int LOADER_HISTORY_FROM_CBS = 2; 140 141 @VisibleForTesting 142 public static final String KEY_LOADER_ID = "loader_id"; 143 144 public static final String KEY_DELETE_DIALOG = "delete_dialog"; 145 146 // IDs of the context menu items (package local, accessed from inner DeleteThreadListener). 147 @VisibleForTesting 148 public static final int MENU_DELETE = 0; 149 @VisibleForTesting 150 public static final int MENU_VIEW_DETAILS = 1; 151 152 // cell broadcast provider from cell broadcast service. 153 public static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts"); 154 155 // Query columns for provider from cell broadcast service. 156 public static final String[] QUERY_COLUMNS = { 157 Telephony.CellBroadcasts._ID, 158 Telephony.CellBroadcasts.SLOT_INDEX, 159 Telephony.CellBroadcasts.SUBSCRIPTION_ID, 160 Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE, 161 Telephony.CellBroadcasts.PLMN, 162 Telephony.CellBroadcasts.LAC, 163 Telephony.CellBroadcasts.CID, 164 Telephony.CellBroadcasts.SERIAL_NUMBER, 165 Telephony.CellBroadcasts.SERVICE_CATEGORY, 166 Telephony.CellBroadcasts.LANGUAGE_CODE, 167 Telephony.CellBroadcasts.DATA_CODING_SCHEME, 168 Telephony.CellBroadcasts.MESSAGE_BODY, 169 Telephony.CellBroadcasts.MESSAGE_FORMAT, 170 Telephony.CellBroadcasts.MESSAGE_PRIORITY, 171 Telephony.CellBroadcasts.ETWS_WARNING_TYPE, 172 Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS, 173 Telephony.CellBroadcasts.CMAS_CATEGORY, 174 Telephony.CellBroadcasts.CMAS_RESPONSE_TYPE, 175 Telephony.CellBroadcasts.CMAS_SEVERITY, 176 Telephony.CellBroadcasts.CMAS_URGENCY, 177 Telephony.CellBroadcasts.CMAS_CERTAINTY, 178 Telephony.CellBroadcasts.RECEIVED_TIME, 179 Telephony.CellBroadcasts.LOCATION_CHECK_TIME, 180 Telephony.CellBroadcasts.MESSAGE_BROADCASTED, 181 Telephony.CellBroadcasts.MESSAGE_DISPLAYED, 182 Telephony.CellBroadcasts.GEOMETRIES, 183 Telephony.CellBroadcasts.MAXIMUM_WAIT_TIME 184 }; 185 186 // This is the Adapter being used to display the list's data. 187 @VisibleForTesting 188 public CursorAdapter mAdapter; 189 190 private int mCurrentLoaderId = 0; 191 192 private MenuItem mInformationMenuItem; 193 private ArrayMap<Integer, Long> mSelectedMessages; 194 195 private CellBroadcastListActivity mActivity; 196 setActivity(CellBroadcastListActivity activity)197 void setActivity(CellBroadcastListActivity activity) { 198 mActivity = activity; 199 } 200 201 @Override onCreate(Bundle savedInstanceState)202 public void onCreate(Bundle savedInstanceState) { 203 super.onCreate(savedInstanceState); 204 205 // We have a menu item to show in action bar. 206 setHasOptionsMenu(true); 207 mSelectedMessages = new ArrayMap<Integer, Long>(); 208 } 209 210 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)211 public View onCreateView(LayoutInflater inflater, ViewGroup container, 212 Bundle savedInstanceState) { 213 return inflater.inflate(R.layout.cell_broadcast_list_screen, container, false); 214 } 215 216 @Override onActivityCreated(Bundle savedInstanceState)217 public void onActivityCreated(Bundle savedInstanceState) { 218 super.onActivityCreated(savedInstanceState); 219 220 // Set context menu for long-press. 221 ListView listView = getListView(); 222 223 // Create a cursor adapter to display the loaded data. 224 mAdapter = new CellBroadcastCursorAdapter(getActivity(), mSelectedMessages); 225 setListAdapter(mAdapter); 226 227 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); 228 listView.setMultiChoiceModeListener(new MultiChoiceModeListener() { 229 @Override 230 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 231 mode.getMenuInflater().inflate(R.menu.cell_broadcast_list_action_menu, menu); 232 mInformationMenuItem = menu.findItem(R.id.action_detail_info); 233 CellBroadcastCursorAdapter.setIsActionMode(true); 234 mAdapter.notifyDataSetChanged(); 235 updateActionIconsVisibility(); 236 return true; 237 } 238 239 @Override 240 public void onDestroyActionMode(ActionMode mode) { 241 CellBroadcastCursorAdapter.setIsActionMode(false); 242 clearSelectedMessages(); 243 mAdapter.notifyDataSetChanged(); 244 } 245 246 @Override 247 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 248 if (item.getItemId() == R.id.action_detail_info) { 249 Cursor cursor = getSelectedItemSingle(); 250 if (cursor != null) { 251 showBroadcastDetails(CellBroadcastCursorAdapter.createFromCursor( 252 getContext(), cursor), getLocationCheckTime(cursor), 253 wasMessageDisplayed(cursor), getGeometryString(cursor)); 254 } else { 255 Log.e(TAG, "Multiple items selected with action_detail_info"); 256 } 257 mode.finish(); 258 return true; 259 } else if (item.getItemId() == R.id.action_delete) { 260 long[] selectedRowId = getSelectedItemsRowId(); 261 confirmDeleteThread(selectedRowId); 262 mode.finish(); 263 return true; 264 } else { 265 Log.e(TAG, "onActionItemClicked: unsupported action return false"); 266 return false; 267 } 268 } 269 270 @Override 271 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 272 return false; 273 } 274 275 @Override 276 public void onItemCheckedStateChanged( 277 ActionMode mode, int position, long id, boolean checked) { 278 int checkedCount = listView.getCheckedItemCount(); 279 Cursor cursor = mAdapter.getCursor(); 280 long rowId = cursor.getLong(cursor.getColumnIndexOrThrow( 281 Telephony.CellBroadcasts._ID)); 282 283 toggleSelectedItem(position, rowId); 284 mode.setTitle(String.valueOf(checkedCount)); 285 mAdapter.notifyDataSetChanged(); 286 } 287 }); 288 289 mCurrentLoaderId = LOADER_NORMAL_HISTORY; 290 if (savedInstanceState != null && savedInstanceState.containsKey(KEY_LOADER_ID)) { 291 mCurrentLoaderId = savedInstanceState.getInt(KEY_LOADER_ID); 292 } 293 294 if (DBG) Log.d(TAG, "onActivityCreated: id=" + mCurrentLoaderId); 295 296 // Prepare the loader. Either re-connect with an existing one, 297 // or start a new one. 298 getLoaderManager().initLoader(mCurrentLoaderId, null, this); 299 } 300 301 @Override onSaveInstanceState(Bundle outState)302 public void onSaveInstanceState(Bundle outState) { 303 // Save the current id for later restoring activity. 304 if (DBG) Log.d(TAG, "onSaveInstanceState: id=" + mCurrentLoaderId); 305 outState.putInt(KEY_LOADER_ID, mCurrentLoaderId); 306 } 307 308 @Override onResume()309 public void onResume() { 310 super.onResume(); 311 if (DBG) Log.d(TAG, "onResume"); 312 if (mCurrentLoaderId != 0) { 313 getLoaderManager().restartLoader(mCurrentLoaderId, null, this); 314 } 315 } 316 317 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)318 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 319 menu.add(0, MENU_DELETE_ALL, 0, R.string.menu_delete_all).setIcon( 320 android.R.drawable.ic_menu_delete); 321 menu.add(0, MENU_SHOW_ALL_MESSAGES, 0, R.string.show_all_messages); 322 menu.add(0, MENU_SHOW_REGULAR_MESSAGES, 0, R.string.show_regular_messages); 323 final UserManager userManager = getContext().getSystemService(UserManager.class); 324 if (userManager.isAdminUser()) { 325 menu.add(0, MENU_PREFERENCES, 0, R.string.menu_preferences).setIcon( 326 android.R.drawable.ic_menu_preferences); 327 } 328 } 329 330 @Override onPrepareOptionsMenu(Menu menu)331 public void onPrepareOptionsMenu(Menu menu) { 332 boolean isTestingMode = CellBroadcastReceiver.isTestingMode( 333 getContext()); 334 // Only allowing delete all messages when not in testing mode because when testing mode 335 // is enabled, the database source is from cell broadcast service. Deleting them does 336 // not affect the database in cell broadcast receiver. Hide the options to reduce 337 // confusion. 338 menu.findItem(MENU_DELETE_ALL).setVisible(!mAdapter.isEmpty() && !isTestingMode); 339 menu.findItem(MENU_SHOW_ALL_MESSAGES).setVisible(isTestingMode 340 && mCurrentLoaderId == LOADER_NORMAL_HISTORY); 341 menu.findItem(MENU_SHOW_REGULAR_MESSAGES).setVisible(isTestingMode 342 && mCurrentLoaderId == LOADER_HISTORY_FROM_CBS); 343 } 344 345 @Override onListItemClick(ListView l, View v, int position, long id)346 public void onListItemClick(ListView l, View v, int position, long id) { 347 CellBroadcastListItem cbli = (CellBroadcastListItem) v; 348 showDialogAndMarkRead(cbli.getMessage()); 349 } 350 351 @Override onCreateLoader(int id, Bundle args)352 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 353 mCurrentLoaderId = id; 354 if (id == LOADER_NORMAL_HISTORY) { 355 Log.d(TAG, "onCreateLoader: normal history."); 356 return new CursorLoader(getActivity(), CellBroadcastContentProvider.CONTENT_URI, 357 CellBroadcastDatabaseHelper.QUERY_COLUMNS, null, null, 358 Telephony.CellBroadcasts.DELIVERY_TIME + " DESC"); 359 } else if (id == LOADER_HISTORY_FROM_CBS) { 360 Log.d(TAG, "onCreateLoader: history from cell broadcast service"); 361 return new CursorLoader(getActivity(), CONTENT_URI, 362 QUERY_COLUMNS, null, null, 363 Telephony.CellBroadcasts.RECEIVED_TIME + " DESC"); 364 } 365 366 return null; 367 } 368 369 @Override onLoadFinished(Loader<Cursor> loader, Cursor data)370 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 371 if (DBG) Log.d(TAG, "onLoadFinished"); 372 // Swap the new cursor in. (The framework will take care of closing the 373 // old cursor once we return.) 374 mAdapter.swapCursor(data); 375 getActivity().invalidateOptionsMenu(); 376 updateNoAlertTextVisibility(); 377 } 378 379 @Override onLoaderReset(Loader<Cursor> loader)380 public void onLoaderReset(Loader<Cursor> loader) { 381 if (DBG) Log.d(TAG, "onLoaderReset"); 382 // This is called when the last Cursor provided to onLoadFinished() 383 // above is about to be closed. We need to make sure we are no 384 // longer using it. 385 mAdapter.swapCursor(null); 386 } 387 showDialogAndMarkRead(SmsCbMessage message)388 private void showDialogAndMarkRead(SmsCbMessage message) { 389 // show emergency alerts with the warning icon, but don't play alert tone 390 Intent i = new Intent(getActivity(), CellBroadcastAlertDialog.class); 391 ArrayList<SmsCbMessage> messageList = new ArrayList<>(); 392 messageList.add(message); 393 i.putParcelableArrayListExtra(CellBroadcastAlertService.SMS_CB_MESSAGE_EXTRA, 394 messageList); 395 startActivity(i); 396 } 397 showBroadcastDetails(SmsCbMessage message, long locationCheckTime, boolean messageDisplayed, String geometry)398 private void showBroadcastDetails(SmsCbMessage message, long locationCheckTime, 399 boolean messageDisplayed, String geometry) { 400 // show dialog with delivery date/time and alert details 401 CharSequence details = CellBroadcastResources.getMessageDetails(getActivity(), 402 mCurrentLoaderId == LOADER_HISTORY_FROM_CBS, message, locationCheckTime, 403 messageDisplayed, geometry); 404 int titleId = (mCurrentLoaderId == LOADER_NORMAL_HISTORY) 405 ? R.string.view_details_title : R.string.view_details_debugging_title; 406 new AlertDialog.Builder(getActivity()) 407 .setTitle(titleId) 408 .setMessage(details) 409 .setCancelable(true) 410 .show(); 411 } 412 updateActionIconsVisibility()413 private void updateActionIconsVisibility() { 414 if (mInformationMenuItem != null) { 415 if (mSelectedMessages.size() == 1) { 416 mInformationMenuItem.setVisible(true); 417 } else { 418 mInformationMenuItem.setVisible(false); 419 } 420 } 421 } 422 getSelectedItemSingle()423 private Cursor getSelectedItemSingle() { 424 if (mSelectedMessages.size() == 1) { 425 Cursor cursor = (Cursor) mAdapter.getItem(mSelectedMessages.keyAt(0)); 426 return cursor; 427 } 428 return null; 429 } 430 getSelectedItemsRowId()431 private long[] getSelectedItemsRowId() { 432 Long[] arr = mSelectedMessages.values().toArray(new Long[mSelectedMessages.size()]); 433 long[] selectedRowId = new long[arr.length]; 434 for (int i = 0; i < arr.length; i++) { 435 selectedRowId[i] = arr[i].longValue(); 436 } 437 return selectedRowId; 438 } 439 440 /** 441 * Record the position and the row ID of the selected items. 442 */ toggleSelectedItem(int position, long rowId)443 public void toggleSelectedItem(int position, long rowId) { 444 if (mSelectedMessages.containsKey(position)) { 445 mSelectedMessages.remove(position); 446 } else { 447 mSelectedMessages.put(position, rowId); 448 } 449 updateActionIconsVisibility(); 450 } 451 452 /** 453 * Clear the position and the row ID of the selected items in the ArrayMap. 454 */ clearSelectedMessages()455 public void clearSelectedMessages() { 456 mSelectedMessages.clear(); 457 } 458 updateNoAlertTextVisibility()459 private void updateNoAlertTextVisibility() { 460 TextView noAlertsTextView = getActivity().findViewById(R.id.empty); 461 if (noAlertsTextView != null) { 462 noAlertsTextView.setVisibility(!hasAlertsInHistory() 463 ? View.VISIBLE : View.INVISIBLE); 464 if (!hasAlertsInHistory()) { 465 getListView().setContentDescription(getString(R.string.no_cell_broadcasts)); 466 } 467 } 468 } 469 470 /** 471 * @return {@code true} if the alert history database has any item 472 */ hasAlertsInHistory()473 private boolean hasAlertsInHistory() { 474 return mAdapter.getCursor().getCount() > 0; 475 } 476 477 /** 478 * Get the location check time of the message. 479 * 480 * @param cursor The cursor of the database 481 * @return The EPOCH time in milliseconds that the location check was performed on the 482 * message. -1 if the information is not available. 483 */ getLocationCheckTime(Cursor cursor)484 private long getLocationCheckTime(Cursor cursor) { 485 if (mCurrentLoaderId != LOADER_HISTORY_FROM_CBS) return -1; 486 return cursor.getLong(cursor.getColumnIndex( 487 Telephony.CellBroadcasts.LOCATION_CHECK_TIME)); 488 } 489 490 /** 491 * Check if the message has been displayed to the user or not 492 * 493 * @param cursor The cursor of the database 494 * @return {@code true} if the message was displayed to the user, otherwise {@code false}. 495 */ wasMessageDisplayed(Cursor cursor)496 private boolean wasMessageDisplayed(Cursor cursor) { 497 if (mCurrentLoaderId != LOADER_HISTORY_FROM_CBS) return true; 498 return cursor.getInt(cursor.getColumnIndex( 499 Telephony.CellBroadcasts.MESSAGE_DISPLAYED)) != 0; 500 } 501 502 /** 503 * Get the geometry string from the message if available. 504 * 505 * @param cursor The cursor of the database 506 * @return The geometry string 507 */ getGeometryString(Cursor cursor)508 private @Nullable String getGeometryString(Cursor cursor) { 509 if (mCurrentLoaderId != LOADER_HISTORY_FROM_CBS) return null; 510 if (cursor.getColumnIndex(Telephony.CellBroadcasts.GEOMETRIES) >= 0) { 511 return cursor.getString(cursor.getColumnIndex(Telephony.CellBroadcasts.GEOMETRIES)); 512 } 513 return null; 514 } 515 516 @Override onContextItemSelected(MenuItem item)517 public boolean onContextItemSelected(MenuItem item) { 518 Cursor cursor = mAdapter.getCursor(); 519 if (cursor != null && cursor.getPosition() >= 0) { 520 switch (item.getItemId()) { 521 case MENU_DELETE: 522 long[] selectedRowId = getSelectedItemsRowId(); 523 confirmDeleteThread(selectedRowId); 524 break; 525 526 case MENU_VIEW_DETAILS: 527 showBroadcastDetails(CellBroadcastCursorAdapter.createFromCursor( 528 getContext(), cursor), getLocationCheckTime(cursor), 529 wasMessageDisplayed(cursor), getGeometryString(cursor)); 530 break; 531 532 default: 533 break; 534 } 535 } 536 return super.onContextItemSelected(item); 537 } 538 539 @Override onOptionsItemSelected(MenuItem item)540 public boolean onOptionsItemSelected(MenuItem item) { 541 switch(item.getItemId()) { 542 case MENU_DELETE_ALL: 543 long[] deleteAll = {-1}; 544 confirmDeleteThread(deleteAll); 545 break; 546 547 case MENU_SHOW_ALL_MESSAGES: 548 getLoaderManager().restartLoader(LOADER_HISTORY_FROM_CBS, null, this); 549 break; 550 551 case MENU_SHOW_REGULAR_MESSAGES: 552 getLoaderManager().restartLoader(LOADER_NORMAL_HISTORY, null, this); 553 break; 554 555 case MENU_PREFERENCES: 556 Intent intent = new Intent(getActivity(), CellBroadcastSettings.class); 557 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP 558 | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 559 startActivity(intent); 560 if (mActivity != null) { 561 mActivity.finish(); 562 } 563 break; 564 565 default: 566 return true; 567 } 568 return false; 569 } 570 571 /** 572 * Start the process of putting up a dialog to confirm deleting a broadcast. 573 * @param rowId array of the row ID that the broadcast to delete, 574 * or rowId[0] = -1 to delete all broadcasts 575 */ confirmDeleteThread(long[] rowId)576 public void confirmDeleteThread(long[] rowId) { 577 DeleteDialogFragment dialog = new DeleteDialogFragment(); 578 Bundle dialogArgs = new Bundle(); 579 dialogArgs.putLongArray(DeleteDialogFragment.ROW_ID, rowId); 580 dialog.setArguments(dialogArgs); 581 dialog.show(getFragmentManager(), KEY_DELETE_DIALOG); 582 } 583 584 public static class DeleteDialogFragment extends DialogFragment { 585 /** 586 * Key for the row id of the message to delete. If the row id is -1, the displayed 587 * dialog will indicate that all messages are to be deleted. 588 */ 589 public static final String ROW_ID = "row_id"; 590 @Override onCreateDialog(Bundle savedInstanceState)591 public Dialog onCreateDialog(Bundle savedInstanceState) { 592 setRetainInstance(true); 593 long[] rowId = getArguments().getLongArray(ROW_ID); 594 boolean deleteAll = rowId[0] == -1; 595 DeleteThreadListener listener = new DeleteThreadListener(getActivity(), rowId); 596 AlertDialog.Builder builder = new AlertDialog.Builder( 597 DeleteDialogFragment.this.getActivity()); 598 builder.setIconAttribute(android.R.attr.alertDialogIcon) 599 .setCancelable(true) 600 .setPositiveButton(R.string.button_delete, listener) 601 .setNegativeButton(R.string.button_cancel, null) 602 .setMessage(deleteAll ? R.string.confirm_delete_all_broadcasts 603 : R.string.confirm_delete_broadcast); 604 return builder.create(); 605 } 606 607 @Override onDestroyView()608 public void onDestroyView() { 609 Dialog dialog = getDialog(); 610 if (dialog != null && getRetainInstance()) { 611 dialog.setDismissMessage(null); 612 } 613 super.onDestroyView(); 614 } 615 } 616 617 public static class DeleteThreadListener implements OnClickListener { 618 private final long[] mRowId; 619 private final Context mContext; 620 DeleteThreadListener(Context context, long[] rowId)621 public DeleteThreadListener(Context context, long[] rowId) { 622 mContext = context; 623 mRowId = rowId; 624 } 625 626 @Override onClick(DialogInterface dialog, int whichButton)627 public void onClick(DialogInterface dialog, int whichButton) { 628 // delete from database on a background thread 629 new CellBroadcastContentProvider.AsyncCellBroadcastTask( 630 mContext.getContentResolver()).execute( 631 (CellBroadcastContentProvider.CellBroadcastOperation) provider -> { 632 if (mRowId[0] != -1) { 633 for (int i = 0; i < mRowId.length; i++) { 634 if (!provider.deleteBroadcast(mRowId[i])) { 635 Log.e(TAG, "failed to delete at row " + mRowId[i]); 636 } 637 } 638 return true; 639 } else { 640 return provider.deleteAllBroadcasts(); 641 } 642 }); 643 644 dialog.dismiss(); 645 } 646 } 647 } 648 } 649