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 registerSimChangeObserver(); 147 } else { 148 // Let user know the SIM is empty 149 updateState(SHOW_EMPTY); 150 } 151 } 152 } 153 154 private void startQuery() { 155 try { 156 mQueryHandler.startQuery(0, null, ICC_URI, null, null, null, null); 157 } catch (SQLiteException e) { 158 SqliteWrapper.checkSQLiteException(this, e); 159 } 160 } 161 162 private void refreshMessageList() { 163 updateState(SHOW_BUSY); 164 if (mCursor != null) { 165 stopManagingCursor(mCursor); 166 mCursor.close(); 167 } 168 startQuery(); 169 } 170 171 @Override 172 public void onCreateContextMenu( 173 ContextMenu menu, View v, 174 ContextMenu.ContextMenuInfo menuInfo) { 175 menu.add(0, MENU_COPY_TO_PHONE_MEMORY, 0, 176 R.string.sim_copy_to_phone_memory); 177 menu.add(0, MENU_DELETE_FROM_SIM, 0, R.string.sim_delete); 178 179 // TODO: Enable this once viewMessage is written. 180 // menu.add(0, MENU_VIEW, 0, R.string.sim_view); 181 } 182 183 @Override 184 public boolean onContextItemSelected(MenuItem item) { 185 AdapterView.AdapterContextMenuInfo info; 186 try { 187 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); 188 } catch (ClassCastException exception) { 189 Log.e(TAG, "Bad menuInfo.", exception); 190 return false; 191 } 192 193 final Cursor cursor = (Cursor) mListAdapter.getItem(info.position); 194 195 switch (item.getItemId()) { 196 case MENU_COPY_TO_PHONE_MEMORY: 197 copyToPhoneMemory(cursor); 198 return true; 199 case MENU_DELETE_FROM_SIM: 200 confirmDeleteDialog(new OnClickListener() { 201 public void onClick(DialogInterface dialog, int which) { 202 updateState(SHOW_BUSY); 203 deleteFromSim(cursor); 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 } 300 }, R.string.confirm_delete_all_SIM_messages); 301 break; 302 } 303 304 return true; 305 } 306 307 private void confirmDeleteDialog(OnClickListener listener, int messageId) { 308 AlertDialog.Builder builder = new AlertDialog.Builder(this); 309 builder.setTitle(R.string.confirm_dialog_title); 310 builder.setIcon(android.R.drawable.ic_dialog_alert); 311 builder.setCancelable(true); 312 builder.setPositiveButton(R.string.yes, listener); 313 builder.setNegativeButton(R.string.no, null); 314 builder.setMessage(messageId); 315 316 builder.show(); 317 } 318 319 private void updateState(int state) { 320 if (mState == state) { 321 return; 322 } 323 324 mState = state; 325 switch (state) { 326 case SHOW_LIST: 327 mSimList.setVisibility(View.VISIBLE); 328 mMessage.setVisibility(View.GONE); 329 setTitle(getString(R.string.sim_manage_messages_title)); 330 setProgressBarIndeterminateVisibility(false); 331 break; 332 case SHOW_EMPTY: 333 mSimList.setVisibility(View.GONE); 334 mMessage.setVisibility(View.VISIBLE); 335 setTitle(getString(R.string.sim_manage_messages_title)); 336 setProgressBarIndeterminateVisibility(false); 337 break; 338 case SHOW_BUSY: 339 mSimList.setVisibility(View.GONE); 340 mMessage.setVisibility(View.GONE); 341 setTitle(getString(R.string.refreshing)); 342 setProgressBarIndeterminateVisibility(true); 343 break; 344 default: 345 Log.e(TAG, "Invalid State"); 346 } 347 } 348 349 private void viewMessage(Cursor cursor) { 350 // TODO: Add this. 351 } 352} 353 354