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