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