MessagesAdapter.java revision 187d0334849cfd922f0df05e57d77224f2f70378
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.os.Handler;
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,
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
76    private static final int ITEM_BACKGROUND_SELECTED = 0xFFB0FFB0; // TODO color not finalized
77
78    private final LayoutInflater mInflater;
79    private final Drawable mAttachmentIcon;
80    private final Drawable mInvitationIcon;
81    private final Drawable mFavoriteIconOn;
82    private final Drawable mFavoriteIconOff;
83
84    private final ColorStateList mTextColorPrimary;
85    private final ColorStateList mTextColorSecondary;
86
87    // How long we want to wait for refreshes (a good starting guess)
88    // I suspect this could be lowered down to even 1000 or so, but this seems ok for now
89    private static final int REFRESH_INTERVAL_MS = 2500;
90
91    private final java.text.DateFormat mDateFormat;
92    private final java.text.DateFormat mTimeFormat;
93
94    private final HashSet<Long> mSelected = new HashSet<Long>();
95
96    /**
97     * Callback from MessageListAdapter.  All methods are called on the UI thread.
98     */
99    public interface Callback {
100        /** Called when the use starts/unstars a message */
101        void onAdapterFavoriteChanged(MessageListItem itemView, boolean newFavorite);
102        /** Called when the user selects/unselects a message */
103        void onAdapterSelectedChanged(MessageListItem itemView, boolean newSelected,
104                int mSelectedCount);
105    }
106
107    private final Callback mCallback;
108
109    /**
110     * Used to call callbacks in the UI thread.
111     */
112    private final Handler mHandler;
113
114    public MessagesAdapter(Context context, Handler handler, Callback callback) {
115        super(context.getApplicationContext(), null, 0 /* no auto requery */);
116        mHandler = handler;
117        mCallback = callback;
118        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
119
120        Resources resources = context.getResources();
121        mAttachmentIcon = resources.getDrawable(R.drawable.ic_mms_attachment_small);
122        mInvitationIcon = resources.getDrawable(R.drawable.ic_calendar_event_small);
123        mFavoriteIconOn = resources.getDrawable(R.drawable.btn_star_big_buttonless_dark_on);
124        mFavoriteIconOff = resources.getDrawable(R.drawable.btn_star_big_buttonless_dark_off);
125
126        Theme theme = context.getTheme();
127        TypedArray array;
128        array = theme.obtainStyledAttributes(new int[] { android.R.attr.textColorPrimary });
129        mTextColorPrimary = resources.getColorStateList(array.getResourceId(0, 0));
130        array = theme.obtainStyledAttributes(new int[] { android.R.attr.textColorSecondary });
131        mTextColorSecondary = resources.getColorStateList(array.getResourceId(0, 0));
132
133        mDateFormat = android.text.format.DateFormat.getDateFormat(context);    // short date
134        mTimeFormat = android.text.format.DateFormat.getTimeFormat(context);    // 12/24 time
135    }
136
137    public void onSaveInstanceState(Bundle outState) {
138        Set<Long> checkedset = getSelectedSet();
139        long[] checkedarray = new long[checkedset.size()];
140        int i = 0;
141        for (Long l : checkedset) {
142            checkedarray[i] = l;
143            i++;
144        }
145        outState.putLongArray(STATE_CHECKED_ITEMS, checkedarray);
146    }
147
148    public void loadState(Bundle savedInstanceState) {
149        Set<Long> checkedset = getSelectedSet();
150        for (long l: savedInstanceState.getLongArray(STATE_CHECKED_ITEMS)) {
151            checkedset.add(l);
152        }
153    }
154
155    public Set<Long> getSelectedSet() {
156        return mSelected;
157    }
158
159    public boolean isSelected(MessageListItem itemView) {
160        return mSelected.contains(itemView.mMessageId);
161    }
162
163    @Override
164    public void bindView(View view, Context context, Cursor cursor) {
165        // Reset the view (in case it was recycled) and prepare for binding
166        MessageListItem itemView = (MessageListItem) view;
167        itemView.bindViewInit(this);
168
169        // Load the public fields in the view (for later use)
170        itemView.mMessageId = cursor.getLong(COLUMN_ID);
171        itemView.mMailboxId = cursor.getLong(COLUMN_MAILBOX_KEY);
172        itemView.mAccountId = cursor.getLong(COLUMN_ACCOUNT_KEY);
173        itemView.mRead = cursor.getInt(COLUMN_READ) != 0;
174        itemView.mFavorite = cursor.getInt(COLUMN_FAVORITE) != 0;
175
176        // Load the UI
177        View chipView = view.findViewById(R.id.chip);
178        chipView.setBackgroundResource(Email.getAccountColorResourceId(itemView.mAccountId));
179
180        TextView fromView = (TextView) view.findViewById(R.id.from);
181        String text = cursor.getString(COLUMN_DISPLAY_NAME);
182        fromView.setText(text);
183
184        TextView subjectView = (TextView) view.findViewById(R.id.subject);
185        text = cursor.getString(COLUMN_SUBJECT);
186        subjectView.setText(text);
187
188        boolean hasInvitation =
189                    (cursor.getInt(COLUMN_FLAGS) & Message.FLAG_INCOMING_MEETING_INVITE) != 0;
190        boolean hasAttachments = cursor.getInt(COLUMN_ATTACHMENTS) != 0;
191        Drawable icon =
192                hasInvitation ? mInvitationIcon
193                : hasAttachments ? mAttachmentIcon : null;
194        subjectView.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
195
196        // TODO ui spec suggests "time", "day", "date" - implement "day"
197        TextView dateView = (TextView) view.findViewById(R.id.date);
198        long timestamp = cursor.getLong(COLUMN_DATE);
199        Date date = new Date(timestamp);
200        if (Utility.isDateToday(date)) {
201            text = mTimeFormat.format(date);
202        } else {
203            text = mDateFormat.format(date);
204        }
205        dateView.setText(text);
206
207        if (itemView.mRead) {
208            subjectView.setTypeface(Typeface.DEFAULT);
209            fromView.setTypeface(Typeface.DEFAULT);
210            fromView.setTextColor(mTextColorSecondary);
211            view.setBackgroundDrawable(context.getResources().getDrawable(
212                    R.drawable.message_list_item_background_read));
213        } else {
214            subjectView.setTypeface(Typeface.DEFAULT_BOLD);
215            fromView.setTypeface(Typeface.DEFAULT_BOLD);
216            fromView.setTextColor(mTextColorPrimary);
217            view.setBackgroundDrawable(context.getResources().getDrawable(
218                    R.drawable.message_list_item_background_unread));
219        }
220
221        ImageView favoriteView = (ImageView) view.findViewById(R.id.favorite);
222        favoriteView.setImageDrawable(itemView.mFavorite ? mFavoriteIconOn : mFavoriteIconOff);
223        updateBackgroundColor(itemView);
224    }
225
226    @Override
227    public View newView(Context context, Cursor cursor, ViewGroup parent) {
228        return mInflater.inflate(R.layout.message_list_item, parent, false);
229    }
230
231    /**
232     * This is used as a callback from the list items, to set the selected state
233     *
234     * <p>Must be called on the UI thread.
235     *
236     * @param itemView the item being changed
237     * @param newSelected the new value of the selected flag (checkbox state)
238     */
239    public void updateSelected(MessageListItem itemView, boolean newSelected) {
240        // Set checkbox state in list, and show/hide panel if necessary
241        Long id = Long.valueOf(itemView.mMessageId);
242        if (newSelected) {
243            mSelected.add(id);
244        } else {
245            mSelected.remove(id);
246        }
247        updateBackgroundColor(itemView);
248        if (mCallback != null) {
249            mCallback.onAdapterSelectedChanged(itemView, newSelected, mSelected.size());
250        }
251    }
252
253    /**
254     * This is used as a callback from the list items, to set the favorite state
255     *
256     * <p>Must be called on the UI thread.
257     *
258     * @param itemView the item being changed
259     * @param newFavorite the new value of the favorite flag (star state)
260     */
261    public void updateFavorite(MessageListItem itemView, boolean newFavorite) {
262        ImageView favoriteView = (ImageView) itemView.findViewById(R.id.favorite);
263        favoriteView.setImageDrawable(newFavorite ? mFavoriteIconOn : mFavoriteIconOff);
264        if (mCallback != null) {
265            mCallback.onAdapterFavoriteChanged(itemView, newFavorite);
266        }
267    }
268
269    /**
270     * Update the background color according to the selection state.
271     */
272    public void updateBackgroundColor(MessageListItem itemView) {
273        if (isSelected(itemView)) {
274            itemView.setBackgroundColor(ITEM_BACKGROUND_SELECTED);
275        } else {
276            itemView.setBackgroundDrawable(null); // Change back to default.
277        }
278    }
279
280    public static Loader<Cursor> createLoader(Context context, long mailboxId) {
281        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
282            Log.d(Email.LOG_TAG, "MessagesAdapter createLoader mailboxId=" + mailboxId);
283        }
284        String selection =
285                Utility.buildMailboxIdSelection(context.getContentResolver(), mailboxId);
286        return new ThrottlingCursorLoader(context, EmailContent.Message.CONTENT_URI,
287                MESSAGE_PROJECTION, selection, null,
288                EmailContent.MessageColumns.TIMESTAMP + " DESC", REFRESH_INTERVAL_MS);
289
290    }
291}
292