MessageViewFragment.java revision 0435a0775e3d6637304a5a4e93675e7462397eb1
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.mail.MeetingInfo;
23import com.android.email.mail.PackedString;
24import com.android.email.provider.EmailContent.Mailbox;
25import com.android.email.provider.EmailContent.Message;
26import com.android.email.service.EmailServiceConstants;
27
28import android.content.res.Resources;
29import android.graphics.drawable.Drawable;
30import android.os.Bundle;
31import android.util.Log;
32import android.view.LayoutInflater;
33import android.view.View;
34import android.view.ViewGroup;
35import android.widget.ImageView;
36import android.widget.TextView;
37
38import java.security.InvalidParameterException;
39
40/**
41 * A {@link MessageViewFragmentBase} subclass for regular email messages.  (regular as in "not eml
42 * files").
43 *
44 * See {@link MessageViewBase} for the class relation diagram.
45 */
46public class MessageViewFragment extends MessageViewFragmentBase {
47
48    private ImageView mFavoriteIcon;
49    private View mInviteSection;
50
51    // calendar meeting invite answers
52    private TextView mMeetingYes;
53    private TextView mMeetingMaybe;
54    private TextView mMeetingNo;
55    private MessageCommandButtonView mCommandButtons;
56    private int mPreviousMeetingResponse = -1;
57
58    private Drawable mFavoriteIconOn;
59    private Drawable mFavoriteIconOff;
60
61    /** ID of the message that will be loaded */
62    private long mMessageIdToOpen = -1;
63
64    /** ID of the currently shown message */
65    private long mCurrentMessageId = -1;
66
67    private final CommandButtonCallback mCommandButtonCallback = new CommandButtonCallback();
68
69    /**
70     * This class has more call backs than {@link MessageViewFragmentBase}.
71     *
72     * - EML files can't be "mark unread".
73     * - EML files can't have the invite buttons or the view in calender link.
74     *   Note EML files can have ICS (calendar invitation) files, but we don't treat them as
75     *   invites.  (Only exchange provider sets the FLAG_INCOMING_MEETING_INVITE
76     *   flag.)
77     *   It'd be weird to respond to an invitation in an EML that might not be addressed to you...
78     */
79    public interface Callback extends MessageViewFragmentBase.Callback {
80        /** Called when the "view in calendar" link is clicked. */
81        public void onCalendarLinkClicked(long epochEventStartTime);
82
83        /**
84         * Called when a calender response button is clicked.
85         *
86         * @param response one of {@link EmailServiceConstants#MEETING_REQUEST_ACCEPTED},
87         * {@link EmailServiceConstants#MEETING_REQUEST_DECLINED}, or
88         * {@link EmailServiceConstants#MEETING_REQUEST_TENTATIVE}.
89         */
90        public void onRespondedToInvite(int response);
91
92        /** Called when the current message is set unread. */
93        public void onMessageSetUnread();
94
95        /** Called when "move to newer" button is pressed. */
96        public void onMoveToNewer();
97
98        /** Called when "move to older" button is pressed. */
99        public void onMoveToOlder();
100
101        /**
102         * Called right before the current message will be deleted.
103         * Callees don't have to delete messages.  The fragment does.
104         */
105        public void onBeforeMessageDelete();
106
107        /** Called when the move button is pressed. */
108        public void onMoveMessage();
109        /** Called when the forward button is pressed. */
110        public void onForward();
111        /** Called when the reply button is pressed. */
112        public void onReply();
113        /** Called when the reply-all button is pressed. */
114        public void onReplyAll();
115    }
116
117    public static final class EmptyCallback extends MessageViewFragmentBase.EmptyCallback
118            implements Callback {
119        public static final Callback INSTANCE = new EmptyCallback();
120
121        @Override public void onCalendarLinkClicked(long epochEventStartTime) { }
122        @Override public void onMessageSetUnread() { }
123        @Override public void onRespondedToInvite(int response) { }
124        @Override public void onMoveToNewer() { }
125        @Override public void onMoveToOlder() { }
126        @Override public void onBeforeMessageDelete() { }
127        @Override public void onMoveMessage() { }
128        @Override public void onForward() { }
129        @Override public void onReply() { }
130        @Override public void onReplyAll() { }
131    }
132
133    private Callback mCallback = EmptyCallback.INSTANCE;
134
135    @Override
136    public void onCreate(Bundle savedInstanceState) {
137        super.onCreate(savedInstanceState);
138
139        final Resources res = getActivity().getResources();
140        mFavoriteIconOn = res.getDrawable(R.drawable.btn_star_big_buttonless_on);
141        mFavoriteIconOff = res.getDrawable(R.drawable.btn_star_big_buttonless_off);
142    }
143
144    @Override
145    public View onCreateView(
146            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
147        final View view = super.onCreateView(inflater, container, savedInstanceState);
148
149        mFavoriteIcon = (ImageView) view.findViewById(R.id.favorite);
150        mInviteSection = view.findViewById(R.id.invite_section);
151        mFavoriteIcon.setOnClickListener(this);
152        mMeetingYes = (TextView) view.findViewById(R.id.accept);
153        mMeetingMaybe = (TextView) view.findViewById(R.id.maybe);
154        mMeetingNo = (TextView) view.findViewById(R.id.decline);
155
156        mMeetingYes.setOnClickListener(this);
157        mMeetingMaybe.setOnClickListener(this);
158        mMeetingNo.setOnClickListener(this);
159        view.findViewById(R.id.invite_link).setOnClickListener(this);
160
161        // Show the command buttons at the bottom.
162        mCommandButtons =
163                (MessageCommandButtonView) view.findViewById(R.id.message_command_buttons);
164        mCommandButtons.setVisibility(View.VISIBLE);
165        mCommandButtons.setCallback(mCommandButtonCallback);
166
167        return view;
168    }
169
170    public void setCallback(Callback callback) {
171        mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback;
172        super.setCallback(mCallback);
173    }
174
175    /**
176     * @deprecated TODO Remove this call from MessageView
177     */
178    public void hideCommandButtons() {
179        mCommandButtons.setVisibility(View.GONE);
180    }
181
182    /** Called by activities to set an id of a message to open. */
183    public void openMessage(long messageId) {
184        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
185            Log.d(Email.LOG_TAG, "MessageViewFragment openMessage");
186        }
187        if (messageId == -1) {
188            throw new InvalidParameterException();
189        }
190        mMessageIdToOpen = messageId;
191        openMessageIfStarted();
192    }
193
194    @Override
195    protected boolean isMessageSpecified() {
196        return mMessageIdToOpen != -1;
197    }
198
199    @Override
200    protected Message openMessageSync() {
201        return Message.restoreMessageWithId(getActivity(), mMessageIdToOpen);
202    }
203
204    @Override
205    protected void onMessageShown(long messageId, int mailboxType) {
206        super.onMessageShown(messageId, mailboxType);
207
208        // Remember the currently shown message ID.
209        mCurrentMessageId = messageId;
210
211        // Disable forward/reply buttons as necessary.
212        // (Draft messages shouldn't be opened with this fragment in the first place, but currently
213        // it's possible due to a problem with "All Starred".)
214        mCommandButtons.enableReplyForwardButtons((mailboxType != Mailbox.TYPE_TRASH)
215                && (mailboxType != Mailbox.TYPE_TRASH));
216    }
217
218    public void enableNavigationButons(boolean enableMoveToNewer, boolean enableMoveToOlder) {
219        mCommandButtons.enableNavigationButons(enableMoveToNewer, enableMoveToOlder);
220    }
221
222    /**
223     * Toggle favorite status and write back to provider
224     */
225    private void onClickFavorite() {
226        Message message = getMessage();
227
228        // Update UI
229        boolean newFavorite = ! message.mFlagFavorite;
230        mFavoriteIcon.setImageDrawable(newFavorite ? mFavoriteIconOn : mFavoriteIconOff);
231
232        // Update provider
233        message.mFlagFavorite = newFavorite;
234        getController().setMessageFavorite(message.mId, newFavorite);
235    }
236
237    /**
238     * Set message read/unread.
239     */
240    public void onMarkMessageAsRead(boolean isRead) {
241        Message message = getMessage();
242        if (message.mFlagRead != isRead) {
243            message.mFlagRead = isRead;
244            getController().setMessageRead(message.mId, isRead);
245            if (!isRead) { // Became unread.  We need to close the message.
246                mCallback.onMessageSetUnread();
247            }
248        }
249    }
250
251    /**
252     * Send a service message indicating that a meeting invite button has been clicked.
253     */
254    private void onRespondToInvite(int response, int toastResId) {
255        Message message = getMessage();
256        // do not send twice in a row the same response
257        if (mPreviousMeetingResponse != response) {
258            getController().sendMeetingResponse(message.mId, response);
259            mPreviousMeetingResponse = response;
260        }
261        Utility.showToast(getActivity(), toastResId);
262        mCallback.onRespondedToInvite(response);
263    }
264
265    private void onInviteLinkClicked() {
266        Message message = getMessage();
267        String startTime = new PackedString(message.mMeetingInfo).get(MeetingInfo.MEETING_DTSTART);
268        if (startTime != null) {
269            long epochTimeMillis = Utility.parseEmailDateTimeToMillis(startTime);
270            mCallback.onCalendarLinkClicked(epochTimeMillis);
271        } else {
272            Email.log("meetingInfo without DTSTART " + message.mMeetingInfo);
273        }
274    }
275
276    @Override
277    public void onClick(View view) {
278        if (!isMessageOpen()) {
279            return; // Ignore.
280        }
281        switch (view.getId()) {
282            case R.id.favorite:
283                onClickFavorite();
284                return;
285            case R.id.accept:
286                onRespondToInvite(EmailServiceConstants.MEETING_REQUEST_ACCEPTED,
287                         R.string.message_view_invite_toast_yes);
288                return;
289            case R.id.maybe:
290                onRespondToInvite(EmailServiceConstants.MEETING_REQUEST_TENTATIVE,
291                         R.string.message_view_invite_toast_maybe);
292                return;
293            case R.id.decline:
294                onRespondToInvite(EmailServiceConstants.MEETING_REQUEST_DECLINED,
295                         R.string.message_view_invite_toast_no);
296                return;
297            case R.id.invite_link:
298                onInviteLinkClicked();
299                return;
300        }
301        super.onClick(view);
302    }
303
304    /**
305     * {@inheritDoc}
306     *
307     * Mark the current as unread.
308     */
309    @Override
310    protected void onPostLoadBody() {
311        onMarkMessageAsRead(true);
312    }
313
314    /**
315     * {@inheritDoc}
316     *
317     * - Update the favorite star icon.
318     * - Show the invite section if necessary.
319     */
320    @Override
321    protected void reloadUiFromMessage(Message message, boolean okToFetch) {
322        super.reloadUiFromMessage(message, okToFetch);
323
324        mFavoriteIcon.setImageDrawable(message.mFlagFavorite ? mFavoriteIconOn : mFavoriteIconOff);
325        // Show the message invite section if we're an incoming meeting invitation only
326        mInviteSection.setVisibility((message.mFlags & Message.FLAG_INCOMING_MEETING_INVITE) != 0 ?
327                View.VISIBLE : View.GONE);
328    }
329
330    private class CommandButtonCallback implements MessageCommandButtonView.Callback {
331        @Override
332        public void onMoveToNewer() {
333            mCallback.onMoveToNewer();
334        }
335
336        @Override
337        public void onMoveToOlder() {
338            mCallback.onMoveToOlder();
339        }
340
341        @Override
342        public void onDelete() {
343            mCallback.onBeforeMessageDelete();
344            ActivityHelper.deleteMessage(getActivity(), mCurrentMessageId);
345        }
346
347        @Override
348        public void onMove() {
349            mCallback.onMoveMessage();
350        }
351
352        @Override
353        public void onForward() {
354            mCallback.onForward();
355        }
356
357        @Override
358        public void onReply() {
359            mCallback.onReply();
360        }
361
362        @Override
363        public void onReplyAll() {
364            mCallback.onReplyAll();
365        }
366
367        @Override
368        public void onMarkUnread() {
369            onMarkMessageAsRead(false);
370            mCallback.onMessageSetUnread();
371        }
372    }
373}
374