1/* 2 * Copyright (C) 2008 Esmertec AG. 3 * Copyright (C) 2008 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.mms.ui; 19 20import com.android.mms.R; 21import android.database.sqlite.SqliteWrapper; 22import com.android.mms.transaction.MessagingNotification; 23 24import android.app.Activity; 25import android.app.AlertDialog; 26 27import android.content.AsyncQueryHandler; 28import android.content.ContentResolver; 29import android.content.DialogInterface; 30import android.content.Intent; 31import android.content.DialogInterface.OnClickListener; 32import android.database.ContentObserver; 33import android.database.Cursor; 34import android.database.sqlite.SQLiteException; 35import android.net.Uri; 36import android.os.Bundle; 37import android.os.Handler; 38import android.provider.Telephony.Sms; 39import android.telephony.SmsManager; 40import android.util.Log; 41import android.view.ContextMenu; 42import android.view.Menu; 43import android.view.MenuItem; 44import android.view.View; 45import android.view.Window; 46import android.widget.AdapterView; 47import android.widget.ListView; 48import android.widget.TextView; 49 50/** 51 * Displays a list of the SMS messages stored on the ICC. 52 */ 53public class ManageSimMessages extends Activity 54 implements View.OnCreateContextMenuListener { 55 private static final Uri ICC_URI = Uri.parse("content://sms/icc"); 56 private static final String TAG = "ManageSimMessages"; 57 private static final int MENU_COPY_TO_PHONE_MEMORY = 0; 58 private static final int MENU_DELETE_FROM_SIM = 1; 59 private static final int MENU_VIEW = 2; 60 private static final int OPTION_MENU_DELETE_ALL = 0; 61 62 private static final int SHOW_LIST = 0; 63 private static final int SHOW_EMPTY = 1; 64 private static final int SHOW_BUSY = 2; 65 private int mState; 66 67 68 private ContentResolver mContentResolver; 69 private Cursor mCursor = null; 70 private ListView mSimList; 71 private TextView mMessage; 72 private MessageListAdapter mListAdapter = null; 73 private AsyncQueryHandler mQueryHandler = null; 74 75 public static final int SIM_FULL_NOTIFICATION_ID = 234; 76 77 private final ContentObserver simChangeObserver = 78 new ContentObserver(new Handler()) { 79 @Override 80 public void onChange(boolean selfUpdate) { 81 refreshMessageList(); 82 } 83 }; 84 85 @Override 86 protected void onCreate(Bundle icicle) { 87 super.onCreate(icicle); 88 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 89 90 mContentResolver = getContentResolver(); 91 mQueryHandler = new QueryHandler(mContentResolver, this); 92 setContentView(R.layout.sim_list); 93 mSimList = (ListView) findViewById(R.id.messages); 94 mMessage = (TextView) findViewById(R.id.empty_message); 95 96 init(); 97 } 98 99 @Override 100 protected void onNewIntent(Intent intent) { 101 setIntent(intent); 102 103 init(); 104 } 105 106 private void init() { 107 MessagingNotification.cancelNotification(getApplicationContext(), 108 SIM_FULL_NOTIFICATION_ID); 109 110 updateState(SHOW_BUSY); 111 startQuery(); 112 } 113 114 private class QueryHandler extends AsyncQueryHandler { 115 private final ManageSimMessages mParent; 116 117 public QueryHandler( 118 ContentResolver contentResolver, ManageSimMessages parent) { 119 super(contentResolver); 120 mParent = parent; 121 } 122 123 @Override 124 protected void onQueryComplete( 125 int token, Object cookie, Cursor cursor) { 126 mCursor = cursor; 127 if (mCursor != null) { 128 if (!mCursor.moveToFirst()) { 129 // Let user know the SIM is empty 130 updateState(SHOW_EMPTY); 131 } else if (mListAdapter == null) { 132 // Note that the MessageListAdapter doesn't support auto-requeries. If we 133 // want to respond to changes we'd need to add a line like: 134 // mListAdapter.setOnDataSetChangedListener(mDataSetChangedListener); 135 // See ComposeMessageActivity for an example. 136 mListAdapter = new MessageListAdapter( 137 mParent, mCursor, mSimList, false, null); 138 mSimList.setAdapter(mListAdapter); 139 mSimList.setOnCreateContextMenuListener(mParent); 140 updateState(SHOW_LIST); 141 } else { 142 mListAdapter.changeCursor(mCursor); 143 updateState(SHOW_LIST); 144 } 145 startManagingCursor(mCursor); 146 } else { 147 // Let user know the SIM is empty 148 updateState(SHOW_EMPTY); 149 } 150 } 151 } 152 153 private void startQuery() { 154 try { 155 mQueryHandler.startQuery(0, null, ICC_URI, null, null, null, null); 156 } catch (SQLiteException e) { 157 SqliteWrapper.checkSQLiteException(this, e); 158 } 159 } 160 161 private void refreshMessageList() { 162 updateState(SHOW_BUSY); 163 if (mCursor != null) { 164 stopManagingCursor(mCursor); 165 mCursor.close(); 166 } 167 startQuery(); 168 } 169 170 @Override 171 public void onCreateContextMenu( 172 ContextMenu menu, View v, 173 ContextMenu.ContextMenuInfo menuInfo) { 174 menu.add(0, MENU_COPY_TO_PHONE_MEMORY, 0, 175 R.string.sim_copy_to_phone_memory); 176 menu.add(0, MENU_DELETE_FROM_SIM, 0, R.string.sim_delete); 177 178 // TODO: Enable this once viewMessage is written. 179 // menu.add(0, MENU_VIEW, 0, R.string.sim_view); 180 } 181 182 @Override 183 public boolean onContextItemSelected(MenuItem item) { 184 AdapterView.AdapterContextMenuInfo info; 185 try { 186 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); 187 } catch (ClassCastException exception) { 188 Log.e(TAG, "Bad menuInfo.", exception); 189 return false; 190 } 191 192 final Cursor cursor = (Cursor) mListAdapter.getItem(info.position); 193 194 switch (item.getItemId()) { 195 case MENU_COPY_TO_PHONE_MEMORY: 196 copyToPhoneMemory(cursor); 197 return true; 198 case MENU_DELETE_FROM_SIM: 199 confirmDeleteDialog(new OnClickListener() { 200 public void onClick(DialogInterface dialog, int which) { 201 updateState(SHOW_BUSY); 202 deleteFromSim(cursor); 203 dialog.dismiss(); 204 } 205 }, R.string.confirm_delete_SIM_message); 206 return true; 207 case MENU_VIEW: 208 viewMessage(cursor); 209 return true; 210 } 211 return super.onContextItemSelected(item); 212 } 213 214 @Override 215 public void onResume() { 216 super.onResume(); 217 registerSimChangeObserver(); 218 } 219 220 @Override 221 public void onPause() { 222 super.onPause(); 223 mContentResolver.unregisterContentObserver(simChangeObserver); 224 } 225 226 private void registerSimChangeObserver() { 227 mContentResolver.registerContentObserver( 228 ICC_URI, true, simChangeObserver); 229 } 230 231 private void copyToPhoneMemory(Cursor cursor) { 232 String address = cursor.getString( 233 cursor.getColumnIndexOrThrow("address")); 234 String body = cursor.getString(cursor.getColumnIndexOrThrow("body")); 235 Long date = cursor.getLong(cursor.getColumnIndexOrThrow("date")); 236 237 try { 238 if (isIncomingMessage(cursor)) { 239 Sms.Inbox.addMessage(mContentResolver, address, body, null, date, true /* read */); 240 } else { 241 Sms.Sent.addMessage(mContentResolver, address, body, null, date); 242 } 243 } catch (SQLiteException e) { 244 SqliteWrapper.checkSQLiteException(this, e); 245 } 246 } 247 248 private boolean isIncomingMessage(Cursor cursor) { 249 int messageStatus = cursor.getInt( 250 cursor.getColumnIndexOrThrow("status")); 251 252 return (messageStatus == SmsManager.STATUS_ON_ICC_READ) || 253 (messageStatus == SmsManager.STATUS_ON_ICC_UNREAD); 254 } 255 256 private void deleteFromSim(Cursor cursor) { 257 String messageIndexString = 258 cursor.getString(cursor.getColumnIndexOrThrow("index_on_icc")); 259 Uri simUri = ICC_URI.buildUpon().appendPath(messageIndexString).build(); 260 261 SqliteWrapper.delete(this, mContentResolver, simUri, null, null); 262 } 263 264 private void deleteAllFromSim() { 265 Cursor cursor = (Cursor) mListAdapter.getCursor(); 266 267 if (cursor != null) { 268 if (cursor.moveToFirst()) { 269 int count = cursor.getCount(); 270 271 for (int i = 0; i < count; ++i) { 272 deleteFromSim(cursor); 273 cursor.moveToNext(); 274 } 275 } 276 } 277 } 278 279 @Override 280 public boolean onPrepareOptionsMenu(Menu menu) { 281 menu.clear(); 282 283 if ((null != mCursor) && (mCursor.getCount() > 0) && mState == SHOW_LIST) { 284 menu.add(0, OPTION_MENU_DELETE_ALL, 0, R.string.menu_delete_messages).setIcon( 285 android.R.drawable.ic_menu_delete); 286 } 287 288 return true; 289 } 290 291 @Override 292 public boolean onOptionsItemSelected(MenuItem item) { 293 switch (item.getItemId()) { 294 case OPTION_MENU_DELETE_ALL: 295 confirmDeleteDialog(new OnClickListener() { 296 public void onClick(DialogInterface dialog, int which) { 297 updateState(SHOW_BUSY); 298 deleteAllFromSim(); 299 dialog.dismiss(); 300 } 301 }, R.string.confirm_delete_all_SIM_messages); 302 break; 303 } 304 305 return true; 306 } 307 308 private void confirmDeleteDialog(OnClickListener listener, int messageId) { 309 AlertDialog.Builder builder = new AlertDialog.Builder(this); 310 builder.setTitle(R.string.confirm_dialog_title); 311 builder.setIcon(android.R.drawable.ic_dialog_alert); 312 builder.setCancelable(true); 313 builder.setPositiveButton(R.string.yes, listener); 314 builder.setNegativeButton(R.string.no, null); 315 builder.setMessage(messageId); 316 317 builder.show(); 318 } 319 320 private void updateState(int state) { 321 if (mState == state) { 322 return; 323 } 324 325 mState = state; 326 switch (state) { 327 case SHOW_LIST: 328 mSimList.setVisibility(View.VISIBLE); 329 mMessage.setVisibility(View.GONE); 330 setTitle(getString(R.string.sim_manage_messages_title)); 331 setProgressBarIndeterminateVisibility(false); 332 break; 333 case SHOW_EMPTY: 334 mSimList.setVisibility(View.GONE); 335 mMessage.setVisibility(View.VISIBLE); 336 setTitle(getString(R.string.sim_manage_messages_title)); 337 setProgressBarIndeterminateVisibility(false); 338 break; 339 case SHOW_BUSY: 340 mSimList.setVisibility(View.GONE); 341 mMessage.setVisibility(View.GONE); 342 setTitle(getString(R.string.refreshing)); 343 setProgressBarIndeterminateVisibility(true); 344 break; 345 default: 346 Log.e(TAG, "Invalid State"); 347 } 348 } 349 350 private void viewMessage(Cursor cursor) { 351 // TODO: Add this. 352 } 353} 354 355