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