MessagesAdapter.java revision 60c6dc47115f76e4287de4e58c18131d373629a5
1/* 2 * Copyright (C) 2010 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.Email; 20import com.android.email.R; 21import com.android.email.Utility; 22import com.android.email.data.ThrottlingCursorLoader; 23import com.android.email.provider.EmailContent; 24import com.android.email.provider.EmailContent.Message; 25import com.android.email.provider.EmailContent.MessageColumns; 26 27import android.content.Context; 28import android.content.Loader; 29import android.content.res.ColorStateList; 30import android.content.res.Resources; 31import android.content.res.Resources.Theme; 32import android.content.res.TypedArray; 33import android.database.Cursor; 34import android.graphics.Typeface; 35import android.graphics.drawable.Drawable; 36import android.os.Bundle; 37import android.text.TextUtils; 38import android.util.Log; 39import android.view.LayoutInflater; 40import android.view.View; 41import android.view.ViewGroup; 42import android.widget.CursorAdapter; 43import android.widget.ImageView; 44import android.widget.TextView; 45 46import java.util.Date; 47import java.util.HashSet; 48import java.util.Set; 49 50 51/** 52 * This class implements the adapter for displaying messages based on cursors. 53 */ 54/* package */ class MessagesAdapter extends CursorAdapter { 55 private static final String STATE_CHECKED_ITEMS = 56 "com.android.email.activity.MessagesAdapter.checkedItems"; 57 58 /* package */ static final String[] MESSAGE_PROJECTION = new String[] { 59 EmailContent.RECORD_ID, MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY, 60 MessageColumns.DISPLAY_NAME, MessageColumns.SUBJECT, MessageColumns.TIMESTAMP, 61 MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, MessageColumns.FLAG_ATTACHMENT, 62 MessageColumns.FLAGS, MessageColumns.SNIPPET 63 }; 64 65 public static final int COLUMN_ID = 0; 66 public static final int COLUMN_MAILBOX_KEY = 1; 67 public static final int COLUMN_ACCOUNT_KEY = 2; 68 public static final int COLUMN_DISPLAY_NAME = 3; 69 public static final int COLUMN_SUBJECT = 4; 70 public static final int COLUMN_DATE = 5; 71 public static final int COLUMN_READ = 6; 72 public static final int COLUMN_FAVORITE = 7; 73 public static final int COLUMN_ATTACHMENTS = 8; 74 public static final int COLUMN_FLAGS = 9; 75 public static final int COLUMN_SNIPPET = 10; 76 77 private final LayoutInflater mInflater; 78 private final Drawable mFavoriteIconOn; 79 private final Drawable mFavoriteIconOff; 80 private final Drawable mSelectedIconOn; 81 private final Drawable mSelectedIconOff; 82 83 private final ColorStateList mTextColorPrimary; 84 private final ColorStateList mTextColorSecondary; 85 86 private final java.text.DateFormat mDateFormat; 87 private final java.text.DateFormat mTimeFormat; 88 89 /** 90 * Set of seleced message IDs. 91 */ 92 private final HashSet<Long> mSelectedSet = new HashSet<Long>(); 93 94 /** 95 * Callback from MessageListAdapter. All methods are called on the UI thread. 96 */ 97 public interface Callback { 98 /** Called when the use starts/unstars a message */ 99 void onAdapterFavoriteChanged(MessageListItem itemView, boolean newFavorite); 100 /** Called when the user selects/unselects a message */ 101 void onAdapterSelectedChanged(MessageListItem itemView, boolean newSelected, 102 int mSelectedCount); 103 } 104 105 private final Callback mCallback; 106 107 public MessagesAdapter(Context context, Callback callback) { 108 super(context.getApplicationContext(), null, 0 /* no auto requery */); 109 mCallback = callback; 110 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 111 112 Resources resources = context.getResources(); 113 mFavoriteIconOn = resources.getDrawable(R.drawable.btn_star_big_buttonless_dark_on); 114 mFavoriteIconOff = resources.getDrawable(R.drawable.btn_star_big_buttonless_dark_off); 115 mSelectedIconOn = resources.getDrawable(R.drawable.btn_check_buttonless_dark_on); 116 mSelectedIconOff = resources.getDrawable(R.drawable.btn_check_buttonless_dark_off); 117 118 Theme theme = context.getTheme(); 119 TypedArray array; 120 array = theme.obtainStyledAttributes(new int[] { android.R.attr.textColorPrimary }); 121 mTextColorPrimary = resources.getColorStateList(array.getResourceId(0, 0)); 122 array = theme.obtainStyledAttributes(new int[] { android.R.attr.textColorSecondary }); 123 mTextColorSecondary = resources.getColorStateList(array.getResourceId(0, 0)); 124 125 mDateFormat = android.text.format.DateFormat.getDateFormat(context); // short date 126 mTimeFormat = android.text.format.DateFormat.getTimeFormat(context); // 12/24 time 127 } 128 129 public void onSaveInstanceState(Bundle outState) { 130 Set<Long> checkedset = getSelectedSet(); 131 long[] checkedarray = new long[checkedset.size()]; 132 int i = 0; 133 for (Long l : checkedset) { 134 checkedarray[i] = l; 135 i++; 136 } 137 outState.putLongArray(STATE_CHECKED_ITEMS, checkedarray); 138 } 139 140 public void loadState(Bundle savedInstanceState) { 141 Set<Long> checkedset = getSelectedSet(); 142 for (long l: savedInstanceState.getLongArray(STATE_CHECKED_ITEMS)) { 143 checkedset.add(l); 144 } 145 } 146 147 public Set<Long> getSelectedSet() { 148 return mSelectedSet; 149 } 150 151 public boolean isSelected(MessageListItem itemView) { 152 return mSelectedSet.contains(itemView.mMessageId); 153 } 154 155 @Override 156 public void bindView(View view, Context context, Cursor cursor) { 157 // Reset the view (in case it was recycled) and prepare for binding 158 MessageListItem itemView = (MessageListItem) view; 159 itemView.bindViewInit(this); 160 161 // Load the public fields in the view (for later use) 162 itemView.mMessageId = cursor.getLong(COLUMN_ID); 163 itemView.mMailboxId = cursor.getLong(COLUMN_MAILBOX_KEY); 164 itemView.mAccountId = cursor.getLong(COLUMN_ACCOUNT_KEY); 165 itemView.mRead = cursor.getInt(COLUMN_READ) != 0; 166 itemView.mFavorite = cursor.getInt(COLUMN_FAVORITE) != 0; 167 168 // Load the UI 169 View chipView = view.findViewById(R.id.chip); 170 chipView.setBackgroundResource(Email.getAccountColorResourceId(itemView.mAccountId)); 171 172 TextView fromView = (TextView) view.findViewById(R.id.from); 173 String text = cursor.getString(COLUMN_DISPLAY_NAME); 174 fromView.setText(text); 175 176 TextView subjectView = (TextView) view.findViewById(R.id.subject); 177 text = cursor.getString(COLUMN_SUBJECT); 178 // Add in the snippet if we have one 179 // TODO Should this be spanned text? 180 // The mocks show, for new messages, only the real subject in bold... 181 // Would it be easier to simply use a 2nd TextView? This would also allow ellipsizing an 182 // overly-long subject, to let the beautiful snippet shine through. 183 String snippet = cursor.getString(COLUMN_SNIPPET); 184 if (!TextUtils.isEmpty(snippet)) { 185 if (TextUtils.isEmpty(text)) { 186 text = snippet; 187 } else { 188 text = context.getString(R.string.message_list_snippet, text, snippet); 189 } 190 } 191 subjectView.setText(text); 192 193 final boolean hasInvitation = 194 (cursor.getInt(COLUMN_FLAGS) & Message.FLAG_INCOMING_MEETING_INVITE) != 0; 195 makeVisible(view.findViewById(R.id.icon_invite), hasInvitation); 196 final boolean hasAttachments = cursor.getInt(COLUMN_ATTACHMENTS) != 0; 197 makeVisible(view.findViewById(R.id.icon_attachment), hasAttachments); 198 199 // TODO ui spec suggests "time", "day", "date" - implement "day" 200 TextView dateView = (TextView) view.findViewById(R.id.date); 201 long timestamp = cursor.getLong(COLUMN_DATE); 202 Date date = new Date(timestamp); 203 if (Utility.isDateToday(date)) { 204 text = mTimeFormat.format(date); 205 } else { 206 text = mDateFormat.format(date); 207 } 208 dateView.setText(text); 209 210 if (itemView.mRead) { 211 subjectView.setTypeface(Typeface.DEFAULT); 212 fromView.setTypeface(Typeface.DEFAULT); 213 fromView.setTextColor(mTextColorSecondary); 214 } else { 215 subjectView.setTypeface(Typeface.DEFAULT_BOLD); 216 fromView.setTypeface(Typeface.DEFAULT_BOLD); 217 fromView.setTextColor(mTextColorPrimary); 218 } 219 220 updateCheckBox(itemView); 221 changeFavoriteIcon(itemView, itemView.mFavorite); 222 updateBackgroundColor(itemView); 223 } 224 225 private static void makeVisible(View v, boolean visible) { 226 v.setVisibility(visible ? View.VISIBLE : View.GONE); 227 } 228 229 @Override 230 public View newView(Context context, Cursor cursor, ViewGroup parent) { 231 return mInflater.inflate(R.layout.message_list_item, parent, false); 232 } 233 234 private void updateCheckBox(MessageListItem itemView) { 235 ImageView selectedView = (ImageView) itemView.findViewById(R.id.selected); 236 selectedView.setImageDrawable(isSelected(itemView) ? mSelectedIconOn : mSelectedIconOff); 237 } 238 239 public void toggleSelected(MessageListItem itemView) { 240 updateSelected(itemView, !isSelected(itemView)); 241 } 242 243 /** 244 * This is used as a callback from the list items, to set the selected state 245 * 246 * <p>Must be called on the UI thread. 247 * 248 * @param itemView the item being changed 249 * @param newSelected the new value of the selected flag (checkbox state) 250 */ 251 private void updateSelected(MessageListItem itemView, boolean newSelected) { 252 if (newSelected) { 253 mSelectedSet.add(itemView.mMessageId); 254 } else { 255 mSelectedSet.remove(itemView.mMessageId); 256 } 257 updateCheckBox(itemView); 258 updateBackgroundColor(itemView); 259 if (mCallback != null) { 260 mCallback.onAdapterSelectedChanged(itemView, newSelected, mSelectedSet.size()); 261 } 262 } 263 264 /** 265 * This is used as a callback from the list items, to set the favorite state 266 * 267 * <p>Must be called on the UI thread. 268 * 269 * @param itemView the item being changed 270 * @param newFavorite the new value of the favorite flag (star state) 271 */ 272 public void updateFavorite(MessageListItem itemView, boolean newFavorite) { 273 changeFavoriteIcon(itemView, newFavorite); 274 if (mCallback != null) { 275 mCallback.onAdapterFavoriteChanged(itemView, newFavorite); 276 } 277 } 278 279 private void changeFavoriteIcon(MessageListItem view, boolean isFavorite) { 280 ((ImageView) view.findViewById(R.id.favorite)).setImageDrawable( 281 isFavorite ? mFavoriteIconOn : mFavoriteIconOff); 282 } 283 284 /** 285 * Update the background color according to the selection state. 286 */ 287 public void updateBackgroundColor(MessageListItem itemView) { 288 // TODO Visual for selected items is not decided. 289 } 290 291 public static Loader<Cursor> createLoader(Context context, long mailboxId) { 292 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 293 Log.d(Email.LOG_TAG, "MessagesAdapter createLoader mailboxId=" + mailboxId); 294 } 295 return new MessagesCursor(context, mailboxId); 296 297 } 298 299 private static class MessagesCursor extends ThrottlingCursorLoader { 300 private final Context mContext; 301 private final long mMailboxId; 302 303 public MessagesCursor(Context context, long mailboxId) { 304 // Initialize with no where clause. We'll set it later. 305 super(context, EmailContent.Message.CONTENT_URI, 306 MESSAGE_PROJECTION, null, null, 307 EmailContent.MessageColumns.TIMESTAMP + " DESC"); 308 mContext = context; 309 mMailboxId = mailboxId; 310 } 311 312 @Override 313 public Cursor loadInBackground() { 314 // Determine the where clause. (Can't do this on the UI thread.) 315 setSelection(Utility.buildMailboxIdSelection(mContext, mMailboxId)); 316 317 // Then do a query. 318 return super.loadInBackground(); 319 } 320 } 321} 322