Public paste
Undefined
By: Guest | Date: Jul 14 2010 02:49 | Format: None | Expires: never | Size: 64 KB | Hits: 1275

  1. /*
  2.  * Copyright (C) 2008 Esmertec AG.
  3.  * Copyright (C) 2008 The Android Open Source Project
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17.  
  18. package com.android.mms.ui;
  19.  
  20. import static android.content.res.Configuration.KEYBOARDHIDDEN_NO;
  21. import static com.android.mms.transaction.ProgressCallbackEntity.PROGRESS_ABORT;
  22. import static com.android.mms.transaction.ProgressCallbackEntity.PROGRESS_COMPLETE;
  23. import static com.android.mms.transaction.ProgressCallbackEntity.PROGRESS_START;
  24. import static com.android.mms.transaction.ProgressCallbackEntity.PROGRESS_STATUS_ACTION;
  25. import static com.android.mms.ui.MessageListAdapter.COLUMN_ID;
  26. import static com.android.mms.ui.MessageListAdapter.COLUMN_MSG_TYPE;
  27. import static com.android.mms.ui.MessageListAdapter.PROJECTION;
  28.  
  29. import com.android.internal.telephony.gsm.GsmAlphabet;
  30. import com.android.mms.ExceedMessageSizeException;
  31. import com.android.mms.R;
  32. import com.android.mms.ResolutionException;
  33. import com.android.mms.UnsupportContentTypeException;
  34. import com.android.mms.model.SlideModel;
  35. import com.android.mms.model.SlideshowModel;
  36. import com.android.mms.model.TextModel;
  37. import com.android.mms.transaction.MessageSender;
  38. import com.android.mms.transaction.MessagingNotification;
  39. import com.android.mms.transaction.MmsMessageSender;
  40. import com.android.mms.transaction.SmsMessageSender;
  41. import com.android.mms.ui.AttachmentEditor.OnAttachmentChangedListener;
  42. import com.android.mms.ui.MessageUtils.ResizeImageResultCallback;
  43. import com.android.mms.ui.RecipientList.Recipient;
  44. import com.android.mms.ui.RecipientsEditor.RecipientContextMenuInfo;
  45. import com.android.mms.util.SendingProgressTokenManager;
  46. import com.google.android.mms.ContentType;
  47. import com.google.android.mms.MmsException;
  48. import com.google.android.mms.pdu.EncodedStringValue;
  49. import com.google.android.mms.pdu.PduBody;
  50. import com.google.android.mms.pdu.PduHeaders;
  51. import com.google.android.mms.pdu.PduPart;
  52. import com.google.android.mms.pdu.PduPersister;
  53. import com.google.android.mms.pdu.SendReq;
  54. import com.google.android.mms.util.SqliteWrapper;
  55.  
  56. import android.app.Activity;
  57. import android.app.AlertDialog;
  58. import android.content.AsyncQueryHandler;
  59. import android.content.BroadcastReceiver;
  60. import android.content.ContentResolver;
  61. import android.content.ContentUris;
  62. import android.content.ContentValues;
  63. import android.content.Context;
  64. import android.content.DialogInterface;
  65. import android.content.Intent;
  66. import android.content.IntentFilter;
  67. import android.content.DialogInterface.OnClickListener;
  68. import android.content.res.Configuration;
  69. import android.content.res.Resources;
  70. import android.database.Cursor;
  71. import android.database.sqlite.SQLiteException;
  72. import android.graphics.Bitmap;
  73. import android.media.RingtoneManager;
  74. import android.net.Uri;
  75. import android.os.Bundle;
  76. import android.os.Handler;
  77. import android.os.Message;
  78. import android.provider.MediaStore;
  79. import android.provider.Settings;
  80. import android.provider.Contacts.People;
  81. import android.provider.Contacts.Intents.Insert;
  82. import android.provider.Telephony.Mms;
  83. import android.provider.Telephony.Sms;
  84. import android.provider.Telephony.Threads;
  85. import android.telephony.gsm.SmsMessage;
  86. import android.text.Editable;
  87. import android.text.InputFilter;
  88. import android.text.Spanned;
  89. import android.text.TextUtils;
  90. import android.text.TextWatcher;
  91. import android.text.method.TextKeyListener;
  92. import android.text.style.URLSpan;
  93. import android.util.Config;
  94. import android.util.Log;
  95. import android.view.ContextMenu;
  96. import android.view.KeyEvent;
  97. import android.view.LayoutInflater;
  98. import android.view.Menu;
  99. import android.view.MenuItem;
  100. import android.view.MotionEvent;
  101. import android.view.View;
  102. import android.view.ViewStub;
  103. import android.view.Window;
  104. import android.view.ContextMenu.ContextMenuInfo;
  105. import android.view.View.OnCreateContextMenuListener;
  106. import android.view.View.OnFocusChangeListener;
  107. import android.view.View.OnKeyListener;
  108. import android.widget.AdapterView;
  109. import android.widget.Button;
  110. import android.widget.EditText;
  111. import android.widget.LinearLayout;
  112. import android.widget.ListView;
  113. import android.widget.TextView;
  114. import android.widget.Toast;
  115.  
  116. import java.util.Arrays;
  117. import java.util.HashSet;
  118.  
  119. /**
  120.  * This is the main UI for:
  121.  * 1. Composing a new message;
  122.  * 2. Viewing/managing message history of a conversation.
  123.  *
  124.  * This activity can handle following parameters from the intent
  125.  * by which it's launched.
  126.  * thread_id long Identify the conversation to be viewed. When creating a
  127.  *         new message, this parameter shouldn't be present.
  128.  * msg_uri Uri The message which should be opened for editing in the editor.
  129.  * address String The addresses of the recipients in current conversation.
  130.  * compose_mode boolean Setting compose_mode to true will force the activity
  131.  *         to show the recipients editor and the attachment editor but hide
  132.  *         the message history. By default, this flag is set to false.
  133.  * exit_on_sent boolean Exit this activity after the message is sent.
  134.  */
  135. public class ComposeMessageActivity extends Activity
  136.         implements View.OnClickListener, OnAttachmentChangedListener {
  137.     public static final int REQUEST_CODE_ATTACH_IMAGE     = 10;
  138.     public static final int REQUEST_CODE_TAKE_PICTURE     = 11;
  139.     public static final int REQUEST_CODE_ATTACH_VIDEO     = 12;
  140.     public static final int REQUEST_CODE_ATTACH_SOUND     = 14;
  141.     public static final int REQUEST_CODE_RECORD_SOUND     = 15;
  142.     public static final int REQUEST_CODE_CREATE_SLIDESHOW = 16;
  143.  
  144.     private static final String TAG = "ComposeMessageActivity";
  145.     private static final boolean DEBUG = false;
  146.     private static final boolean TRACE = false;
  147.     private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
  148.  
  149.     // Menu ID
  150.     private static final int MENU_ADD_SUBJECT           = 0;
  151.     private static final int MENU_DELETE_THREAD         = 1;
  152.     private static final int MENU_ADD_ATTACHMENT        = 2;
  153.     private static final int MENU_DISCARD               = 3;
  154.     static final int         MENU_SEND                  = 4;
  155.     private static final int MENU_COMPOSE_NEW           = 5;
  156.     private static final int MENU_CONVERSATION_LIST     = 6;
  157.  
  158.     // Context menu ID
  159.     private static final int MENU_VIEW_CONTACT          = 12;
  160.     private static final int MENU_ADD_TO_CONTACTS       = 13;
  161.  
  162.     private static final int MENU_EDIT_MESSAGE          = 14;
  163.     private static final int MENU_VIEW_PICTURE          = 15;
  164.     private static final int MENU_VIEW_SLIDESHOW        = 16;
  165.     private static final int MENU_VIEW_MESSAGE_DETAILS  = 17;
  166.     private static final int MENU_DELETE_MESSAGE        = 18;
  167.     private static final int MENU_SEARCH                = 19;
  168.     private static final int MENU_DELIVERY_REPORT       = 20;
  169.     private static final int MENU_FORWARD_MESSAGE       = 21;
  170.     private static final int MENU_CALL_BACK             = 22;
  171.     private static final int MENU_SEND_EMAIL            = 23;
  172.  
  173.     private static final int SUBJECT_MAX_LENGTH    =  40;
  174.     private static final int RECIPIENTS_MAX_LENGTH = 312;
  175.  
  176.     private static final int MESSAGE_LIST_QUERY_TOKEN = 9527;
  177.     private static final int THREAD_READ_QUERY_TOKEN = 9696;
  178.  
  179.     private static final int MMS_THRESHOLD = 4;
  180.  
  181.     private static final int CHARS_BEFORE_COUNTER_SHOWN = 130;
  182.  
  183.     private static final long NO_DATE_FOR_DIALOG = -1L;
  184.  
  185.     private ContentResolver mContentResolver;
  186.  
  187.     // The parameters/states of the activity.
  188.     private long mThreadId;                 // Database key for the current conversation
  189.     private String mExternalAddress;        // Serialized recipients in the current conversation
  190.     private boolean mComposeMode;           // Should we show the recipients editor on startup?
  191.     private boolean mExitOnSent;            // Should we finish() after sending a message?
  192.  
  193.     private View mTopPanel;                 // View containing the recipient and subject editors
  194.     private View mBottomPanel;              // View containing the text editor, send button, ec.
  195.     private EditText mTextEditor;           // Text editor to type your message into
  196.     private TextView mTextCounter;          // Shows the number of characters used in text editor
  197.     private Button mSendButton;             // Press to detonate
  198.  
  199.     private String mMsgText;                // Text of message
  200.  
  201.     private Cursor mMsgListCursor;          // Cursor for messages-in-thread query
  202.     private final Object mMsgListCursorLock = new Object();
  203.     private MsgListQueryHandler mMsgListQueryHandler;
  204.  
  205.     private ListView mMsgListView;               // ListView for messages in this conversation
  206.     private MessageListAdapter mMsgListAdapter;  // and its corresponding ListAdapter
  207.  
  208.     private RecipientList mRecipientList;        // List of recipients for this conversation
  209.     private RecipientsEditor mRecipientsEditor;  // UI control for editing recipients
  210.  
  211.     private boolean mIsKeyboardOpen;             // Whether the hardware keyboard is visible
  212.  
  213.     private boolean mPossiblePendingNotification;   // If the message list has changed, we may have
  214.                                                     // a pending notification to deal with.
  215.  
  216.     private static final int RECIPIENTS_REQUIRE_MMS = (1 << 0);
  217.     private static final int HAS_SUBJECT = (1 << 1);
  218.     private static final int HAS_ATTACHMENT = (1 << 2);
  219.     private static final int LENGTH_REQUIRES_MMS = (1 << 3);
  220.  
  221.     private int mMessageState;                  // A bitmap of the above indicating different
  222.                                                 // properties of the message -- any bit set
  223.                                                 // will require conversion to MMS.
  224.  
  225.     private int mSeptets;   // Number of septets required for the message.
  226.     private int mMsgCount;  // Number of SMS messages required to send the current message.
  227.  
  228.     // These fields are only used in MMS compose mode (requiresMms() == true) and should
  229.     // otherwise be null.
  230.     private SlideshowModel mSlideshow;
  231.     private Uri mMessageUri;
  232.     private EditText mSubjectTextEditor;    // Text editor for MMS subject
  233.     private String mSubject;                // MMS subject
  234.     private AttachmentEditor mAttachmentEditor;
  235.     private PduPersister mPersister;
  236.  
  237.     //==========================================================
  238.     // Inner classes
  239.     //==========================================================
  240.  
  241.     private final Handler mAttachmentEditorHandler = new Handler() {
  242.         @Override
  243.         public void handleMessage(Message msg) {
  244.             switch (msg.what) {
  245.                 case AttachmentEditor.MSG_EDIT_SLIDESHOW: {
  246.                     Intent intent = new Intent(ComposeMessageActivity.this,
  247.                             SlideshowEditActivity.class);
  248.                     // Need this to make sure mMessageUri is set up.
  249.                     convertMessageIfNeeded(HAS_ATTACHMENT, true);
  250.                     intent.setData(mMessageUri);
  251.                     startActivityForResult(intent, REQUEST_CODE_CREATE_SLIDESHOW);
  252.                     break;
  253.                 }
  254.                 case AttachmentEditor.MSG_SEND_SLIDESHOW: {
  255.                     if (isPreparedForSending()) {
  256.                         ComposeMessageActivity.this.confirmSendMessageIfNeeded();
  257.                     }
  258.                     break;
  259.                 }
  260.                 case AttachmentEditor.MSG_VIEW_IMAGE:
  261.                 case AttachmentEditor.MSG_PLAY_AUDIO:
  262.                 case AttachmentEditor.MSG_PLAY_VIDEO:
  263.                 case AttachmentEditor.MSG_PLAY_SLIDESHOW: {
  264.                     Intent intent = new Intent(ComposeMessageActivity.this,
  265.                             SlideshowActivity.class);
  266.                     intent.setData(mMessageUri);
  267.                     startActivity(intent);
  268.                     break;
  269.                 }
  270.  
  271.                 case AttachmentEditor.MSG_REPLACE_IMAGE:
  272.                     showReplaceImageDialog();
  273.                     break;
  274.  
  275.                 case AttachmentEditor.MSG_REPLACE_VIDEO:
  276.                     MessageUtils.selectVideo(
  277.                             ComposeMessageActivity.this, REQUEST_CODE_ATTACH_VIDEO);
  278.                     break;
  279.  
  280.                 case AttachmentEditor.MSG_REPLACE_AUDIO:
  281.                     MessageUtils.selectAudio(
  282.                             ComposeMessageActivity.this, REQUEST_CODE_ATTACH_SOUND);
  283.                     break;
  284.  
  285.                 default:
  286.                     break;
  287.             }
  288.         }
  289.     };
  290.  
  291.     private final Handler mMessageListItemHandler = new Handler() {
  292.         @Override
  293.         public void handleMessage(Message msg) {
  294.             String type;
  295.             switch (msg.what) {
  296.                 case MessageListItem.MSG_LIST_EDIT_MMS:
  297.                     type = "mms";
  298.                     break;
  299.                 case MessageListItem.MSG_LIST_EDIT_SMS:
  300.                     type = "sms";
  301.                     break;
  302.                 default:
  303.                     Log.w(TAG, "Unknown message: " + msg.what);
  304.                     return;
  305.             }
  306.  
  307.             MessageItem msgItem = getMessageItem(type, (Long) msg.obj);
  308.             if (msgItem != null) {
  309.                 editMessageItem(msgItem);
  310.                 int attachmentType = requiresMms()
  311.                         ? MessageUtils.getAttachmentType(mSlideshow)
  312.                         : AttachmentEditor.TEXT_ONLY;
  313.                 drawBottomPanel(attachmentType);
  314.             }
  315.         }
  316.     };
  317.  
  318.     private final OnKeyListener mSubjectKeyListener = new OnKeyListener() {
  319.         public boolean onKey(View v, int keyCode, KeyEvent event) {
  320.             if (event.getAction() != KeyEvent.ACTION_DOWN) {
  321.                 return false;
  322.             }
  323.  
  324.             // When the subject editor is empty, press "DEL" to hide the input field.
  325.             if ((keyCode == KeyEvent.KEYCODE_DEL) && (mSubjectTextEditor.length() == 0)) {
  326.                 mSubjectTextEditor.setVisibility(View.GONE);
  327.                 ComposeMessageActivity.this.hideTopPanelIfNecessary();
  328.                 convertMessageIfNeeded(HAS_SUBJECT, false);
  329.                 return true;
  330.             }
  331.  
  332.             return false;
  333.         }
  334.     };
  335.  
  336.     private final OnKeyListener mEmbeddedTextEditorKeyListener =
  337.             new OnKeyListener() {
  338.                 public boolean onKey(View v, int keyCode, KeyEvent event) {
  339.                     if ((event.getAction() == KeyEvent.ACTION_DOWN)
  340.                             && (keyCode == KeyEvent.KEYCODE_ENTER)
  341.                             && !event.isShiftPressed()) {
  342.                         if (isPreparedForSending()) {
  343.                             sendMessage();
  344.                         }
  345.                         return true;
  346.                     } else {
  347.                         return false;
  348.                     }
  349.                 }
  350.             };
  351.  
  352.     private MessageItem getMessageItem(String type, long msgId) {
  353.         // Check whether the cursor is valid or not.
  354.         if (mMsgListCursor.isClosed()
  355.                 || mMsgListCursor.isBeforeFirst()
  356.                 || mMsgListCursor.isAfterLast()) {
  357.             Log.e(TAG, "Bad cursor.", new RuntimeException());
  358.             return null;
  359.         }
  360.  
  361.         MessageItem msgItem = mMsgListAdapter.getCachedMessageItem(type, msgId);
  362.         if (msgItem == null) {
  363.             try {
  364.                 msgItem = mMsgListAdapter.cacheMessageItem(type, mMsgListCursor);
  365.             } catch (MmsException e) {
  366.                 Log.e(TAG, e.getMessage());
  367.             }
  368.         }
  369.         return msgItem;
  370.     }
  371.  
  372.     private void resetCounter() {
  373.         mSeptets = 0;
  374.         mMsgCount = 1;
  375.  
  376.         mTextCounter.setText("");
  377.         mTextCounter.setVisibility(View.GONE);
  378.     }
  379.  
  380.     private void updateCounter(CharSequence text, boolean increment) {
  381.         boolean wasShown = (mSeptets >= CHARS_BEFORE_COUNTER_SHOWN);
  382.  
  383.         if (increment) {
  384.             mSeptets += GsmAlphabet.countGsmSeptets(text.toString());
  385.         } else {
  386.             mSeptets -= GsmAlphabet.countGsmSeptets(text.toString());
  387.         }
  388.  
  389.         boolean needsShown = (mSeptets >= CHARS_BEFORE_COUNTER_SHOWN);
  390.  
  391.         if (needsShown) {
  392.             // Calculate the number of messages required and space remaining.
  393.             int remainingInCurrentMessage;
  394.             if (mSeptets > SmsMessage.MAX_USER_DATA_SEPTETS) {
  395.                 // If we have more characters than will fit in one SMS, we need to factor
  396.                 // in the size of the header to determine how many will fit.
  397.                 mMsgCount = mSeptets / (SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER) + 1;
  398.                 remainingInCurrentMessage = SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER
  399.                                             - (mSeptets % SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER);
  400.             } else {
  401.                 mMsgCount = 1;
  402.                 remainingInCurrentMessage = SmsMessage.MAX_USER_DATA_SEPTETS - mSeptets;
  403.             }
  404.  
  405.             // Update the remaining characters and number of messages required.
  406.             mTextCounter.setText(remainingInCurrentMessage + " / " + mMsgCount);
  407.             if (!wasShown) {
  408.                 mTextCounter.setVisibility(View.VISIBLE);
  409.             }
  410.         } else {
  411.             if (wasShown) {
  412.                 mTextCounter.setVisibility(View.GONE);
  413.             }
  414.         }
  415.  
  416.         convertMessageIfNeeded(LENGTH_REQUIRES_MMS, mMsgCount >= MMS_THRESHOLD);
  417.     }
  418.  
  419.     private void initMmsComponents() {
  420.        // Initialize subject editor.
  421.         mSubjectTextEditor = (EditText) findViewById(R.id.subject);
  422.         mSubjectTextEditor.setOnKeyListener(mSubjectKeyListener);
  423.         mSubjectTextEditor.setFilters(new InputFilter[] {
  424.                 new InputFilter.LengthFilter(SUBJECT_MAX_LENGTH) });
  425.         if (!TextUtils.isEmpty(mSubject)) {
  426.             mSubjectTextEditor.setText(mSubject);
  427.         }
  428.  
  429.         try {
  430.             if (mMessageUri != null) {
  431.                 // Move the message into Draft before editing it.
  432.                 mMessageUri = mPersister.move(mMessageUri, Mms.Draft.CONTENT_URI);
  433.                 mSlideshow = SlideshowModel.createFromMessageUri(this, mMessageUri);
  434.             } else {
  435.                 mSlideshow = createNewMessage(this);
  436.                 if (mMsgText != null) {
  437.                     mSlideshow.get(0).getText().setText(mMsgText);
  438.                 }
  439.                 mMessageUri = createTemporaryMmsMessage();
  440.             }
  441.         } catch (MmsException e) {
  442.             Log.e(TAG, e.getMessage(), e);
  443.             finish();
  444.             return;
  445.         }
  446.  
  447.         // Set up the attachment editor.
  448.         mAttachmentEditor = new AttachmentEditor(this, mAttachmentEditorHandler,
  449.                 findViewById(R.id.attachment_editor));
  450.         mAttachmentEditor.setOnAttachmentChangedListener(this);
  451.  
  452.         int attachmentType = MessageUtils.getAttachmentType(mSlideshow);
  453.         if (attachmentType == AttachmentEditor.EMPTY) {
  454.             fixEmptySlideshow(mSlideshow);
  455.             attachmentType = AttachmentEditor.TEXT_ONLY;
  456.         }
  457.         mAttachmentEditor.setAttachment(mSlideshow, attachmentType);
  458.     }
  459.  
  460.     synchronized private void uninitMmsComponents() {
  461.         // Get text from slideshow if needed.
  462.         if (mAttachmentEditor != null) {
  463.             int attachmentType = mAttachmentEditor.getAttachmentType();
  464.             if (AttachmentEditor.TEXT_ONLY == attachmentType) {
  465.                 mMsgText = mSlideshow.get(0).getText().getText();
  466.             }
  467.         }
  468.  
  469.         mMessageState = 0;
  470.         mSlideshow = null;
  471.         if (mMessageUri != null) {
  472.             // Not sure if this is the best way to do this..
  473.             if (mMessageUri.toString().startsWith(Mms.Draft.CONTENT_URI.toString())) {
  474.                 SqliteWrapper.delete(this, mContentResolver, mMessageUri, null, null);
  475.                 mMessageUri = null;
  476.             }
  477.         }
  478.         if (mSubjectTextEditor != null) {
  479.             mSubjectTextEditor.setText("");
  480.             mSubjectTextEditor.setVisibility(View.GONE);
  481.             hideTopPanelIfNecessary();
  482.             mSubjectTextEditor = null;
  483.         }
  484.         mSubject = null;
  485.         mAttachmentEditor = null;
  486.     }
  487.  
  488.     synchronized private void refreshMmsComponents() {
  489.         mMessageState = RECIPIENTS_REQUIRE_MMS;
  490.         if (mSubjectTextEditor != null) {
  491.             mSubjectTextEditor.setText("");
  492.             mSubjectTextEditor.setVisibility(View.GONE);
  493.         }
  494.         mSubject = null;
  495.  
  496.         try {
  497.             mSlideshow = createNewMessage(this);
  498.             if (mMsgText != null) {
  499.                 mSlideshow.get(0).getText().setText(mMsgText);
  500.             }
  501.             mMessageUri = createTemporaryMmsMessage();
  502.         } catch (MmsException e) {
  503.             Log.e(TAG, e.getMessage(), e);
  504.             finish();
  505.             return;
  506.         }
  507.  
  508.         int attachmentType = MessageUtils.getAttachmentType(mSlideshow);
  509.         if (attachmentType == AttachmentEditor.EMPTY) {
  510.             fixEmptySlideshow(mSlideshow);
  511.             attachmentType = AttachmentEditor.TEXT_ONLY;
  512.         }
  513.         mAttachmentEditor.setAttachment(mSlideshow, attachmentType);
  514.     }
  515.  
  516.     private boolean requiresMms() {
  517.         return (mMessageState > 0);
  518.     }
  519.  
  520.     private boolean recipientsRequireMms() {
  521.         return mRecipientList.containsBcc() || mRecipientList.containsEmail();
  522.     }
  523.  
  524.     private boolean hasAttachment() {
  525.         return ((mAttachmentEditor != null)
  526.              && (mAttachmentEditor.getAttachmentType() > AttachmentEditor.TEXT_ONLY));
  527.     }
  528.  
  529.     private void updateState(int whichState, boolean set) {
  530.         if (set) {
  531.             mMessageState |= whichState;
  532.         } else {
  533.             mMessageState &= ~whichState;
  534.         }
  535.     }
  536.  
  537.     private void convertMessage(boolean toMms) {
  538.         if (LOCAL_LOGV) {
  539.             Log.v(TAG, "Message type: " + (requiresMms() ? "MMS" : "SMS")
  540.                     + " -> " + (toMms ? "MMS" : "SMS"));
  541.         }
  542.  
  543.         if (toMms) {
  544.             // Hide the counter and alert the user with a toast
  545.             if (mTextCounter != null) {
  546.                 mTextCounter.setVisibility(View.GONE);
  547.             }
  548.             initMmsComponents();
  549.         } else {
  550.             uninitMmsComponents();
  551.             // Show the counter and alert the user with a toast
  552.             if ((mTextCounter != null) && (mSeptets >= CHARS_BEFORE_COUNTER_SHOWN)) {
  553.                 mTextCounter.setVisibility(View.VISIBLE);
  554.             }
  555.         }
  556.  
  557.         updateSendButtonState();
  558.     }
  559.  
  560.     private void toastConvertInfo(boolean toMms) {
  561.         // If we didn't know whether to convert (e.g. resetting after message
  562.         // send, we need to notify the user.
  563.         int resId = toMms  ? R.string.converting_to_picture_message
  564.                            : R.string.converting_to_text_message;
  565.         Toast.makeText(this, resId, Toast.LENGTH_SHORT).show();
  566.     }
  567.  
  568.     private void convertMessageIfNeeded(int whichState, boolean set) {
  569.         int oldState = mMessageState;
  570.         updateState(whichState, set);
  571.  
  572.         boolean toMms;
  573.         // If any bits are set in the new state and none were set in the
  574.         // old state, we need to convert to MMS.
  575.         if ((oldState == 0) && (mMessageState != 0)) {
  576.             toMms = true;
  577.         } else if ((oldState != 0) && (mMessageState == 0)) {
  578.             // Vice versa, to SMS.
  579.             toMms = false;
  580.         } else {
  581.             // If we changed state but didn't change SMS vs. MMS status,
  582.             // there is nothing to do.
  583.             return;
  584.         }
  585.  
  586.         toastConvertInfo(toMms);
  587.         convertMessage(toMms);
  588.     }
  589.  
  590.     private class DeleteMessageListener implements OnClickListener {
  591.         private final Uri mDeleteUri;
  592.         private final boolean mDeleteAll;
  593.  
  594.         public DeleteMessageListener(Uri uri, boolean all) {
  595.             mDeleteUri = uri;
  596.             mDeleteAll = all;
  597.         }
  598.  
  599.         public DeleteMessageListener(long msgId, String type) {
  600.             if ("mms".equals(type)) {
  601.                 mDeleteUri = ContentUris.withAppendedId(
  602.                         Mms.CONTENT_URI, msgId);
  603.             } else {
  604.                 mDeleteUri = ContentUris.withAppendedId(
  605.                         Sms.CONTENT_URI, msgId);
  606.             }
  607.             mDeleteAll = false;
  608.         }
  609.  
  610.         public void onClick(DialogInterface dialog, int whichButton) {
  611.             SqliteWrapper.delete(ComposeMessageActivity.this, mContentResolver,
  612.                                 mDeleteUri, null, null);
  613.  
  614.             // Update the notification for new messages since they
  615.             // may be deleted.
  616.             MessagingNotification.updateNewMessageIndicator(
  617.                     ComposeMessageActivity.this);
  618.             // Update the notification for failed messages since they
  619.             // may be deleted.
  620.             MessagingNotification.updateSendFailedNotification(
  621.                     ComposeMessageActivity.this);
  622.  
  623.             if (mDeleteAll) {
  624.                 discardTemporaryMessage();
  625.                 ComposeMessageActivity.this.finish();
  626.             }
  627.         }
  628.     }
  629.  
  630.     private class ResizeButtonListener implements OnClickListener {
  631.         private final Uri mImageUri;
  632.         private final ResizeImageResultCallback
  633.         mCallback = new ResizeImageResultCallback() {
  634.             public void onResizeResult(PduPart part) {
  635.                 convertMessageIfNeeded(HAS_ATTACHMENT, true);
  636.  
  637.                 Context context = ComposeMessageActivity.this;
  638.                 Resources r = context.getResources();
  639.                 try {
  640.                     long messageId = ContentUris.parseId(mMessageUri);
  641.                     Uri newUri = mPersister.persistPart(part, messageId);
  642.                     mAttachmentEditor.changeImage(newUri);
  643.                     mAttachmentEditor.setAttachment(
  644.                             mSlideshow, AttachmentEditor.IMAGE_ATTACHMENT);
  645.                 } catch (MmsException e) {
  646.                     Toast.makeText(context,
  647.                             r.getString(R.string.failed_to_add_media, getPictureString()),
  648.                             Toast.LENGTH_SHORT).show();
  649.                 } catch (UnsupportContentTypeException e) {
  650.                     MessageUtils.showErrorDialog(context,
  651.                             r.getString(R.string.unsupported_media_format, getPictureString()),
  652.                             r.getString(R.string.select_different_media, getPictureString()));
  653.                 } catch (ResolutionException e) {
  654.                     MessageUtils.showErrorDialog(context,
  655.                             r.getString(R.string.failed_to_resize_image),
  656.                             r.getString(R.string.resize_image_error_information));
  657.                 } catch (ExceedMessageSizeException e) {
  658.                     MessageUtils.showErrorDialog(context,
  659.                             r.getString(R.string.exceed_message_size_limitation),
  660.                             r.getString(R.string.failed_to_add_media, getPictureString()));
  661.                 }
  662.             }
  663.         };
  664.  
  665.         public ResizeButtonListener(Uri uri) {
  666.             mImageUri = uri;
  667.         }
  668.  
  669.         public void onClick(DialogInterface dialog, int which) {
  670.             MessageUtils.resizeImageAsync(ComposeMessageActivity.this,
  671.                     mImageUri, mAttachmentEditorHandler, mCallback);
  672.         }
  673.     }
  674.  
  675.     private void discardTemporaryMessage() {
  676.         if (requiresMms()) {
  677.             if (mMessageUri != null) {
  678.                 SqliteWrapper.delete(ComposeMessageActivity.this,
  679.                         mContentResolver, mMessageUri, null, null);
  680.             }
  681.         } else if (mThreadId > 0) {
  682.             deleteTemporarySmsMessage(mThreadId);
  683.         }
  684.  
  685.         // Don't save this message as a draft, even if it is only an SMS.
  686.         mMsgText = "";
  687.     }
  688.  
  689.     private class DiscardDraftListener implements OnClickListener {
  690.         public void onClick(DialogInterface dialog, int whichButton) {
  691.             discardTemporaryMessage();
  692.             goToConversationList();
  693.             finish();
  694.         }
  695.     }
  696.  
  697.     private class SendIgnoreInvalidRecipientListener implements OnClickListener {
  698.         public void onClick(DialogInterface dialog, int whichButton) {
  699.             sendMessage();
  700.         }
  701.     }
  702.  
  703.     private class CancelSendingListener implements OnClickListener {
  704.         public void onClick(DialogInterface dialog, int whichButton) {
  705.             if ((mRecipientsEditor != null) &&
  706.                     (mRecipientsEditor.getVisibility() == View.VISIBLE)) {
  707.                 mRecipientsEditor.requestFocus();
  708.             }
  709.         }
  710.     }
  711.  
  712.     private void confirmSendMessageIfNeeded() {
  713.         if (mRecipientList.hasInvalidRecipient()) {
  714.             if (mRecipientList.hasValidRecipient()) {
  715.                 String title = getResourcesString(R.string.has_invalid_recipient,
  716.                         mRecipientList.getInvalidRecipientString());
  717.                 new AlertDialog.Builder(this)
  718.                     .setIcon(android.R.drawable.ic_dialog_alert)
  719.                     .setTitle(title)
  720.                     .setMessage(R.string.invalid_recipient_message)
  721.                     .setPositiveButton(R.string.try_to_send,
  722.                             new SendIgnoreInvalidRecipientListener())
  723.                     .setNegativeButton(R.string.no, new CancelSendingListener())
  724.                     .show();
  725.             } else {
  726.                 new AlertDialog.Builder(this)
  727.                     .setIcon(android.R.drawable.ic_dialog_alert)
  728.                     .setTitle(R.string.cannot_send_message)
  729.                     .setMessage(R.string.cannot_send_message_reason)
  730.                     .setPositiveButton(R.string.yes, new CancelSendingListener())
  731.                     .show();
  732.             }
  733.         } else {
  734.             sendMessage();
  735.         }
  736.     }
  737.  
  738.     private final OnFocusChangeListener mRecipientsFocusListener = new OnFocusChangeListener() {
  739.         public void onFocusChange(View v, boolean hasFocus) {
  740.             if (!hasFocus) {
  741.                 convertMessageIfNeeded(RECIPIENTS_REQUIRE_MMS, recipientsRequireMms());
  742.                 updateWindowTitle();
  743.             }
  744.         }
  745.     };
  746.  
  747.     private final TextWatcher mRecipientsWatcher = new TextWatcher() {
  748.         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  749.         }
  750.  
  751.         public void onTextChanged(CharSequence s, int start, int before, int count) {
  752.         }
  753.  
  754.         public void afterTextChanged(Editable s) {
  755.             int oldValidCount = mRecipientList.size();
  756.             int oldTotal = mRecipientList.countInvalidRecipients() + oldValidCount;
  757.  
  758.             // Refresh our local copy of the recipient list.
  759.             mRecipientList = mRecipientsEditor.getRecipientList();
  760.             // If we have gone to zero recipients, disable send button.
  761.             updateSendButtonState();
  762.  
  763.             // If a recipient has been added or deleted (or an invalid one has become valid),
  764.             // convert the message if necessary.  This causes us to "drop" conversions when
  765.             // a recipient becomes invalid, but we check again upon losing focus to ensure our
  766.             // state doesn't get too stale.  This keeps us from thrashing around between
  767.             // valid and invalid when typing in an email address.
  768.             int newValidCount = mRecipientList.size();
  769.             int newTotal = mRecipientList.countInvalidRecipients() + newValidCount;
  770.             if ((oldTotal != newTotal) || (newValidCount > oldValidCount)) {
  771.                 convertMessageIfNeeded(RECIPIENTS_REQUIRE_MMS, recipientsRequireMms());
  772.             }
  773.  
  774.             String recipients = s.toString();
  775.             if (recipients.endsWith(",") || recipients.endsWith(", ")) {
  776.                 updateWindowTitle();
  777.             }
  778.         }
  779.     };
  780.  
  781.     private final OnCreateContextMenuListener mRecipientsMenuCreateListener =
  782.         new OnCreateContextMenuListener() {
  783.         public void onCreateContextMenu(ContextMenu menu, View v,
  784.                 ContextMenuInfo menuInfo) {
  785.             if (menuInfo != null) {
  786.                 Recipient r = ((RecipientContextMenuInfo) menuInfo).recipient;
  787.                 RecipientsMenuClickListener l = new RecipientsMenuClickListener(r);
  788.  
  789.                 if (r.person_id != -1) {
  790.                     menu.add(0, MENU_VIEW_CONTACT, 0, R.string.menu_view_contact)
  791.                             .setOnMenuItemClickListener(l);
  792.                 } else {
  793.                     menu.add(0, MENU_ADD_TO_CONTACTS, 0, R.string.menu_add_to_contacts)
  794.                             .setOnMenuItemClickListener(l);
  795.                 }
  796.             }
  797.         }
  798.     };
  799.  
  800.     private final class RecipientsMenuClickListener implements MenuItem.OnMenuItemClickListener {
  801.         private final Recipient mRecipient;
  802.  
  803.         RecipientsMenuClickListener(Recipient recipient) {
  804.             mRecipient = recipient;
  805.         }
  806.  
  807.         public boolean onMenuItemClick(MenuItem item) {
  808.             switch (item.getItemId()) {
  809.                 // Context menu handlers for the recipients editor.
  810.                 case MENU_VIEW_CONTACT: {
  811.                     Uri uri = ContentUris.withAppendedId(People.CONTENT_URI,
  812.                             mRecipient.person_id);
  813.                     Intent intent = new Intent(Intent.ACTION_VIEW, uri);
  814.                     ComposeMessageActivity.this.startActivity(intent);
  815.                     return true;
  816.                 }
  817.                 case MENU_ADD_TO_CONTACTS: {
  818.                     Intent intent = new Intent(Insert.ACTION, People.CONTENT_URI);
  819.                     if (Recipient.isPhoneNumber(mRecipient.number)) {
  820.                         intent.putExtra(Insert.PHONE, mRecipient.number);
  821.                     } else {
  822.                         intent.putExtra(Insert.EMAIL, mRecipient.number);
  823.                     }
  824.                     ComposeMessageActivity.this.startActivity(intent);
  825.                     return true;
  826.                 }
  827.             }
  828.             return false;
  829.         }
  830.     }
  831.  
  832.     private void addPositionBasedMenuItems(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
  833.         AdapterView.AdapterContextMenuInfo info;
  834.  
  835.         try {
  836.             info = (AdapterView.AdapterContextMenuInfo) menuInfo;
  837.         } catch (ClassCastException e) {
  838.             Log.e(TAG, "bad menuInfo");
  839.             return;
  840.         }
  841.         final int position = info.position;
  842.  
  843.         addUriSpecificMenuItems(menu, v, position);
  844.     }
  845.  
  846.     private Uri getSelectedUriFromMessageList(ListView listView, int position) {
  847.         // If the context menu was opened over a uri, get that uri.
  848.         MessageListItem msglistItem = (MessageListItem) listView.getChildAt(position);
  849.         if (msglistItem == null) {
  850.             // FIXME: Should get the correct view. No such interface in ListView currently
  851.             // to get the view by position. The ListView.getChildAt(position) cannot
  852.             // get correct view since the list doesn't create one child for each item.
  853.             // And if setSelection(position) then getSelectedView(),
  854.             // cannot get corrent view when in touch mode.
  855.             return null;
  856.         }
  857.  
  858.         TextView textView;
  859.         CharSequence text = null;
  860.         int selStart = -1;
  861.         int selEnd = -1;
  862.  
  863.         //check if message sender is selected
  864.         textView = (TextView) msglistItem.findViewById(R.id.text_view);
  865.         if (textView != null) {
  866.             text = textView.getText();
  867.             selStart = textView.getSelectionStart();
  868.             selEnd = textView.getSelectionEnd();
  869.         }
  870.  
  871.         if (selStart == -1) {
  872.             //sender is not being selected, it may be within the message body
  873.             textView = (TextView) msglistItem.findViewById(R.id.body_text_view);
  874.             if (textView != null) {
  875.                 text = textView.getText();
  876.                 selStart = textView.getSelectionStart();
  877.                 selEnd = textView.getSelectionEnd();
  878.             }
  879.         }
  880.  
  881.         // Check that some text is actually selected, rather than the cursor
  882.         // just being placed within the TextView.
  883.         if (selStart != selEnd) {
  884.             int min = Math.min(selStart, selEnd);
  885.             int max = Math.max(selStart, selEnd);
  886.  
  887.             URLSpan[] urls = ((Spanned) text).getSpans(min, max,
  888.                                                         URLSpan.class);
  889.  
  890.             if (urls.length == 1) {
  891.                 return Uri.parse(urls[0].getURL());
  892.             }
  893.         }
  894.  
  895.         //no uri was selected
  896.         return null;
  897.     }
  898.  
  899.     private void addUriSpecificMenuItems(ContextMenu menu, View v, int position) {
  900.         Uri uri = getSelectedUriFromMessageList((ListView) v, position);
  901.  
  902.         if (uri != null) {
  903.             Intent intent = new Intent(null, uri);
  904.             intent.addCategory(Intent.CATEGORY_SELECTED_ALTERNATIVE);
  905.             menu.addIntentOptions(0, 0, 0,
  906.                     new android.content.ComponentName(this, ComposeMessageActivity.class),
  907.                     null, intent, 0, null);
  908.         }
  909.     }
  910.  
  911.     private final void addReplyMenuItems(
  912.             ContextMenu menu, MsgListMenuClickListener l, String recipient) {
  913.         if (Sms.isEmailAddress(recipient)) {
  914.             String sendEmailString = getString(
  915.                     R.string.menu_send_email).replace("%s", recipient);
  916.  
  917.             menu.add(0, MENU_SEND_EMAIL, 0, sendEmailString)
  918.                     .setOnMenuItemClickListener(l);
  919.         } else {
  920.             String callBackString = getString(
  921.                     R.string.menu_call_back).replace("%s", recipient);
  922.  
  923.             menu.add(0, MENU_CALL_BACK, 0, callBackString)
  924.                     .setOnMenuItemClickListener(l);
  925.         }
  926.     }
  927.  
  928.     private final OnCreateContextMenuListener mMsgListMenuCreateListener =
  929.         new OnCreateContextMenuListener() {
  930.         public void onCreateContextMenu(ContextMenu menu, View v,
  931.                 ContextMenuInfo menuInfo) {
  932.             String type = mMsgListCursor.getString(COLUMN_MSG_TYPE);
  933.             long msgId = mMsgListCursor.getLong(COLUMN_ID);
  934.  
  935.             addPositionBasedMenuItems(menu, v, menuInfo);
  936.  
  937.             MessageItem msgItem = mMsgListAdapter.getCachedMessageItem(type, msgId);
  938.             if (msgItem == null) {
  939.                 try {
  940.                     msgItem = mMsgListAdapter.cacheMessageItem(type, mMsgListCursor);
  941.                 } catch (MmsException e) {
  942.                     Log.e(TAG, e.getMessage());
  943.                 }
  944.             }
  945.  
  946.             if (msgItem == null) {
  947.                 Log.e(TAG, "Cannot load message item for type = " + type
  948.                         + ", msgId = " + msgId);
  949.                 return;
  950.             }
  951.  
  952.             String recipient = msgItem.mAddress;
  953.  
  954.             MsgListMenuClickListener l = new MsgListMenuClickListener();
  955.             if (msgItem.isMms()) {
  956.                 switch (msgItem.mBoxId) {
  957.                     case Mms.MESSAGE_BOX_INBOX:
  958.                         break;
  959.                     case Mms.MESSAGE_BOX_DRAFTS:
  960.                     case Mms.MESSAGE_BOX_OUTBOX:
  961.                         menu.add(0, MENU_EDIT_MESSAGE, 0, R.string.menu_edit)
  962.                                 .setOnMenuItemClickListener(l);
  963.                         break;
  964.                 }
  965.                 switch (msgItem.mAttachmentType) {
  966.                     case AttachmentEditor.TEXT_ONLY:
  967.                         break;
  968.                     case AttachmentEditor.IMAGE_ATTACHMENT:
  969.                         menu.add(0, MENU_VIEW_PICTURE, 0, R.string.view_picture)
  970.                                 .setOnMenuItemClickListener(l);
  971.                         break;
  972.                     case AttachmentEditor.SLIDESHOW_ATTACHMENT:
  973.                     default:
  974.                         menu.add(0, MENU_VIEW_SLIDESHOW, 0, R.string.view_slideshow)
  975.                                 .setOnMenuItemClickListener(l);
  976.                         break;
  977.                 }
  978.             } else {
  979.                 // Message type is sms.
  980.                 if ((msgItem.mBoxId == Sms.MESSAGE_TYPE_OUTBOX) ||
  981.                         (msgItem.mBoxId == Sms.MESSAGE_TYPE_FAILED)) {
  982.                     menu.add(0, MENU_EDIT_MESSAGE, 0, R.string.menu_edit)
  983.                             .setOnMenuItemClickListener(l);
  984.                 }
  985.             }
  986.             addReplyMenuItems(menu, l, recipient);
  987.  
  988.             // Forward is not available for undownloaded messages.
  989.             if (msgItem.isDownloaded()) {
  990.                 menu.add(0, MENU_FORWARD_MESSAGE, 0, R.string.menu_forward)
  991.                         .setOnMenuItemClickListener(l);
  992.             }
  993.  
  994.             menu.add(0, MENU_VIEW_MESSAGE_DETAILS, 0, R.string.view_message_details)
  995.                     .setOnMenuItemClickListener(l);
  996.             menu.add(0, MENU_DELETE_MESSAGE, 0, R.string.delete_message)
  997.                     .setOnMenuItemClickListener(l);
  998.             if (msgItem.mDeliveryReport || msgItem.mReadReport) {
  999.                 menu.add(0, MENU_DELIVERY_REPORT, 0, R.string.view_delivery_report)
  1000.                         .setOnMenuItemClickListener(l);
  1001.             }
  1002.         }
  1003.     };
  1004.  
  1005.     private void editMessageItem(MessageItem msgItem) {
  1006.         if ("sms".equals(msgItem.mType)) {
  1007.             editSmsMessageItem(msgItem);
  1008.         } else {
  1009.             editMmsMessageItem(msgItem);
  1010.         }
  1011.     }
  1012.  
  1013.     private void editSmsMessageItem(MessageItem msgItem) {
  1014.         // Delete the old undelivered SMS and load its content.
  1015.         Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, msgItem.mMsgId);
  1016.         SqliteWrapper.delete(ComposeMessageActivity.this,
  1017.                   mContentResolver, uri, null, null);
  1018.         mMsgText = msgItem.mBody;
  1019.     }
  1020.  
  1021.     private void editMmsMessageItem(MessageItem msgItem) {
  1022.         if (mMessageUri != null) {
  1023.             // Delete the former draft.
  1024.             SqliteWrapper.delete(ComposeMessageActivity.this,
  1025.                     mContentResolver, mMessageUri, null, null);
  1026.         }
  1027.         mMessageUri = msgItem.mMessageUri;
  1028.         ContentValues values = new ContentValues(1);
  1029.         values.put(Mms.MESSAGE_BOX, Mms.MESSAGE_BOX_DRAFTS);
  1030.         SqliteWrapper.update(ComposeMessageActivity.this,
  1031.                 mContentResolver, mMessageUri, values, null, null);
  1032.  
  1033.         updateState(RECIPIENTS_REQUIRE_MMS, recipientsRequireMms());
  1034.         if (!TextUtils.isEmpty(msgItem.mSubject)) {
  1035.             mSubject = msgItem.mSubject;
  1036.             updateState(HAS_SUBJECT, true);
  1037.         }
  1038.  
  1039.         if (msgItem.mAttachmentType > AttachmentEditor.TEXT_ONLY) {
  1040.             updateState(HAS_ATTACHMENT, true);
  1041.         }
  1042.  
  1043.         convertMessage(true);
  1044.         if (!TextUtils.isEmpty(mSubject)) {
  1045.             mSubjectTextEditor.setVisibility(View.VISIBLE);
  1046.             mTopPanel.setVisibility(View.VISIBLE);
  1047.         } else {
  1048.             mSubjectTextEditor.setVisibility(View.GONE);
  1049.             hideTopPanelIfNecessary();
  1050.         }
  1051.     }
  1052.  
  1053.     /**
  1054.      * Context menu handlers for the message list view.
  1055.      */
  1056.     private final class MsgListMenuClickListener implements MenuItem.OnMenuItemClickListener {
  1057.         public boolean onMenuItemClick(MenuItem item) {
  1058.             String type = mMsgListCursor.getString(COLUMN_MSG_TYPE);
  1059.             long msgId = mMsgListCursor.getLong(COLUMN_ID);
  1060.             MessageItem msgItem = getMessageItem(type, msgId);
  1061.  
  1062.             if (msgItem == null) {
  1063.                 return false;
  1064.             }
  1065.  
  1066.             switch (item.getItemId()) {
  1067.                 case MENU_EDIT_MESSAGE: {
  1068.                     editMessageItem(msgItem);
  1069.                     int attachmentType = requiresMms()
  1070.                             ? MessageUtils.getAttachmentType(mSlideshow)
  1071.                             : AttachmentEditor.TEXT_ONLY;
  1072.                     drawBottomPanel(attachmentType);
  1073.                     return true;
  1074.                 }
  1075.                 case MENU_FORWARD_MESSAGE: {
  1076.                     Uri uri = null;
  1077.                     Intent intent = new Intent(ComposeMessageActivity.this,
  1078.                                                ComposeMessageActivity.class);
  1079.  
  1080.                     intent.putExtra("compose_mode", true);
  1081.                     intent.putExtra("exit_on_sent", true);
  1082.                     if (type.equals("sms")) {
  1083.                         uri = ContentUris.withAppendedId(
  1084.                                 Sms.CONTENT_URI, msgId);
  1085.                         intent.putExtra("sms_body", msgItem.mBody);
  1086.                     } else {
  1087.                         SendReq sendReq = new SendReq();
  1088.                         String subject = getString(R.string.forward_prefix);
  1089.                         if (msgItem.mSubject != null) {
  1090.                             subject += msgItem.mSubject;
  1091.                         }
  1092.                         sendReq.setSubject(new EncodedStringValue(subject));
  1093.                         sendReq.setBody(msgItem.mSlideshow.makeCopy(
  1094.                                 ComposeMessageActivity.this));
  1095.  
  1096.                         try {
  1097.                             // Implicitly copy the parts of the message here.
  1098.                             uri = mPersister.persist(sendReq, Mms.Draft.CONTENT_URI);
  1099.                         } catch (MmsException e) {
  1100.                             Log.e(TAG, "Failed to copy message: " + msgItem.mMessageUri, e);
  1101.                             Toast.makeText(ComposeMessageActivity.this,
  1102.                                     R.string.cannot_save_message, Toast.LENGTH_SHORT).show();
  1103.                             return true;
  1104.                         }
  1105.  
  1106.                         intent.putExtra("msg_uri", uri);
  1107.                         intent.putExtra("subject", subject);
  1108.                     }
  1109.                     startActivityIfNeeded(intent, -1);
  1110.                     return true;
  1111.                 }
  1112.                 case MENU_VIEW_PICTURE:
  1113.                     // FIXME: Use SlideshowActivity to view image for the time being.
  1114.                     // As described in UI spec, Pressing an inline attachment will
  1115.                     // open up the full view of the attachment in its associated app
  1116.                     // (here should the pictures app).
  1117.                     // But the <ViewImage> would only show images in MediaStore.
  1118.                     // Should we save a copy to MediaStore temporarily for displaying?
  1119.                 case MENU_VIEW_SLIDESHOW: {
  1120.                     Intent intent = new Intent(ComposeMessageActivity.this,
  1121.                             SlideshowActivity.class);
  1122.                     intent.setData(ContentUris.withAppendedId(Mms.CONTENT_URI, msgId));
  1123.                     startActivity(intent);
  1124.                     return true;
  1125.                 }
  1126.                 case MENU_VIEW_MESSAGE_DETAILS: {
  1127.                     String messageDetails = MessageUtils.getMessageDetails(
  1128.                             ComposeMessageActivity.this, mMsgListCursor, msgItem.mMessageSize);
  1129.                     new AlertDialog.Builder(ComposeMessageActivity.this)
  1130.                             .setTitle(R.string.message_details_title)
  1131.                             .setMessage(messageDetails)
  1132.                             .setPositiveButton(android.R.string.ok, null)
  1133.                             .setCancelable(false)
  1134.                             .show();
  1135.                     return true;
  1136.                 }
  1137.                 case MENU_DELETE_MESSAGE: {
  1138.                     DeleteMessageListener l = new DeleteMessageListener(
  1139.                             msgItem.mMessageUri, false);
  1140.                     confirmDeleteDialog(l, false);
  1141.                     return true;
  1142.                 }
  1143.                 case MENU_DELIVERY_REPORT:
  1144.                     showDeliveryReport(msgId, type);
  1145.                     return true;
  1146.  
  1147.                 case MENU_CALL_BACK: {
  1148.                     String address = msgItem.mAddress;
  1149.  
  1150.                     if (Sms.isEmailAddress(address)) {
  1151.                         return false;
  1152.                     } else {
  1153.                         startActivity(
  1154.                                 new Intent(
  1155.                                         Intent.ACTION_DIAL,
  1156.                                         Uri.parse("tel:" + address)));
  1157.                         return true;
  1158.                     }
  1159.                 }
  1160.  
  1161.                 case MENU_SEND_EMAIL: {
  1162.                     String address = msgItem.mAddress;
  1163.  
  1164.                     if (Sms.isEmailAddress(address)) {
  1165.                         startActivity(
  1166.                                 new Intent(
  1167.                                         Intent.ACTION_VIEW,
  1168.                                         Uri.parse("mailto:" + address)));
  1169.                         return true;
  1170.                     } else {
  1171.                         return false;
  1172.                     }
  1173.                 }
  1174.  
  1175.                 default:
  1176.                     return false;
  1177.             }
  1178.         }
  1179.     }
  1180.  
  1181.     private void showDeliveryReport(long messageId, String type) {
  1182.         Intent intent = new Intent(this, DeliveryReportActivity.class);
  1183.         intent.putExtra("message_id", messageId);
  1184.         intent.putExtra("message_type", type);
  1185.  
  1186.         startActivity(intent);
  1187.     }
  1188.  
  1189.     private final IntentFilter mHttpProgressFilter = new IntentFilter(PROGRESS_STATUS_ACTION);
  1190.  
  1191.     private final BroadcastReceiver mHttpProgressReceiver = new BroadcastReceiver() {
  1192.         @Override
  1193.         public void onReceive(Context context, Intent intent) {
  1194.             if (PROGRESS_STATUS_ACTION.equals(intent.getAction())) {
  1195.                 long token = intent.getLongExtra("token",
  1196.                                     SendingProgressTokenManager.NO_TOKEN);
  1197.                 if (token != mThreadId) {
  1198.                     return;
  1199.                 }
  1200.  
  1201.                 int progress = intent.getIntExtra("progress", 0);
  1202.                 switch (progress) {
  1203.                     case PROGRESS_START:
  1204.                         setProgressBarVisibility(true);
  1205.                         break;
  1206.                     case PROGRESS_ABORT:
  1207.                     case PROGRESS_COMPLETE:
  1208.                         setProgressBarVisibility(false);
  1209.                         break;
  1210.                     default:
  1211.                         setProgress(100 * progress);
  1212.                 }
  1213.             }
  1214.         }
  1215.     };
  1216.  
  1217.     //==========================================================
  1218.     // Static methods
  1219.     //==========================================================
  1220.  
  1221.     private static SlideshowModel createNewMessage(Context context) {
  1222.         SlideshowModel slideshow = SlideshowModel.createNew(context);
  1223.         SlideModel slide = new SlideModel(slideshow);
  1224.  
  1225.         TextModel text = new TextModel(
  1226.                 context, ContentType.TEXT_PLAIN, "text_0.txt",
  1227.                 slideshow.getLayout().getTextRegion());
  1228.         slide.add(text);
  1229.  
  1230.         slideshow.add(slide);
  1231.         return slideshow;
  1232.     }
  1233.  
  1234.     private static EncodedStringValue[] encodeStrings(String[] array) {
  1235.         int count = array.length;
  1236.         if (count > 0) {
  1237.             EncodedStringValue[] encodedArray = new EncodedStringValue[count];
  1238.             for (int i = 0; i < count; i++) {
  1239.                 encodedArray[i] = new EncodedStringValue(array[i]);
  1240.             }
  1241.             return encodedArray;
  1242.         }
  1243.         return null;
  1244.     }
  1245.  
  1246.     // Get the recipients editor ready to be displayed onscreen.
  1247.     private void initRecipientsEditor() {
  1248.         ViewStub stub = (ViewStub)findViewById(R.id.recipients_editor_stub);
  1249.         mRecipientsEditor = (RecipientsEditor) stub.inflate();
  1250.  
  1251.         mRecipientsEditor.setAdapter(new RecipientsAdapter(this));
  1252.         mRecipientsEditor.populate(mRecipientList);
  1253.         mRecipientsEditor.setOnCreateContextMenuListener(mRecipientsMenuCreateListener);
  1254.         mRecipientsEditor.addTextChangedListener(mRecipientsWatcher);
  1255.         mRecipientsEditor.setOnFocusChangeListener(mRecipientsFocusListener);
  1256.         mRecipientsEditor.setFilters(new InputFilter[] {
  1257.                 new InputFilter.LengthFilter(RECIPIENTS_MAX_LENGTH) });
  1258.  
  1259.         mTopPanel.setVisibility(View.VISIBLE);
  1260.     }
  1261.  
  1262.     //==========================================================
  1263.     // Activity methods
  1264.     //==========================================================
  1265.  
  1266.     private boolean isFailedToDeliver() {
  1267.         Intent intent = getIntent();
  1268.         return (intent != null) && intent.getBooleanExtra("undelivered_flag", false);
  1269.     }
  1270.  
  1271.     private static final String[] MMS_DRAFT_PROJECTION = {
  1272.             Mms._ID,        // 0
  1273.             Mms.SUBJECT     // 1
  1274.         };
  1275.  
  1276.     private static final int MMS_ID_INDEX       = 0;
  1277.     private static final int MMS_SUBJECT_INDEX  = 1;
  1278.  
  1279.     private Cursor queryMmsDraft(long threadId) {
  1280.         final String selection = Mms.THREAD_ID + " = " + threadId;
  1281.         return SqliteWrapper.query(this, mContentResolver,
  1282.                     Mms.Draft.CONTENT_URI, MMS_DRAFT_PROJECTION,
  1283.                     selection, null, null);
  1284.     }
  1285.  
  1286.     private void loadMmsDraftIfNeeded() {
  1287.         Cursor cursor = queryMmsDraft(mThreadId);
  1288.         if (cursor != null) {
  1289.             try {
  1290.                 if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
  1291.                     mMessageUri = ContentUris.withAppendedId(Mms.Draft.CONTENT_URI,
  1292.                                         cursor.getLong(MMS_ID_INDEX));
  1293.                     mSubject = cursor.getString(MMS_SUBJECT_INDEX);
  1294.                     if (!TextUtils.isEmpty(mSubject)) {
  1295.                         updateState(HAS_SUBJECT, true);
  1296.                     }
  1297.  
  1298.                     if (!requiresMms()) {
  1299.                         // it is an MMS draft, since it has no subject or
  1300.                         // multiple recipients, it must have an attachment
  1301.                         updateState(HAS_ATTACHMENT, true);
  1302.                     }
  1303.                 }
  1304.             } finally {
  1305.                 cursor.close();
  1306.             }
  1307.         }
  1308.     }
  1309.  
  1310.     @Override
  1311.     protected void onCreate(Bundle savedInstanceState) {
  1312.         super.onCreate(savedInstanceState);
  1313.         requestWindowFeature(Window.FEATURE_PROGRESS);
  1314.         setContentView(R.layout.compose_message_activity);
  1315.         setProgressBarVisibility(false);
  1316.         setTitle("");
  1317.  
  1318.         // Initialize members for UI elements.
  1319.         initResourceRefs();
  1320.  
  1321.         mContentResolver = getContentResolver();
  1322.         mPersister = PduPersister.getPduPersister(this);
  1323.  
  1324.         // Read parameters or previously saved state of this activity.
  1325.         initActivityState(savedInstanceState, getIntent());
  1326.  
  1327.         if (LOCAL_LOGV) {
  1328.             Log.v(TAG, "onCreate(): savedInstanceState = " + savedInstanceState);
  1329.             Log.v(TAG, "onCreate(): intent = " + getIntent());
  1330.             Log.v(TAG, "onCreate(): mThreadId = " + mThreadId);
  1331.             Log.v(TAG, "onCreate(): mMessageUri = " + mMessageUri);
  1332.         }
  1333.  
  1334.         // Parse the recipient list.
  1335.         mRecipientList = RecipientList.from(mExternalAddress, this);
  1336.         updateState(RECIPIENTS_REQUIRE_MMS, recipientsRequireMms());
  1337.  
  1338.         if (isFailedToDeliver()) {
  1339.             // Show a pop-up dialog to inform user the message was
  1340.             // failed to deliver.
  1341.             undeliveredMessageDialog(getMessageDate(mMessageUri));
  1342.         }
  1343.         loadMmsDraftIfNeeded();
  1344.  
  1345.         // Initialize MMS-specific stuff if we need to.
  1346.         if ((mMessageUri != null) || requiresMms()) {
  1347.             convertMessage(true);
  1348.  
  1349.             if (!TextUtils.isEmpty(mSubject)) {
  1350.                 mSubjectTextEditor.setVisibility(View.VISIBLE);
  1351.                 mTopPanel.setVisibility(View.VISIBLE);
  1352.             } else {
  1353.                 mSubjectTextEditor.setVisibility(View.GONE);
  1354.                 hideTopPanelIfNecessary();
  1355.             }
  1356.         } else if (isEmptySms()) {
  1357.             mMsgText = readTemporarySmsMessage(mThreadId);
  1358.         }
  1359.  
  1360.         // If we are in an existing thread and we are not in "compose mode",
  1361.         // start up the message list view.
  1362.         if ((mThreadId > 0L) && !mComposeMode) {
  1363.             initMessageList(false);
  1364.         } else {
  1365.             // Otherwise, show the recipients editor.
  1366.             initRecipientsEditor();
  1367.         }
  1368.  
  1369.         int attachmentType = requiresMms()
  1370.                 ? MessageUtils.getAttachmentType(mSlideshow)
  1371.                 : AttachmentEditor.TEXT_ONLY;
  1372.         drawBottomPanel(attachmentType);
  1373.  
  1374.         updateSendButtonState();
  1375.  
  1376.         handleSendIntent(getIntent());
  1377.  
  1378.         mTopPanel.setFocusable(false);
  1379.  
  1380.         Configuration config = getResources().getConfiguration();
  1381.         mIsKeyboardOpen = config.keyboardHidden == KEYBOARDHIDDEN_NO;
  1382.         onKeyboardStateChanged(mIsKeyboardOpen);
  1383.  
  1384.         if (TRACE) {
  1385.             android.os.Debug.startMethodTracing("compose");
  1386.         }
  1387.     }
  1388.  
  1389.     private void hideTopPanelIfNecessary() {
  1390.         if ( (((mSubjectTextEditor != null) && (mSubjectTextEditor.getVisibility() != View.VISIBLE)) ||
  1391.                 (mSubjectTextEditor == null)) &&
  1392.             (((mRecipientsEditor != null) && (mRecipientsEditor.getVisibility() != View.VISIBLE)) ||
  1393.                 (mRecipientsEditor == null))) {
  1394.             mTopPanel.setVisibility(View.GONE);
  1395.         }
  1396.     }
  1397.  
  1398.     @Override
  1399.     protected void onNewIntent(Intent intent) {
  1400.         setIntent(intent);
  1401.  
  1402.         long oldThreadId = mThreadId;
  1403.         boolean oldIsMms = requiresMms();
  1404.         mMessageState = 0;
  1405.         String oldText = mMsgText;
  1406.  
  1407.         // Read parameters or previously saved state of this activity.
  1408.         initActivityState(null, intent);
  1409.  
  1410.         if (LOCAL_LOGV) {
  1411.             Log.v(TAG, "onNewIntent(): intent = " + getIntent());
  1412.             Log.v(TAG, "onNewIntent(): mThreadId = " + mThreadId);
  1413.             Log.v(TAG, "onNewIntent(): mMessageUri = " + mMessageUri);
  1414.         }
  1415.  
  1416.         if (mThreadId != oldThreadId) {
  1417.             // Save the old message as a draft.
  1418.             if (oldIsMms) {
  1419.                 // Save the old temporary message if necessary.
  1420.                 if ((mMessageUri != null) && isPreparedForSending()) {
  1421.                     try {
  1422.                         updateTemporaryMmsMessage();
  1423.                     } catch (MmsException e) {
  1424.                         Log.e(TAG, "Cannot update temporary message.", e);
  1425.                     }
  1426.                 }
  1427.             } else {
  1428.                 if (oldThreadId <= 0) {
  1429.                     oldThreadId = getOrCreateThreadId(mRecipientList.getToNumbers());
  1430.                 }
  1431.                 updateTemporarySmsMessage(oldThreadId, oldText);
  1432.             }
  1433.  
  1434.             // Refresh the recipient list.
  1435.             mRecipientList = RecipientList.from(mExternalAddress, this);
  1436.             updateState(RECIPIENTS_REQUIRE_MMS, recipientsRequireMms());
  1437.  
  1438.             if ((mThreadId > 0L) && !mComposeMode) {
  1439.                 // If we have already initialized the recipients editor, just
  1440.                 // hide it in the display.
  1441.                 if (mRecipientsEditor != null) {
  1442.                     mRecipientsEditor.setVisibility(View.GONE);
  1443.                     hideTopPanelIfNecessary();
  1444.                 }
  1445.                 initMessageList(false);
  1446.             } else {
  1447.                 initRecipientsEditor();
  1448.             }
  1449.  
  1450.             boolean isMms = (mMessageUri != null) || requiresMms();
  1451.             if (isMms != oldIsMms) {
  1452.                 convertMessage(isMms);
  1453.             }
  1454.  
  1455.             if (isMms) {
  1456.                 // Initialize subject editor.
  1457.                 if (!TextUtils.isEmpty(mSubject)) {
  1458.                     mSubjectTextEditor.setText(mSubject);
  1459.                     mSubjectTextEditor.setVisibility(View.VISIBLE);
  1460.                     mTopPanel.setVisibility(View.VISIBLE);
  1461.                 } else {
  1462.                     mSubjectTextEditor.setVisibility(View.GONE);
  1463.                     hideTopPanelIfNecessary();
  1464.                 }
  1465.  
  1466.                 try {
  1467.                     mSlideshow = createNewMessage(this);
  1468.                     mMessageUri = createTemporaryMmsMessage();
  1469.                     Toast.makeText(this, R.string.message_saved_as_draft,
  1470.                             Toast.LENGTH_SHORT).show();
  1471.                 } catch (MmsException e) {
  1472.                     Log.e(TAG, "Cannot create new slideshow and temporary message.");
  1473.                     finish();
  1474.                 }
  1475.             } else if (isEmptySms()) {
  1476.                 mMsgText = readTemporarySmsMessage(mThreadId);
  1477.             }
  1478.  
  1479.             int attachmentType = requiresMms() ? MessageUtils.getAttachmentType(mSlideshow)
  1480.                                                : AttachmentEditor.TEXT_ONLY;
  1481.             drawBottomPanel(attachmentType);
  1482.  
  1483.             if (mMsgListCursor != null) {
  1484.                 mMsgListCursor.close();
  1485.                 mMsgListCursor = null;
  1486.             }
  1487.         }
  1488.     }
  1489.  
  1490.     @Override
  1491.     protected void onStart() {
  1492.         super.onStart();
  1493.  
  1494.         updateWindowTitle();
  1495.         initFocus();
  1496.  
  1497.         // Register a BroadcastReceiver to listen on HTTP I/O process.
  1498.         registerReceiver(mHttpProgressReceiver, mHttpProgressFilter);
  1499.  
  1500.         if (mMsgListAdapter != null) {
  1501.             mMsgListAdapter.registerObservers();
  1502.             synchronized (mMsgListCursorLock) {
  1503.                 if (mMsgListCursor == null) {
  1504.                     startMsgListQuery();
  1505.                 } else {
  1506.                     SqliteWrapper.requery(this, mMsgListCursor);
  1507.                 }
  1508.             }
  1509.         }
  1510.     }
  1511.  
  1512.     @Override
  1513.     protected void onResume() {
  1514.         super.onResume();
  1515.  
  1516.         if (mThreadId > 0) {
  1517.             MessageUtils.handleReadReport(
  1518.                     ComposeMessageActivity.this, mThreadId,
  1519.                     PduHeaders.READ_STATUS_READ, null);
  1520.             MessageUtils.markAsRead(this, mThreadId);
  1521.         }
  1522.     }
  1523.  
  1524.     @Override
  1525.     public void onSaveInstanceState(Bundle outState) {
  1526.         super.onSaveInstanceState(outState);
  1527.  
  1528.         if (mThreadId > 0L) {
  1529.             if (LOCAL_LOGV) {
  1530.                 Log.v(TAG, "ONFREEZE: thread_id: " + mThreadId);
  1531.             }
  1532.             outState.putLong("thread_id", mThreadId);
  1533.         }
  1534.  
  1535.         if (LOCAL_LOGV) {
  1536.             Log.v(TAG, "ONFREEZE: address: " + mRecipientList.serialize());
  1537.         }
  1538.         outState.putString("address", mRecipientList.serialize());
  1539.  
  1540.         if (requiresMms()) {
  1541.             if ((mSubjectTextEditor != null)
  1542.                     && (View.VISIBLE == mSubjectTextEditor.getVisibility())) {
  1543.                 outState.putString("subject", mSubjectTextEditor.getText().toString());
  1544.             }
  1545.  
  1546.             if (mMessageUri != null) {
  1547.                 if (LOCAL_LOGV) {
  1548.                     Log.v(TAG, "ONFREEZE: mMessageUri: " + mMessageUri);
  1549.                 }
  1550.                 try {
  1551.                     updateTemporaryMmsMessage();
  1552.                 } catch (MmsException e) {
  1553.                     Log.e(TAG, "Cannot update message.", e);
  1554.                 }
  1555.                 outState.putParcelable("msg_uri", mMessageUri);
  1556.             }
  1557.         } else {
  1558.             outState.putString("sms_body", mMsgText);
  1559.             if (mThreadId <= 0) {
  1560.                 mThreadId = getOrCreateThreadId(mRecipientList.getToNumbers());
  1561.             }
  1562.             updateTemporarySmsMessage(mThreadId, mMsgText);
  1563.         }
  1564.  
  1565.         if (mComposeMode) {
  1566.             outState.putBoolean("compose_mode", mComposeMode);
  1567.         }
  1568.  
  1569.         if (mExitOnSent) {
  1570.             outState.putBoolean("exit_on_sent", mExitOnSent);
  1571.         }
  1572.     }
  1573.  
  1574.     private boolean isEmptyMessage() {
  1575.         if (requiresMms()) {
  1576.             return isEmptyMms();
  1577.         }
  1578.         return isEmptySms();
  1579.     }
  1580.  
  1581.     private boolean isEmptySms() {
  1582.         return TextUtils.isEmpty(mMsgText);
  1583.     }
  1584.  
  1585.     private boolean isEmptyMms() {
  1586.         return !(hasText() || hasSubject() || hasAttachment());
  1587.     }
  1588.  
  1589.     private boolean needSaveAsMms() {
  1590.         // subject editor is visible without any contents.
  1591.         if ( (mMessageState == HAS