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 17package com.android.cellbroadcastreceiver; 18 19import android.app.ActionBar; 20import android.app.Activity; 21import android.app.AlertDialog; 22import android.app.FragmentManager; 23import android.app.ListFragment; 24import android.app.LoaderManager; 25import android.app.NotificationManager; 26import android.content.Context; 27import android.content.CursorLoader; 28import android.content.DialogInterface; 29import android.content.DialogInterface.OnClickListener; 30import android.content.Intent; 31import android.content.Loader; 32import android.database.Cursor; 33import android.os.Bundle; 34import android.provider.Telephony; 35import android.telephony.CellBroadcastMessage; 36import android.view.ContextMenu; 37import android.view.ContextMenu.ContextMenuInfo; 38import android.view.LayoutInflater; 39import android.view.Menu; 40import android.view.MenuInflater; 41import android.view.MenuItem; 42import android.view.View; 43import android.view.View.OnCreateContextMenuListener; 44import android.view.ViewGroup; 45import android.widget.CursorAdapter; 46import android.widget.ListView; 47 48import java.util.ArrayList; 49 50/** 51 * This activity provides a list view of received cell broadcasts. Most of the work is handled 52 * in the inner CursorLoaderListFragment class. 53 */ 54public class CellBroadcastListActivity extends Activity { 55 56 @Override 57 protected void onCreate(Bundle savedInstanceState) { 58 super.onCreate(savedInstanceState); 59 60 ActionBar actionBar = getActionBar(); 61 if (actionBar != null) { 62 // android.R.id.home will be triggered in onOptionsItemSelected() 63 actionBar.setDisplayHomeAsUpEnabled(true); 64 } 65 66 setTitle(getString(R.string.cb_list_activity_title)); 67 68 // Dismiss the notification that brought us here (if any). 69 ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)) 70 .cancel(CellBroadcastAlertService.NOTIFICATION_ID); 71 72 FragmentManager fm = getFragmentManager(); 73 74 // Create the list fragment and add it as our sole content. 75 if (fm.findFragmentById(android.R.id.content) == null) { 76 CursorLoaderListFragment listFragment = new CursorLoaderListFragment(); 77 fm.beginTransaction().add(android.R.id.content, listFragment).commit(); 78 } 79 } 80 81 @Override 82 public boolean onOptionsItemSelected(MenuItem item) { 83 switch (item.getItemId()) { 84 // Respond to the action bar's Up/Home button 85 case android.R.id.home: 86 finish(); 87 return true; 88 } 89 return super.onOptionsItemSelected(item); 90 } 91 92 /** 93 * List fragment queries SQLite database on worker thread. 94 */ 95 public static class CursorLoaderListFragment extends ListFragment 96 implements LoaderManager.LoaderCallbacks<Cursor> { 97 98 // IDs of the main menu items. 99 private static final int MENU_DELETE_ALL = 3; 100 101 // IDs of the context menu items (package local, accessed from inner DeleteThreadListener). 102 static final int MENU_DELETE = 0; 103 static final int MENU_VIEW_DETAILS = 1; 104 105 // This is the Adapter being used to display the list's data. 106 CursorAdapter mAdapter; 107 108 @Override 109 public void onCreate(Bundle savedInstanceState) { 110 super.onCreate(savedInstanceState); 111 112 // We have a menu item to show in action bar. 113 setHasOptionsMenu(true); 114 } 115 116 @Override 117 public View onCreateView(LayoutInflater inflater, ViewGroup container, 118 Bundle savedInstanceState) { 119 return inflater.inflate(R.layout.cell_broadcast_list_screen, container, false); 120 } 121 122 @Override 123 public void onActivityCreated(Bundle savedInstanceState) { 124 super.onActivityCreated(savedInstanceState); 125 126 // Set context menu for long-press. 127 ListView listView = getListView(); 128 listView.setOnCreateContextMenuListener(mOnCreateContextMenuListener); 129 130 // Create a cursor adapter to display the loaded data. 131 mAdapter = new CellBroadcastCursorAdapter(getActivity(), null); 132 setListAdapter(mAdapter); 133 134 // Prepare the loader. Either re-connect with an existing one, 135 // or start a new one. 136 getLoaderManager().initLoader(0, null, this); 137 } 138 139 @Override 140 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 141 menu.add(0, MENU_DELETE_ALL, 0, R.string.menu_delete_all).setIcon( 142 android.R.drawable.ic_menu_delete); 143 } 144 145 @Override 146 public void onPrepareOptionsMenu(Menu menu) { 147 menu.findItem(MENU_DELETE_ALL).setVisible(!mAdapter.isEmpty()); 148 } 149 150 @Override 151 public void onListItemClick(ListView l, View v, int position, long id) { 152 CellBroadcastListItem cbli = (CellBroadcastListItem) v; 153 showDialogAndMarkRead(cbli.getMessage()); 154 } 155 156 @Override 157 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 158 return new CursorLoader(getActivity(), CellBroadcastContentProvider.CONTENT_URI, 159 Telephony.CellBroadcasts.QUERY_COLUMNS, null, null, 160 Telephony.CellBroadcasts.DELIVERY_TIME + " DESC"); 161 } 162 163 @Override 164 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 165 // Swap the new cursor in. (The framework will take care of closing the 166 // old cursor once we return.) 167 mAdapter.swapCursor(data); 168 getActivity().invalidateOptionsMenu(); 169 } 170 171 @Override 172 public void onLoaderReset(Loader<Cursor> loader) { 173 // This is called when the last Cursor provided to onLoadFinished() 174 // above is about to be closed. We need to make sure we are no 175 // longer using it. 176 mAdapter.swapCursor(null); 177 } 178 179 private void showDialogAndMarkRead(CellBroadcastMessage cbm) { 180 // show emergency alerts with the warning icon, but don't play alert tone 181 Intent i = new Intent(getActivity(), CellBroadcastAlertDialog.class); 182 ArrayList<CellBroadcastMessage> messageList = new ArrayList<CellBroadcastMessage>(1); 183 messageList.add(cbm); 184 i.putParcelableArrayListExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, messageList); 185 startActivity(i); 186 } 187 188 private void showBroadcastDetails(CellBroadcastMessage cbm) { 189 // show dialog with delivery date/time and alert details 190 CharSequence details = CellBroadcastResources.getMessageDetails(getActivity(), cbm); 191 new AlertDialog.Builder(getActivity()) 192 .setTitle(R.string.view_details_title) 193 .setMessage(details) 194 .setCancelable(true) 195 .show(); 196 } 197 198 private final OnCreateContextMenuListener mOnCreateContextMenuListener = 199 new OnCreateContextMenuListener() { 200 @Override 201 public void onCreateContextMenu(ContextMenu menu, View v, 202 ContextMenuInfo menuInfo) { 203 menu.setHeaderTitle(R.string.message_options); 204 menu.add(0, MENU_VIEW_DETAILS, 0, R.string.menu_view_details); 205 menu.add(0, MENU_DELETE, 0, R.string.menu_delete); 206 } 207 }; 208 209 @Override 210 public boolean onContextItemSelected(MenuItem item) { 211 Cursor cursor = mAdapter.getCursor(); 212 if (cursor != null && cursor.getPosition() >= 0) { 213 switch (item.getItemId()) { 214 case MENU_DELETE: 215 confirmDeleteThread(cursor.getLong(cursor.getColumnIndexOrThrow( 216 Telephony.CellBroadcasts._ID))); 217 break; 218 219 case MENU_VIEW_DETAILS: 220 showBroadcastDetails(CellBroadcastMessage.createFromCursor(cursor)); 221 break; 222 223 default: 224 break; 225 } 226 } 227 return super.onContextItemSelected(item); 228 } 229 230 @Override 231 public boolean onOptionsItemSelected(MenuItem item) { 232 switch(item.getItemId()) { 233 case MENU_DELETE_ALL: 234 confirmDeleteThread(-1); 235 break; 236 237 default: 238 return true; 239 } 240 return false; 241 } 242 243 /** 244 * Start the process of putting up a dialog to confirm deleting a broadcast. 245 * @param rowId the row ID of the broadcast to delete, or -1 to delete all broadcasts 246 */ 247 public void confirmDeleteThread(long rowId) { 248 DeleteThreadListener listener = new DeleteThreadListener(rowId); 249 confirmDeleteThreadDialog(listener, (rowId == -1), getActivity()); 250 } 251 252 /** 253 * Build and show the proper delete broadcast dialog. The UI is slightly different 254 * depending on whether there are locked messages in the thread(s) and whether we're 255 * deleting a single broadcast or all broadcasts. 256 * @param listener gets called when the delete button is pressed 257 * @param deleteAll whether to show a single thread or all threads UI 258 * @param context used to load the various UI elements 259 */ 260 public static void confirmDeleteThreadDialog(DeleteThreadListener listener, 261 boolean deleteAll, Context context) { 262 AlertDialog.Builder builder = new AlertDialog.Builder(context); 263 builder.setIconAttribute(android.R.attr.alertDialogIcon) 264 .setCancelable(true) 265 .setPositiveButton(R.string.button_delete, listener) 266 .setNegativeButton(R.string.button_cancel, null) 267 .setMessage(deleteAll ? R.string.confirm_delete_all_broadcasts 268 : R.string.confirm_delete_broadcast) 269 .show(); 270 } 271 272 public class DeleteThreadListener implements OnClickListener { 273 private final long mRowId; 274 275 public DeleteThreadListener(long rowId) { 276 mRowId = rowId; 277 } 278 279 @Override 280 public void onClick(DialogInterface dialog, int whichButton) { 281 // delete from database on a background thread 282 new CellBroadcastContentProvider.AsyncCellBroadcastTask( 283 getActivity().getContentResolver()).execute( 284 new CellBroadcastContentProvider.CellBroadcastOperation() { 285 @Override 286 public boolean execute(CellBroadcastContentProvider provider) { 287 if (mRowId != -1) { 288 return provider.deleteBroadcast(mRowId); 289 } else { 290 return provider.deleteAllBroadcasts(); 291 } 292 } 293 }); 294 295 dialog.dismiss(); 296 } 297 } 298 } 299} 300