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