MessageList.java revision 7c3de934291814095908cfdd6816a98f89a43096
1/*
2 * Copyright (C) 2009 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
17package com.android.email.activity;
18
19import com.android.email.Controller;
20import com.android.email.R;
21import com.android.email.Utility;
22import com.android.email.activity.setup.AccountSettings;
23import com.android.email.mail.MessagingException;
24import com.android.email.provider.EmailContent;
25import com.android.email.provider.EmailContent.MessageColumns;
26
27import android.app.ListActivity;
28import android.content.Context;
29import android.content.Intent;
30import android.content.res.Resources;
31import android.database.Cursor;
32import android.graphics.drawable.Drawable;
33import android.os.AsyncTask;
34import android.os.Bundle;
35import android.os.Handler;
36import android.view.LayoutInflater;
37import android.view.Menu;
38import android.view.MenuItem;
39import android.view.View;
40import android.view.ViewGroup;
41import android.view.Window;
42import android.view.View.OnClickListener;
43import android.widget.AdapterView;
44import android.widget.CursorAdapter;
45import android.widget.ImageView;
46import android.widget.ListView;
47import android.widget.TextView;
48import android.widget.AdapterView.OnItemClickListener;
49
50import java.util.Date;
51import java.util.HashSet;
52
53public class MessageList extends ListActivity implements OnItemClickListener, OnClickListener {
54
55    private static final String EXTRA_MAILBOX_ID = "com.android.email.activity.MAILBOX_ID";
56    private static final String EXTRA_ACCOUNT_NAME = "com.android.email.activity.ACCOUNT_NAME";
57    private static final String EXTRA_MAILBOX_NAME = "com.android.email.activity.MAILBOX_NAME";
58
59    // UI support
60    private ListView mListView;
61    private MessageListAdapter mListAdapter;
62    private MessageListHandler mHandler = new MessageListHandler();
63    private ControllerResults mControllerCallback = new ControllerResults();
64
65    // DB access
66    private long mMailboxId;
67    private LoadMessagesTask mLoadMessagesTask;
68
69    /**
70     * Open a specific mailbox.
71     *
72     * TODO This should just shortcut to a more generic version that can accept a list of
73     * accounts/mailboxes (e.g. merged inboxes).
74     *
75     * @param context
76     * @param id mailbox key
77     * @param accountName the account we're viewing
78     * @param mailboxName the mailbox we're viewing
79     */
80    public static void actionHandleAccount(Context context, long id,
81            String accountName, String mailboxName) {
82        Intent intent = new Intent(context, MessageList.class);
83        intent.putExtra(EXTRA_MAILBOX_ID, id);
84        intent.putExtra(EXTRA_ACCOUNT_NAME, accountName);
85        intent.putExtra(EXTRA_MAILBOX_NAME, mailboxName);
86        context.startActivity(intent);
87    }
88
89    @Override
90    public void onCreate(Bundle icicle) {
91        super.onCreate(icicle);
92
93        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
94
95        setContentView(R.layout.message_list);
96        mListView = getListView();
97        mListView.setOnItemClickListener(this);
98        mListView.setItemsCanFocus(false);
99        registerForContextMenu(mListView);
100
101        mListAdapter = new MessageListAdapter(this);
102        setListAdapter(mListAdapter);
103        mListView.setAdapter(mAdapter);
104
105        // TODO set title to "account > mailbox (#unread)"
106
107        // TODO extend this to properly deal with multiple mailboxes, cursor, etc.
108        mMailboxId = getIntent().getLongExtra(EXTRA_MAILBOX_ID, -1);
109
110        mLoadMessagesTask = (LoadMessagesTask) new LoadMessagesTask(mMailboxId).execute();
111    }
112
113    @Override
114    public void onPause() {
115        super.onPause();
116        Controller.getInstance(getApplication()).removeResultCallback(mControllerCallback);
117    }
118
119    @Override
120    public void onResume() {
121        super.onResume();
122        Controller.getInstance(getApplication()).addResultCallback(mControllerCallback);
123
124        // TODO: may need to clear notifications here
125    }
126
127    @Override
128    protected void onDestroy() {
129        super.onDestroy();
130
131        if (mLoadMessagesTask != null &&
132                mLoadMessagesTask.getStatus() != LoadMessagesTask.Status.FINISHED) {
133            mLoadMessagesTask.cancel(true);
134            mLoadMessagesTask = null;
135        }
136    }
137
138    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
139        // TODO these can be lighter-weight lookups
140        EmailContent.Message message = EmailContent.Message.restoreMessageWithId(this, id);
141        EmailContent.Mailbox mailbox =
142            EmailContent.Mailbox.restoreMailboxWithId(this, message.mMailboxKey);
143
144        if (mailbox.mType == EmailContent.Mailbox.TYPE_DRAFTS) {
145            // TODO need id-based API for MessageCompose
146            // MessageCompose.actionEditDraft(this, id);
147        }
148        else {
149            MessageView.actionView(this, id);
150        }
151    }
152
153    public void onClick(View v) {
154        // TODO Auto-generated method stub
155
156    }
157
158    @Override
159    public boolean onCreateOptionsMenu(Menu menu) {
160        super.onCreateOptionsMenu(menu);
161        getMenuInflater().inflate(R.menu.message_list_option, menu);
162        return true;
163    }
164
165    @Override
166    public boolean onOptionsItemSelected(MenuItem item) {
167        switch (item.getItemId()) {
168            case R.id.refresh:
169                onRefresh();
170                return true;
171            case R.id.accounts:
172                onAccounts();
173                return true;
174            case R.id.compose:
175                onCompose();
176                return true;
177            case R.id.account_settings:
178                onEditAccount();
179                return true;
180            default:
181                return super.onOptionsItemSelected(item);
182        }
183    }
184
185    private void onRefresh() {
186        // TODO: This needs to loop through all open mailboxes (there might be more than one)
187        EmailContent.Mailbox mailbox =
188            EmailContent.Mailbox.restoreMailboxWithId(this, mMailboxId);
189        EmailContent.Account account =
190            EmailContent.Account.restoreAccountWithId(this, mailbox.mAccountKey);
191        mHandler.progress(true);
192        Controller.getInstance(getApplication()).updateMailbox(
193                account, mailbox, mControllerCallback);
194    }
195
196    private void onAccounts() {
197        Accounts.actionShowAccounts(this);
198        finish();
199    }
200
201    private void onCompose() {
202        // TODO: Select correct account to send from when there are multiple mailboxes
203        EmailContent.Mailbox mailbox =
204            EmailContent.Mailbox.restoreMailboxWithId(this, mMailboxId);
205        MessageCompose.actionCompose(this, mailbox.mAccountKey);
206    }
207
208    private void onEditAccount() {
209        // TODO: Select correct account to edit when there are multiple mailboxes
210        EmailContent.Mailbox mailbox =
211            EmailContent.Mailbox.restoreMailboxWithId(this, mMailboxId);
212        AccountSettings.actionSettings(this, mailbox.mAccountKey);
213    }
214
215    /**
216     * Async task for loading a single folder out of the UI thread
217     *
218     * TODO: Extend API to support compound select (e.g. merged inbox list)
219     */
220    private class LoadMessagesTask extends AsyncTask<Void, Void, Cursor> {
221
222        private long mMailboxKey;
223
224        /**
225         * Special constructor to cache some local info
226         */
227        public LoadMessagesTask(long mailboxKey) {
228            mMailboxKey = mailboxKey;
229        }
230
231        @Override
232        protected Cursor doInBackground(Void... params) {
233            return MessageList.this.managedQuery(
234                    EmailContent.Message.CONTENT_URI,
235                    MessageListAdapter.PROJECTION,
236                    EmailContent.MessageColumns.MAILBOX_KEY + "=?",
237                    new String[] {
238                            String.valueOf(mMailboxKey)
239                            },
240                    EmailContent.MessageColumns.TIMESTAMP + " DESC");
241        }
242
243        @Override
244        protected void onPostExecute(Cursor cursor) {
245            MessageList.this.mListAdapter.changeCursor(cursor);
246
247            // TODO: remove this hack and only update at the right time
248            if (cursor != null && cursor.getCount() == 0) {
249                onRefresh();
250            }
251        }
252    }
253
254    /**
255     * Handler for UI-thread operations (when called from callbacks or any other threads)
256     */
257    class MessageListHandler extends Handler {
258        private static final int MSG_PROGRESS = 1;
259
260        @Override
261        public void handleMessage(android.os.Message msg) {
262            switch (msg.what) {
263                case MSG_PROGRESS:
264                    setProgressBarIndeterminateVisibility(msg.arg1 != 0);
265                    break;
266                default:
267                    super.handleMessage(msg);
268            }
269        }
270
271        public void progress(boolean progress) {
272            android.os.Message msg =android.os.Message.obtain();
273            msg.what = MSG_PROGRESS;
274            msg.arg1 = progress ? 1 : 0;
275            sendMessage(msg);
276        }
277    }
278
279    /**
280     * Callback for async Controller results.  This is all a placeholder until we figure out the
281     * final way to do this.
282     */
283    private class ControllerResults implements Controller.Result {
284        public void updateMailboxListCallback(MessagingException result, long accountKey) {
285        }
286
287        public void updateMailboxCallback(MessagingException result, long accountKey,
288                long mailboxKey, int totalMessagesInMailbox, int numNewMessages) {
289            mHandler.progress(false);
290        }
291    }
292
293    /**
294     * This class implements the adapter for displaying messages based on cursors.
295     */
296    private static class MessageListAdapter extends CursorAdapter {
297
298        public static final int COLUMN_ID = 0;
299        public static final int COLUMN_MAILBOX_KEY = 1;
300        public static final int COLUMN_DISPLAY_NAME = 2;
301        public static final int COLUMN_SUBJECT = 3;
302        public static final int COLUMN_DATE = 4;
303        public static final int COLUMN_READ = 5;
304        public static final int COLUMN_FAVORITE = 6;
305        public static final int COLUMN_ATTACHMENTS = 7;
306
307        public static final String[] PROJECTION = new String[] {
308            EmailContent.RECORD_ID, MessageColumns.MAILBOX_KEY,
309            MessageColumns.DISPLAY_NAME, MessageColumns.SUBJECT, MessageColumns.TIMESTAMP,
310            MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, MessageColumns.FLAG_ATTACHMENT,
311        };
312
313        Context mContext;
314        private LayoutInflater mInflater;
315        private Drawable mAttachmentIcon;
316        private Drawable mFavoriteIconOn;
317        private Drawable mFavoriteIconOff;
318        private Drawable mSelectedIconOn;
319        private Drawable mSelectedIconOff;
320
321        private java.text.DateFormat mDateFormat;
322        private java.text.DateFormat mDayFormat;
323        private java.text.DateFormat mTimeFormat;
324
325        private HashSet<Long> mChecked = new HashSet<Long>();
326
327        public MessageListAdapter(Context context) {
328            super(context, null);
329            mContext = context;
330            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
331
332            Resources resources = context.getResources();
333            mAttachmentIcon = resources.getDrawable(R.drawable.ic_mms_attachment_small);
334            mFavoriteIconOn = resources.getDrawable(android.R.drawable.star_on);
335            mFavoriteIconOff = resources.getDrawable(android.R.drawable.star_off);
336            mSelectedIconOn = resources.getDrawable(R.drawable.btn_check_buttonless_on);
337            mSelectedIconOff = resources.getDrawable(R.drawable.btn_check_buttonless_off);
338
339            mDateFormat = android.text.format.DateFormat.getDateFormat(context);    // short date
340            mDayFormat = android.text.format.DateFormat.getDateFormat(context);     // TODO: day
341            mTimeFormat = android.text.format.DateFormat.getTimeFormat(context);    // 12/24 time
342        }
343
344        @Override
345        public void bindView(View view, Context context, Cursor cursor) {
346            View clipView = view.findViewById(R.id.chip);
347            boolean readFlag = cursor.getInt(COLUMN_READ) != 0;
348            clipView.getBackground().setAlpha(readFlag ? 0 : 255);
349
350            TextView fromView = (TextView) view.findViewById(R.id.from);
351            String text = cursor.getString(COLUMN_DISPLAY_NAME);
352            if (text != null) fromView.setText(text);
353
354            boolean hasAttachments = cursor.getInt(COLUMN_ATTACHMENTS) != 0;
355            fromView.setCompoundDrawablesWithIntrinsicBounds(null, null,
356                    hasAttachments ? mAttachmentIcon : null, null);
357
358            TextView subjectView = (TextView) view.findViewById(R.id.subject);
359            text = cursor.getString(COLUMN_SUBJECT);
360            if (text != null) subjectView.setText(text);
361
362            // TODO ui spec suggests "time", "day", "date" - implement "day"
363            TextView dateView = (TextView) view.findViewById(R.id.date);
364            long timestamp = cursor.getLong(COLUMN_DATE);
365            Date date = new Date(timestamp);
366            if (Utility.isDateToday(date)) {
367                text = mTimeFormat.format(date);
368            } else {
369                text = mDateFormat.format(date);
370            }
371            dateView.setText(text);
372
373            ImageView selectedView = (ImageView) view.findViewById(R.id.selected);
374            boolean selected = mChecked.contains(Long.valueOf(cursor.getLong(COLUMN_ID)));
375            selectedView.setImageDrawable(selected ? mSelectedIconOn : mSelectedIconOff);
376
377            ImageView favoriteView = (ImageView) view.findViewById(R.id.favorite);
378            boolean favorite = cursor.getInt(COLUMN_FAVORITE) != 0;
379            favoriteView.setImageDrawable(favorite ? mFavoriteIconOn : mFavoriteIconOff);
380        }
381
382        @Override
383        public View newView(Context context, Cursor cursor, ViewGroup parent) {
384            // TODO:  This should be a custom view so we can deal with touch events
385            // in the checkbox & star.
386            return mInflater.inflate(R.layout.message_list_item, parent, false);
387        }
388    }
389
390
391}
392