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 18 package com.android.mms.ui; 19 20 import android.app.ActionBar; 21 import android.app.Activity; 22 import android.app.AlertDialog; 23 import android.content.AsyncQueryHandler; 24 import android.content.BroadcastReceiver; 25 import android.content.ContentResolver; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.content.DialogInterface.OnClickListener; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.database.ContentObserver; 32 import android.database.Cursor; 33 import android.database.sqlite.SQLiteException; 34 import android.database.sqlite.SqliteWrapper; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.provider.Telephony.Sms; 39 import android.telephony.SmsManager; 40 import android.util.Log; 41 import android.view.ContextMenu; 42 import android.view.Menu; 43 import android.view.MenuItem; 44 import android.view.View; 45 import android.view.Window; 46 import android.widget.AdapterView; 47 import android.widget.ListView; 48 import android.widget.TextView; 49 50 import com.android.internal.telephony.IccCardConstants; 51 import com.android.internal.telephony.TelephonyIntents; 52 import com.android.mms.LogTag; 53 import com.android.mms.R; 54 import com.android.mms.transaction.MessagingNotification; 55 56 /** 57 * Displays a list of the SMS messages stored on the ICC. 58 */ 59 public class ManageSimMessages extends Activity 60 implements View.OnCreateContextMenuListener { 61 private static final Uri ICC_URI = Uri.parse("content://sms/icc"); 62 private static final String TAG = LogTag.TAG; 63 private static final int MENU_COPY_TO_PHONE_MEMORY = 0; 64 private static final int MENU_DELETE_FROM_SIM = 1; 65 private static final int MENU_VIEW = 2; 66 private static final int OPTION_MENU_DELETE_ALL = 0; 67 68 private static final int SHOW_LIST = 0; 69 private static final int SHOW_EMPTY = 1; 70 private static final int SHOW_BUSY = 2; 71 private int mState; 72 73 74 private ContentResolver mContentResolver; 75 private Cursor mCursor = null; 76 private ListView mSimList; 77 private TextView mMessage; 78 private MessageListAdapter mListAdapter = null; 79 private AsyncQueryHandler mQueryHandler = null; 80 81 public static final int SIM_FULL_NOTIFICATION_ID = 234; 82 83 protected BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 84 public void onReceive(Context context, Intent intent) { 85 if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) { 86 String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE); 87 if (stateExtra != null 88 && ((IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra) 89 || IccCardConstants.INTENT_VALUE_ICC_UNKNOWN.equals(stateExtra)))) { 90 updateState(SHOW_EMPTY); 91 } 92 } 93 } 94 }; 95 96 private final ContentObserver simChangeObserver = 97 new ContentObserver(new Handler()) { 98 @Override 99 public void onChange(boolean selfUpdate) { 100 refreshMessageList(); 101 } 102 }; 103 104 @Override onCreate(Bundle icicle)105 protected void onCreate(Bundle icicle) { 106 super.onCreate(icicle); 107 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 108 109 mContentResolver = getContentResolver(); 110 mQueryHandler = new QueryHandler(mContentResolver, this); 111 setContentView(R.layout.sim_list); 112 mSimList = (ListView) findViewById(R.id.messages); 113 mMessage = (TextView) findViewById(R.id.empty_message); 114 115 ActionBar actionBar = getActionBar(); 116 actionBar.setDisplayHomeAsUpEnabled(true); 117 118 init(); 119 } 120 121 @Override onNewIntent(Intent intent)122 protected void onNewIntent(Intent intent) { 123 setIntent(intent); 124 125 init(); 126 } 127 init()128 private void init() { 129 MessagingNotification.cancelNotification(getApplicationContext(), 130 SIM_FULL_NOTIFICATION_ID); 131 132 updateState(SHOW_BUSY); 133 startQuery(); 134 } 135 136 private class QueryHandler extends AsyncQueryHandler { 137 private final ManageSimMessages mParent; 138 QueryHandler( ContentResolver contentResolver, ManageSimMessages parent)139 public QueryHandler( 140 ContentResolver contentResolver, ManageSimMessages parent) { 141 super(contentResolver); 142 mParent = parent; 143 } 144 145 @Override onQueryComplete( int token, Object cookie, Cursor cursor)146 protected void onQueryComplete( 147 int token, Object cookie, Cursor cursor) { 148 mCursor = cursor; 149 if (mCursor != null) { 150 if (!mCursor.moveToFirst()) { 151 // Let user know the SIM is empty 152 updateState(SHOW_EMPTY); 153 } else if (mListAdapter == null) { 154 // Note that the MessageListAdapter doesn't support auto-requeries. If we 155 // want to respond to changes we'd need to add a line like: 156 // mListAdapter.setOnDataSetChangedListener(mDataSetChangedListener); 157 // See ComposeMessageActivity for an example. 158 mListAdapter = new MessageListAdapter( 159 mParent, mCursor, mSimList, false, null); 160 mSimList.setAdapter(mListAdapter); 161 mSimList.setOnCreateContextMenuListener(mParent); 162 updateState(SHOW_LIST); 163 } else { 164 mListAdapter.changeCursor(mCursor); 165 updateState(SHOW_LIST); 166 } 167 startManagingCursor(mCursor); 168 } else { 169 // Let user know the SIM is empty 170 updateState(SHOW_EMPTY); 171 } 172 // Show option menu when query complete. 173 invalidateOptionsMenu(); 174 } 175 } 176 startQuery()177 private void startQuery() { 178 try { 179 mQueryHandler.startQuery(0, null, ICC_URI, null, null, null, null); 180 } catch (SQLiteException e) { 181 SqliteWrapper.checkSQLiteException(this, e); 182 } 183 } 184 refreshMessageList()185 private void refreshMessageList() { 186 updateState(SHOW_BUSY); 187 if (mCursor != null) { 188 stopManagingCursor(mCursor); 189 mCursor.close(); 190 } 191 startQuery(); 192 } 193 194 @Override onCreateContextMenu( ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)195 public void onCreateContextMenu( 196 ContextMenu menu, View v, 197 ContextMenu.ContextMenuInfo menuInfo) { 198 menu.add(0, MENU_COPY_TO_PHONE_MEMORY, 0, 199 R.string.sim_copy_to_phone_memory); 200 menu.add(0, MENU_DELETE_FROM_SIM, 0, R.string.sim_delete); 201 202 // TODO: Enable this once viewMessage is written. 203 // menu.add(0, MENU_VIEW, 0, R.string.sim_view); 204 } 205 206 @Override onContextItemSelected(MenuItem item)207 public boolean onContextItemSelected(MenuItem item) { 208 AdapterView.AdapterContextMenuInfo info; 209 try { 210 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); 211 } catch (ClassCastException exception) { 212 Log.e(TAG, "Bad menuInfo.", exception); 213 return false; 214 } 215 216 final Cursor cursor = (Cursor) mListAdapter.getItem(info.position); 217 218 switch (item.getItemId()) { 219 case MENU_COPY_TO_PHONE_MEMORY: 220 copyToPhoneMemory(cursor); 221 return true; 222 case MENU_DELETE_FROM_SIM: 223 confirmDeleteDialog(new OnClickListener() { 224 public void onClick(DialogInterface dialog, int which) { 225 updateState(SHOW_BUSY); 226 deleteFromSim(cursor); 227 dialog.dismiss(); 228 } 229 }, R.string.confirm_delete_SIM_message); 230 return true; 231 case MENU_VIEW: 232 viewMessage(cursor); 233 return true; 234 } 235 return super.onContextItemSelected(item); 236 } 237 238 @Override onResume()239 public void onResume() { 240 super.onResume(); 241 registerSimChangeObserver(); 242 } 243 244 @Override onPause()245 public void onPause() { 246 super.onPause(); 247 unregisterReceiver(mBroadcastReceiver); 248 mContentResolver.unregisterContentObserver(simChangeObserver); 249 } 250 registerSimChangeObserver()251 private void registerSimChangeObserver() { 252 mContentResolver.registerContentObserver( 253 ICC_URI, true, simChangeObserver); 254 final IntentFilter intentFilter = 255 new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED); 256 registerReceiver(mBroadcastReceiver, intentFilter); 257 } 258 copyToPhoneMemory(Cursor cursor)259 private void copyToPhoneMemory(Cursor cursor) { 260 String address = cursor.getString( 261 cursor.getColumnIndexOrThrow("address")); 262 String body = cursor.getString(cursor.getColumnIndexOrThrow("body")); 263 Long date = cursor.getLong(cursor.getColumnIndexOrThrow("date")); 264 265 try { 266 if (isIncomingMessage(cursor)) { 267 Sms.Inbox.addMessage(mContentResolver, address, body, null, date, true /* read */); 268 } else { 269 Sms.Sent.addMessage(mContentResolver, address, body, null, date); 270 } 271 } catch (SQLiteException e) { 272 SqliteWrapper.checkSQLiteException(this, e); 273 } 274 } 275 isIncomingMessage(Cursor cursor)276 private boolean isIncomingMessage(Cursor cursor) { 277 int messageStatus = cursor.getInt( 278 cursor.getColumnIndexOrThrow("status")); 279 280 return (messageStatus == SmsManager.STATUS_ON_ICC_READ) || 281 (messageStatus == SmsManager.STATUS_ON_ICC_UNREAD); 282 } 283 deleteFromSim(Cursor cursor)284 private void deleteFromSim(Cursor cursor) { 285 String messageIndexString = 286 cursor.getString(cursor.getColumnIndexOrThrow("index_on_icc")); 287 Uri simUri = ICC_URI.buildUpon().appendPath(messageIndexString).build(); 288 289 SqliteWrapper.delete(this, mContentResolver, simUri, null, null); 290 } 291 deleteAllFromSim()292 private void deleteAllFromSim() { 293 Cursor cursor = (Cursor) mListAdapter.getCursor(); 294 295 if (cursor != null) { 296 if (cursor.moveToFirst()) { 297 int count = cursor.getCount(); 298 299 for (int i = 0; i < count; ++i) { 300 deleteFromSim(cursor); 301 cursor.moveToNext(); 302 } 303 } 304 } 305 } 306 307 @Override onPrepareOptionsMenu(Menu menu)308 public boolean onPrepareOptionsMenu(Menu menu) { 309 menu.clear(); 310 311 if (mState == SHOW_LIST && (null != mCursor) && (mCursor.getCount() > 0)) { 312 menu.add(0, OPTION_MENU_DELETE_ALL, 0, R.string.menu_delete_messages).setIcon( 313 android.R.drawable.ic_menu_delete); 314 } 315 316 return true; 317 } 318 319 @Override onOptionsItemSelected(MenuItem item)320 public boolean onOptionsItemSelected(MenuItem item) { 321 switch (item.getItemId()) { 322 case OPTION_MENU_DELETE_ALL: 323 confirmDeleteDialog(new OnClickListener() { 324 public void onClick(DialogInterface dialog, int which) { 325 updateState(SHOW_BUSY); 326 deleteAllFromSim(); 327 dialog.dismiss(); 328 } 329 }, R.string.confirm_delete_all_SIM_messages); 330 break; 331 332 case android.R.id.home: 333 // The user clicked on the Messaging icon in the action bar. Take them back from 334 // wherever they came from 335 finish(); 336 break; 337 } 338 339 return true; 340 } 341 confirmDeleteDialog(OnClickListener listener, int messageId)342 private void confirmDeleteDialog(OnClickListener listener, int messageId) { 343 AlertDialog.Builder builder = new AlertDialog.Builder(this); 344 builder.setTitle(R.string.confirm_dialog_title); 345 builder.setIconAttribute(android.R.attr.alertDialogIcon); 346 builder.setCancelable(true); 347 builder.setPositiveButton(R.string.yes, listener); 348 builder.setNegativeButton(R.string.no, null); 349 builder.setMessage(messageId); 350 351 builder.show(); 352 } 353 updateState(int state)354 private void updateState(int state) { 355 if (mState == state) { 356 return; 357 } 358 359 mState = state; 360 switch (state) { 361 case SHOW_LIST: 362 mSimList.setVisibility(View.VISIBLE); 363 mMessage.setVisibility(View.GONE); 364 setTitle(getString(R.string.sim_manage_messages_title)); 365 setProgressBarIndeterminateVisibility(false); 366 mSimList.requestFocus(); 367 break; 368 case SHOW_EMPTY: 369 mSimList.setVisibility(View.GONE); 370 mMessage.setVisibility(View.VISIBLE); 371 setTitle(getString(R.string.sim_manage_messages_title)); 372 setProgressBarIndeterminateVisibility(false); 373 break; 374 case SHOW_BUSY: 375 mSimList.setVisibility(View.GONE); 376 mMessage.setVisibility(View.GONE); 377 setTitle(getString(R.string.refreshing)); 378 setProgressBarIndeterminateVisibility(true); 379 break; 380 default: 381 Log.e(TAG, "Invalid State"); 382 } 383 } 384 viewMessage(Cursor cursor)385 private void viewMessage(Cursor cursor) { 386 // TODO: Add this. 387 } 388 } 389 390