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