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 android.app.ActionBar;
21import android.app.Activity;
22import android.app.AlertDialog;
23import android.content.AsyncQueryHandler;
24import android.content.BroadcastReceiver;
25import android.content.ContentResolver;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.content.DialogInterface.OnClickListener;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.database.ContentObserver;
32import android.database.Cursor;
33import android.database.sqlite.SQLiteException;
34import android.database.sqlite.SqliteWrapper;
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
50import com.android.internal.telephony.IccCardConstants;
51import com.android.internal.telephony.TelephonyIntents;
52import com.android.mms.LogTag;
53import com.android.mms.R;
54import com.android.mms.transaction.MessagingNotification;
55
56/**
57 * Displays a list of the SMS messages stored on the ICC.
58 */
59public 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
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
122    protected void onNewIntent(Intent intent) {
123        setIntent(intent);
124
125        init();
126    }
127
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
139        public QueryHandler(
140                ContentResolver contentResolver, ManageSimMessages parent) {
141            super(contentResolver);
142            mParent = parent;
143        }
144
145        @Override
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
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
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
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
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
239    public void onResume() {
240        super.onResume();
241        registerSimChangeObserver();
242    }
243
244    @Override
245    public void onPause() {
246        super.onPause();
247        unregisterReceiver(mBroadcastReceiver);
248        mContentResolver.unregisterContentObserver(simChangeObserver);
249    }
250
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
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
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
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
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
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
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
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
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
385    private void viewMessage(Cursor cursor) {
386        // TODO: Add this.
387    }
388}
389
390